palpic2png.c: improve, make usable with ppic binary files
[rofl0r-openDOW.git] / spriteview.c
blobe0514ea06805b95643e274956ace616de91788be
1 #include "../lib/include/timelib.h"
2 #include "../lib/include/macros.h"
3 #include "../lib/include/sblist.h"
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <stdint.h>
7 #include <stdbool.h>
8 #include <assert.h>
9 #include "vec2f.h"
10 #include "anim.h"
11 #include "gameobj.h"
12 #include "video.h"
13 #include "direction.h"
14 #include "weapon.h"
15 #include "palpic.h"
16 #include "sdl_rgb.h"
17 #include "audio.h"
18 #include "muzzle_tab.h"
19 #include "spritemaps.h"
20 #include "enemy.h"
21 #include "font.h"
22 #include "maps.h"
23 #include "mapsprites.h"
24 #include "walls.h"
25 #include "music.h"
26 #include "spawnmaps.h"
28 #include <SDL/SDL.h>
30 #ifndef IN_KDEVELOP_PARSER
31 #include "../lib/include/bitarray.h"
33 #endif
35 enum mousebutton {
36 MB_LEFT = 0,
37 MB_RIGHT,
40 // 1 if button down, 0 if not, >1 to count ms pressed
41 unsigned long mousebutton_down[] = {
42 [MB_LEFT] = 0,
43 [MB_RIGHT] = 0,
46 #pragma RcB2 LINK "-lSDL"
47 #if 0
48 static void get_last_move_event(SDL_Event* e) {
49 #define numpeek 32
50 SDL_Event peek[numpeek];
51 SDL_Event* last_event = NULL;
52 int i, results;
53 results = SDL_PeepEvents(peek, numpeek, SDL_PEEKEVENT, (uint32_t) ~0);
54 if(results == -1) return;
55 for(i = 0; i < results; i++) {
56 if(peek[i].type == SDL_MOUSEMOTION)
57 last_event = &peek[i];
58 else
59 break;
61 if(last_event) {
62 *e = *last_event;
63 SDL_PeepEvents(peek, i + 1, SDL_GETEVENT, (uint32_t) ~0);
65 #undef numpeek
67 #endif
69 static vec2f get_sprite_center(const struct palpic *p) {
70 assert(p->spritecount);
71 vec2f res;
72 res.x = palpic_getspritewidth(p) * SCALE / 2;
73 res.y = palpic_getspriteheight(p) * SCALE / 2;
74 return res;
77 static int player_ids[2];
78 static int player_kills[2];
79 static int player_score[2];
80 static int player_cash[2];
81 static enum weapon_id player_weapons[2][WP_MAX];
82 static int weapon_count[2];
83 static int weapon_active[2]; // index into player_weapons[playerno]
84 static int player_ammo[2][AMMO_MAX];
85 static enum weapon_id get_active_weapon_id(int player_no);
86 static void switch_anim(int obj_id, int aid);
87 static vec2f get_vel_from_direction(enum direction dir, float speed);
88 static vec2f get_vel_from_direction16(enum direction16 dir, float speed);
89 // used by game_tick
90 static sblist go_player_bullets;
91 static sblist go_enemy_bullets;
92 static sblist go_explosions;
93 static sblist go_enemy_explosions;
94 static sblist go_enemies;
95 static sblist go_players;
96 static sblist go_flames;
97 static sblist go_enemy_flames;
98 static sblist go_rockets;
99 static sblist go_grenades;
100 static sblist go_enemy_grenades;
101 static sblist go_vehicles;
102 static sblist go_mines;
103 static sblist go_turrets;
104 static sblist go_bunkers;
105 static sblist go_boss;
106 static sblist go_crosshair;
107 static sblist go_muzzleflash;
108 static sblist go_blood;
109 static void add_pbullet(uint8_t bullet_id) {
110 sblist_add(&go_player_bullets, &bullet_id);
112 static void add_ebullet(uint8_t bullet_id) {
113 sblist_add(&go_enemy_bullets, &bullet_id);
115 static void add_player(uint8_t player_id) {
116 sblist_add(&go_players, &player_id);
118 static void add_enemy(uint8_t enem_id) {
119 sblist_add(&go_enemies, &enem_id);
121 static void add_explosion(uint8_t expl_id) {
122 sblist_add(&go_explosions, &expl_id);
124 static void add_enemy_explosion(uint8_t expl_id) {
125 sblist_add(&go_enemy_explosions, &expl_id);
127 static void add_flame(uint8_t id) {
128 sblist_add(&go_flames, &id);
130 static void add_grenade(uint8_t id) {
131 sblist_add(&go_grenades, &id);
133 static void add_enemy_grenade(uint8_t id) {
134 sblist_add(&go_enemy_grenades, &id);
136 static void add_rocket(uint8_t id) {
137 sblist_add(&go_rockets, &id);
139 static void add_vehicle(uint8_t id) {
140 sblist_add(&go_vehicles, &id);
142 static void add_mine(uint8_t id) {
143 sblist_add(&go_mines, &id);
145 static void add_turret(uint8_t id) {
146 sblist_add(&go_turrets, &id);
148 static void add_bunker(uint8_t id) {
149 sblist_add(&go_bunkers, &id);
151 static void add_boss(uint8_t id) {
152 sblist_add(&go_boss, &id);
154 static void add_crosshair(uint8_t id) {
155 sblist_add(&go_crosshair, &id);
157 static void add_muzzleflash(uint8_t id) {
158 sblist_add(&go_muzzleflash, &id);
160 static void add_blood(uint8_t id) {
161 sblist_add(&go_blood, &id);
163 static void add_enemy_flame(uint8_t id) {
164 sblist_add(&go_enemy_flames, &id);
166 static void golist_remove(sblist *l, uint8_t objid) {
167 size_t i;
168 uint8_t *itemid;
169 sblist_iter_counter2(l, i, itemid) {
170 if(*itemid == objid) {
171 sblist_delete(l, i);
172 return;
177 static int get_next_anim_frame(enum animation_id aid, anim_step curr) {
178 if(curr == ANIM_STEP_INIT) return animations[aid].first;
179 curr++;
180 if(curr > animations[aid].last) return animations[aid].first;
181 return curr;
184 #define SCREEN_MIN_X 64*SCALE
185 #define SCREEN_MAX_X VMODE_W - 64*SCALE
186 #define SCREEN_MIN_Y 0
187 #define SCREEN_MAX_Y 200*SCALE
189 static void draw_status_bar(void) {
190 enum weapon_id wid = get_active_weapon_id(0);
191 int x, y;
192 sdl_rgb_t *ptr = (sdl_rgb_t *) video.mem;
193 unsigned pitch = video.pitch/4;
194 for(y = SCREEN_MAX_Y; y < VMODE_H; y++)
195 for (x = SCREEN_MIN_X; x < SCREEN_MAX_X; x++)
196 ptr[y*pitch + x] = SRGB_BLACK;
198 blit_sprite(((320 / 2) - (59 / 2)) * SCALE, (200 + (40/2) - (16/2)) * SCALE,
199 &video, SCALE, spritemaps[SI_WEAPONS], wid, 0);
201 char buf[16];
202 snprintf(buf, 16, "%.6u", player_score[0]);
203 font_print(SCREEN_MIN_X + 8, SCREEN_MAX_Y + 8, buf, 6, 1 * SCALE, PRGB(255,255,255));
206 enum map_index current_map = MI_VIETNAM;
207 const struct map *map;
208 const struct map_screen* map_scr;
209 const struct palpic *map_bg;
210 const struct palpic *map_fg;
211 const mapscreen_index *map_bonus_indices;
212 const struct map_fglayer *map_bonus_scr;
214 int mapscreen_yoff, mapscreen_xoff;
215 struct { int x,y; } mapsquare;
216 enum map_scrolldir mapscrolldir;
217 unsigned map_spawn_screen_index;
218 unsigned map_spawn_line;
219 unsigned map_spawn_current;
221 static const int fps = 64;
223 static void init_map(enum map_index mapindex) {
224 map = maps[mapindex];
225 map_scr = map_screens[mapindex];
226 map_bg = map_bg_sprites[map->maptype];
227 map_fg = map_fg_sprites[map->maptype];
228 map_bonus_scr = map_bonus_screens[mapindex];
229 map_bonus_indices = map_bonus_layer_indices[mapindex];
230 mapscreen_yoff = 0;
231 mapscreen_xoff = 0;
232 mapsquare.x = 5;
233 mapsquare.y = 26;
234 mapscrolldir = MS_UP;
235 map_spawn_screen_index = 0;
236 map_spawn_line = 0;
237 map_spawn_current = 0;
240 static mapscreen_index get_bonus_layer_index(mapscreen_index screen) {
241 unsigned i;
242 for (i = 0; i < map->bonuslayer_count; i++)
243 if(map_bonus_indices[i] == screen) return i;
244 return MAPSCREEN_BLOCKED;
247 static mapscreen_index screen_to_mapscreen(int *x, int *y) {
248 *x = ((int) *x - SCREEN_MIN_X) / SCALE;
249 *y = ((int) *y - SCREEN_MIN_Y) / SCALE;
250 int yscr = (*y + mapscreen_yoff) / 192;
251 *y = (*y + mapscreen_yoff) - yscr*192;
252 int xscr = (*x + mapscreen_xoff) / 192;
253 *x = (*x + mapscreen_xoff) - xscr*192;
254 return map->screen_map[mapsquare.y + yscr][mapsquare.x + xscr];
257 static enum walltype is_wall(vec2f *pos) {
258 int x = pos->x;
259 int y = pos->y;
260 mapscreen_index scr_idx = screen_to_mapscreen(&x, &y);
261 /* can happen when a bullet goes partially off-screen */
262 if(scr_idx == MAPSCREEN_BLOCKED) return WT_NONE;
263 uint8_t spriteno = map_scr[scr_idx].fg.fg[y/16][x/16];
264 if(spriteno && walls[map->maptype][spriteno]) return walls[map->maptype][spriteno];
265 scr_idx = get_bonus_layer_index(scr_idx);
266 if(scr_idx == MAPSCREEN_BLOCKED) return WT_NONE;
267 spriteno = map_bonus_scr[scr_idx].fg[y/16][x/16];
268 if(spriteno && walls[map->maptype][spriteno]) return walls[map->maptype][spriteno];
269 return WT_NONE;
272 static void draw_map() {
273 int y, x, my, mx;
274 unsigned map_off = 192-mapscreen_yoff;
275 unsigned vis_x = 192-mapscreen_xoff;
276 int x_iter_max16 = 192/16;
277 int x_iter_max64 = 192/64;
278 unsigned x_iter_start64 = x_iter_max64 - (vis_x / 64 + !!(vis_x % 64));
279 unsigned x_iter_start16 = x_iter_max16 - (vis_x / 16 + !!(vis_x % 16));
281 int x_screen_start64 = -(!!(vis_x%64)*64-(vis_x%64));
282 int x_screen_start16 = -(!!(vis_x%16)*16-(vis_x%16));
284 unsigned x_screen_iter;
285 for(x_screen_iter = 0; x_screen_iter <= !!mapscreen_xoff; x_screen_iter++) {
286 mapscreen_index bonus_layer;
287 if(x_screen_iter) {
288 x_screen_start16 = x_screen_start16+(x_iter_max16-x_iter_start16)*16;
289 x_screen_start64 = x_screen_start64+(x_iter_max64-x_iter_start64)*64;
290 x_iter_max16 = mapscreen_xoff / 16 + !!(mapscreen_xoff % 16);
291 x_iter_max64 = mapscreen_xoff / 64 + !!(mapscreen_xoff % 64);
292 x_iter_start16 = 0;
293 x_iter_start64 = 0;
294 assert(map->screen_map[mapsquare.y][mapsquare.x+x_screen_iter] != MAPSCREEN_BLOCKED);
296 uint8_t spriteno;
298 for(my = 6-map_off/32-!!(map_off%32), y = SCREEN_MIN_Y + (!!(map_off%32)*32-(map_off%32))*-SCALE; my < 6; my++, y+=32*SCALE)
299 for(mx = x_iter_start64, x = SCREEN_MIN_X + x_screen_start64*SCALE; mx < x_iter_max64; mx++, x += 64*SCALE) {
300 spriteno = map_scr[map->screen_map[mapsquare.y][mapsquare.x+x_screen_iter]].bg.bg[my][mx];
301 blit_sprite(x, y, &video,
302 SCALE, map_bg, spriteno, 0);
304 for(my = 12-map_off/16-!!(map_off%16), y = SCREEN_MIN_Y + (!!(map_off%16)*16-(map_off%16))*-SCALE; my < 12; my++, y+=16*SCALE)
305 for(mx = x_iter_start16, x = SCREEN_MIN_X + x_screen_start16*SCALE; mx < x_iter_max16; mx++, x += 16*SCALE) {
306 spriteno = map_scr[map->screen_map[mapsquare.y][mapsquare.x+x_screen_iter]].fg.fg[my][mx];
307 if(spriteno) blit_sprite(x, y, &video, SCALE, map_fg, spriteno, 0);
309 bonus_layer = get_bonus_layer_index(map->screen_map[mapsquare.y][mapsquare.x+x_screen_iter]);
310 if(bonus_layer != MAPSCREEN_BLOCKED) {
311 for(my = 12-map_off/16-!!(map_off%16), y = SCREEN_MIN_Y + (!!(map_off%16)*16-(map_off%16))*-SCALE; my < 12; my++, y+=16*SCALE)
312 for(mx = x_iter_start16, x = SCREEN_MIN_X + x_screen_start16*SCALE; mx < x_iter_max16; mx++, x += 16*SCALE) {
313 spriteno = map_bonus_scr[bonus_layer].fg[my][mx];
314 if(spriteno) blit_sprite(x, y, &video, SCALE, map_fg, spriteno, 0);
319 int yleft = 200-map_off;
320 if(yleft > 192) yleft = 192;
321 for(my = 0, y = SCREEN_MIN_Y + (map_off * SCALE); my < yleft/32+!!(yleft%32); my++, y+=32*SCALE)
322 for(mx = x_iter_start64, x = SCREEN_MIN_X + x_screen_start64*SCALE; mx < x_iter_max64; mx++, x += 64*SCALE) {
323 spriteno = map_scr[map->screen_map[mapsquare.y+1][mapsquare.x+x_screen_iter]].bg.bg[my][mx];
324 blit_sprite(x, y, &video, SCALE, map_bg, spriteno, 0);
326 for(my = 0, y = SCREEN_MIN_Y + (map_off * SCALE); my < yleft/16+!!(yleft%16); my++, y+=16*SCALE)
327 for(mx = x_iter_start16, x = SCREEN_MIN_X + x_screen_start16*SCALE; mx < x_iter_max16; mx++, x += 16*SCALE) {
328 spriteno = map_scr[map->screen_map[mapsquare.y+1][mapsquare.x+x_screen_iter]].fg.fg[my][mx];
329 if(spriteno) blit_sprite(x, y, &video, SCALE, map_fg, spriteno, 0);
332 bonus_layer = get_bonus_layer_index(map->screen_map[mapsquare.y+1][mapsquare.x+x_screen_iter]);
333 if(bonus_layer != MAPSCREEN_BLOCKED) {
334 for(my = 0, y = SCREEN_MIN_Y + (map_off * SCALE); my < yleft/16+!!(yleft%16); my++, y+=16*SCALE)
335 for(mx = x_iter_start16, x = SCREEN_MIN_X + x_screen_start16*SCALE; mx < x_iter_max16; mx++, x += 16*SCALE) {
336 spriteno = map_bonus_scr[bonus_layer].fg[my][mx];
337 if(spriteno) blit_sprite(x, y, &video, SCALE, map_fg, spriteno, 0);
341 /* this is never triggered when mapscreen_xoff != 0 */
342 if(mapscreen_yoff > 192 - 8) {
343 for(mx = 0, x = SCREEN_MIN_X + x_screen_start64*SCALE; mx < 3; mx++, x += 64*SCALE)
344 blit_sprite(x, SCALE*(192*2-mapscreen_yoff), &video,
345 SCALE, map_bg, map_scr[map->screen_map[mapsquare.y+2][mapsquare.x]].bg.bg[0][mx], 0);
346 for(mx = 0, x = SCREEN_MIN_X + x_screen_start16*SCALE; mx < 12; mx++, x += 16*SCALE) {
347 spriteno = map_scr[map->screen_map[mapsquare.y+2][mapsquare.x]].fg.fg[0][mx];
348 if(spriteno) blit_sprite(x, SCALE*(192*2-mapscreen_yoff), &video, SCALE, map_fg, spriteno, 0);
350 bonus_layer = get_bonus_layer_index(map->screen_map[mapsquare.y+2][mapsquare.x]);
351 if(bonus_layer != MAPSCREEN_BLOCKED) {
352 for(mx = 0, x = SCREEN_MIN_X + x_screen_start16*SCALE; mx < 12; mx++, x += 16*SCALE) {
353 spriteno = map_bonus_scr[bonus_layer].fg[0][mx];
354 if(spriteno) blit_sprite(x, SCALE*(192*2-mapscreen_yoff), &video, SCALE, map_fg, spriteno, 0);
361 #define VSCROLL_TRESHOLD (200-74)
362 #define HSCROLLR_TRESHOLD (54-6)
363 #define HSCROLLL_TRESHOLD (192-(78+3))
364 static int scroll_needed() {
365 struct gameobj *player = &objs[player_ids[0]];
366 if((mapscrolldir == MS_UP && player->pos.y - SCREEN_MIN_Y < VSCROLL_TRESHOLD*SCALE) ||
367 (mapscrolldir == MS_RIGHT && player->pos.x - SCREEN_MIN_X > HSCROLLR_TRESHOLD*SCALE) ||
368 (mapscrolldir == MS_LEFT && player->pos.x - SCREEN_MIN_X < HSCROLLL_TRESHOLD*SCALE))
369 return 1;
370 return 0;
373 static void scroll_gameobjs(int scroll_step) {
374 if(!scroll_step) return;
375 unsigned i, avail = obj_count;
376 for(i = 0; i < OBJ_MAX && avail; i++) {
377 if(!obj_slot_used[i]) continue;
378 avail--;
379 if(objs[i].objtype == OBJ_CROSSHAIR) continue;
381 if(mapscrolldir == MS_UP)
382 objs[i].pos.y += scroll_step*SCALE;
383 else if(mapscrolldir == MS_LEFT)
384 objs[i].pos.x += scroll_step*SCALE;
385 else if(mapscrolldir == MS_RIGHT)
386 objs[i].pos.x -= scroll_step*SCALE;
390 static void next_screen() {
391 map_spawn_screen_index++;
392 map_spawn_line = 0;
393 map_spawn_current = 0;
396 static int init_enemy(const struct enemy_spawn *spawn);
397 static void handle_spawns(unsigned scrollstep) {
398 assert(scrollstep <= 192);
399 unsigned i;
400 const struct enemy_spawn_screen *spawn_map = spawn_maps[current_map];
401 if(!spawn_map[map_spawn_screen_index].spawns) goto done;
402 for(i = 0; i < scrollstep; i++) {
403 while(map_spawn_current < spawn_map[map_spawn_screen_index].num_spawns &&
404 map_spawn_line+i >= spawn_map[map_spawn_screen_index].spawns[map_spawn_current].scroll_line) {
405 init_enemy(&spawn_map[map_spawn_screen_index].spawns[map_spawn_current]);
406 map_spawn_current++;
409 done:
410 map_spawn_line += scrollstep;
413 static int scroll_map() {
414 int ret = 0;
415 int scroll_step = 1;
416 if(scroll_needed()) {
417 if(mapscrolldir == MS_UP) {
418 mapscreen_yoff -= scroll_step;
419 if(mapscreen_yoff < 0) {
420 mapsquare.y--;
421 if(map->screen_map[mapsquare.y][mapsquare.x] == MAPSCREEN_BLOCKED) {
422 scroll_step = -mapscreen_yoff;
423 mapscreen_yoff = 0;
424 mapsquare.y++;
425 scroll_gameobjs(scroll_step);
426 if(map->screen_map[mapsquare.y][mapsquare.x+1] == MAPSCREEN_BLOCKED) {
427 mapscrolldir = MS_LEFT;
428 } else {
429 mapscrolldir = MS_RIGHT;
430 next_screen();
432 scroll_step = 0;
433 } else {
434 next_screen();
435 mapscreen_yoff += 192;
438 handle_objs:;
439 handle_spawns(scroll_step);
440 scroll_gameobjs(scroll_step);
441 ret = 1;
442 } else if(mapscrolldir == MS_LEFT) {
443 mapscreen_xoff -= scroll_step;
444 if(mapscreen_xoff < 0) {
445 mapsquare.x--;
446 if(map->screen_map[mapsquare.y][mapsquare.x] == MAPSCREEN_BLOCKED) {
447 scroll_step = -mapscreen_xoff;
448 mapscreen_xoff = 0;
449 mapscreen_yoff = 0;
450 mapsquare.x++;
451 scroll_gameobjs(scroll_step);
452 mapscrolldir = MS_UP;
453 scroll_step = 0;
454 } else {
455 mapscreen_xoff += 192;
456 next_screen();
459 goto handle_objs;
460 } else if(mapscrolldir == MS_RIGHT) {
461 mapscreen_xoff += scroll_step;
462 if(mapscreen_xoff >= 192) {
463 mapsquare.x++;
464 if(map->screen_map[mapsquare.y][mapsquare.x+1] == MAPSCREEN_BLOCKED) {
465 scroll_step = mapscreen_xoff - 192;
466 mapscreen_xoff = 0;
467 mapscreen_yoff = 0;
468 scroll_gameobjs(scroll_step);
469 mapscrolldir = MS_UP;
470 scroll_step = 0;
471 } else {
472 next_screen();
473 mapscreen_xoff -= 192;
476 goto handle_objs;
479 return ret;
482 static void init_player_once(int player_no) {
483 player_score[player_no] = 0;
484 player_cash[player_no] = 10000;
487 static int init_player(int player_no) {
488 assert(player_no == 0 || player_no == 1);
489 int pid = gameobj_alloc();
490 gameobj_init(pid, &VEC( SCREEN_MIN_X, SCREEN_MAX_Y - (25 * SCALE) ), &VEC( 0, 0 ),
491 SI_PLAYERS, player_no == 0 ? ANIM_P1_MOVE_N : ANIM_P2_MOVE_N, player_no == 0 ? OBJ_P1 : OBJ_P2);
492 if(pid == -1) return -1;
493 player_ids[player_no] = pid;
494 weapon_active[player_no] = 0;
496 add_player(pid);
497 return pid;
500 static vec2f *mousepos;
501 static int init_crosshair() {
502 int id = gameobj_alloc();
503 gameobj_init(id, &VEC(VMODE_W/2, VMODE_H/2), &VEC(0,0), SI_CROSSHAIR, ANIM_CROSSHAIR, OBJ_CROSSHAIR);
504 if(id == -1) return -1;
505 mousepos = &objs[id].pos;
506 return id;
509 static int init_blood(vec2f *pos) {
510 int id = gameobj_alloc();
511 gameobj_init(id, pos, &VEC(0,0), SI_MISC, ANIM_BLOOD, OBJ_BLOOD);
512 gameobj_init_bulletdata(id, 4);
513 return id;
516 static int init_bullet(vec2f *pos, vec2f *vel, int steps) {
517 int id = gameobj_alloc();
518 gameobj_init(id, pos, vel, SI_BULLET, ANIM_BULLET, OBJ_BULLET);
519 gameobj_init_bulletdata(id, steps);
520 return id;
523 static int init_grenade(vec2f *pos, vec2f *vel, int steps) {
524 int id = gameobj_alloc();
525 gameobj_init(id, pos, vel, SI_GRENADE, ANIM_GRENADE_SMALL, OBJ_GRENADE);
526 gameobj_init_bulletdata(id, steps);
527 return id;
530 static int init_grenade_explosion(vec2f *pos, int from_enemy) {
531 const int ticks_per_anim_frame = 4;
532 const int expl_anim_frames = 11;
533 vec2f grenade_center = get_sprite_center(spritemaps[SI_GRENADE_EXPLOSION]);
534 vec2f mypos = vecsub(pos, &grenade_center);
535 int id = gameobj_alloc();
536 if(id == -1) return -1;
537 gameobj_init(id, &mypos, &VEC(0,0), SI_GRENADE_EXPLOSION, ANIM_GRENADE_EXPLOSION, OBJ_GRENADE_EXPLOSION);
538 gameobj_init_bulletdata(id, expl_anim_frames*ticks_per_anim_frame -1);
539 audio_play_wave_resource(wavesounds[WS_GRENADE_EXPLOSION]);
540 if(!from_enemy) add_explosion(id);
541 else add_enemy_explosion(id);
542 return id;
545 static int init_big_explosion(vec2f *pos) {
546 const int ticks_per_anim_frame = 8;
547 const int expl_anim_frames = 5;
548 vec2f rocket_center = get_sprite_center(spritemaps[SI_BIG_EXPLOSION]);
549 vec2f mypos = vecsub(pos, &rocket_center);
550 int id = gameobj_alloc();
551 if(id == -1) return -1;
552 gameobj_init(id, &mypos, &VEC(0,0), SI_BIG_EXPLOSION, ANIM_BIG_EXPLOSION, OBJ_BIG_EXPLOSION);
553 gameobj_init_bulletdata(id, expl_anim_frames*ticks_per_anim_frame -1);
554 audio_play_wave_resource(wavesounds[WS_GRENADE_EXPLOSION]);
555 add_explosion(id);
556 return id;
559 static int init_rocket_explosion(vec2f *pos) {
560 vec2f ax = vecadd(pos, &VEC(-15*SCALE, 9*SCALE));
561 vec2f bx = vecadd(pos, &VEC(1*SCALE, -6*SCALE));
562 vec2f cx = vecadd(pos, &VEC(-8*SCALE, -8*SCALE));
563 vec2f dx = vecadd(pos, &VEC(8*SCALE, 8*SCALE));
564 int ret = 0;
565 ret += init_grenade_explosion(&ax, 0) != -1;
566 ret += init_grenade_explosion(&bx, 0) != -1;
567 ret += init_big_explosion(&cx) != -1;
568 ret += init_big_explosion(&dx) != -1;
569 return ret;
572 static int init_flame(vec2f *pos, vec2f *vel, int steps) {
573 int id = gameobj_alloc();
574 if(id == -1) return -1;
575 gameobj_init(id, pos, vel, SI_FLAME, ANIM_FLAME, OBJ_FLAME);
576 gameobj_init_bulletdata(id, steps);
577 return id;
580 static int init_player_flame(enum direction dir, vec2f *pos, vec2f *vel, int steps) {
581 static const vec2f flame_origin[] = {
582 [DIR_O] = { 4.0, 8.0 },
583 [DIR_NO] = { 5.0, 11.0 },
584 [DIR_N] = { 7.5, 12.0 },
585 [DIR_NW] = { 10.0, 11.0 },
586 [DIR_W] = { 11.0, 8.0 },
587 [DIR_SW] = { 10.0, 5.0 },
588 [DIR_S] = { 7.5, 3.0 },
589 [DIR_SO] = { 4.0, 4.0 },
591 vec2f mypos = *pos;
592 mypos.x -= flame_origin[dir].x * SCALE;
593 mypos.y -= flame_origin[dir].y * SCALE;
594 return init_flame(&mypos, vel, steps);
597 static int init_rocket(enum direction dir, vec2f *pos, vec2f *vel, int steps) {
598 static const vec2f rocket_origin[] = {
599 [DIR_N] = { 1.0, 10.0 },
600 [DIR_S] = { 1.0, 0.0 },
601 [DIR_O] = { 0.0, 1.0 },
602 [DIR_W] = { 10.0, 1.0 },
603 [DIR_NO] = { 0.0, 7.0 },
604 [DIR_SO] = { 0.0, 0.0 },
605 [DIR_SW] = { 7.0, 0.0 },
606 [DIR_NW] = { 7.0, 7.0 },
608 static const enum animation_id rocket_anim[] = {
609 [DIR_N] = ANIM_ROCKET_N,
610 [DIR_S] = ANIM_ROCKET_S,
611 [DIR_O] = ANIM_ROCKET_O,
612 [DIR_W] = ANIM_ROCKET_W,
613 [DIR_NO] = ANIM_ROCKET_NO,
614 [DIR_SO] = ANIM_ROCKET_SO,
615 [DIR_SW] = ANIM_ROCKET_SW,
616 [DIR_NW] = ANIM_ROCKET_NW,
618 vec2f mypos = *pos;
619 mypos.x -= rocket_origin[dir].x * SCALE;
620 mypos.y -= rocket_origin[dir].y * SCALE;
621 int id = gameobj_alloc();
622 if(id == -1) return -1;
623 gameobj_init(id, &mypos, vel, SI_ROCKET, rocket_anim[dir], OBJ_ROCKET);
624 gameobj_init_bulletdata(id, steps);
625 add_rocket(id);
626 return id;
630 static const struct enemy_route* get_enemy_current_route(int curr_step, const struct enemy_spawn *spawn) {
631 int i = ENEMY_MAX_ROUTE -1;
632 for(; i >= 0; i--)
633 if(spawn->route[i].shape != ES_INVALID &&
634 curr_step >= spawn->route[i].start_step)
635 return &spawn->route[i];
636 return 0;
639 static vec2f get_enemy_vel(int curr_step, const struct enemy_spawn *spawn) {
640 const struct enemy_route *route = get_enemy_current_route(curr_step, spawn);
641 return get_vel_from_direction16(route->dir, (float)route->vel/8.f);
644 static const enum animation_id enemy_animation_lut[] = {
645 [ES_SOLDIER1_DOWN] = ANIM_ENEMY_GUNNER_DOWN,
646 [ES_SOLDIER1_RIGHT] = ANIM_ENEMY_GUNNER_RIGHT,
647 [ES_SOLDIER1_LEFT] = ANIM_ENEMY_GUNNER_LEFT,
648 [ES_SOLDIER2_DOWN] = ANIM_ENEMY_BOMBER_DOWN,
649 [ES_SOLDIER2_RIGHT] = ANIM_ENEMY_BOMBER_RIGHT,
650 [ES_SOLDIER2_LEFT] = ANIM_ENEMY_BOMBER_LEFT,
651 [ES_JEEP] = ANIM_JEEP,
652 [ES_TANK_SMALL] = ANIM_TANK_SMALL,
653 [ES_TANK_BIG] = ANIM_TANK_BIG,
654 [ES_TRANSPORTER] = ANIM_TRANSPORTER,
655 [ES_GUNTURRET_MOVABLE_MACHINE] = ANIM_GUNTURRET_MOVABLE_MACHINE_S,
656 [ES_GUNTURRET_MOVABLE_MAN] = ANIM_GUNTURRET_MOVABLE_MAN_S,
657 [ES_MINE_FLAT] = ANIM_MINE_FLAT,
658 [ES_MINE_CROSS] = ANIM_MINE_CROSSED,
659 [ES_FLAMETURRET] = ANIM_FLAMETURRET,
660 [ES_GUNTURRET_FIXED_SOUTH] = ANIM_GUNTURRET_FIXED_SOUTH,
661 [ES_GUNTURRET_FIXED_NORTH] = ANIM_GUNTURRET_FIXED_NORTH,
662 [ES_BUNKER_1] = ANIM_BUNKER1,
663 [ES_BUNKER_2] = ANIM_BUNKER2,
664 [ES_BUNKER_3] = ANIM_BUNKER3,
665 [ES_BUNKER_4] = ANIM_BUNKER4,
666 [ES_BUNKER_5] = ANIM_BUNKER5,
669 static int init_enemy(const struct enemy_spawn *spawn) {
670 const enum objtype enemy_soldier_objtype_lut[] = {
671 [0] = OBJ_ENEMY_SHOOTER,
672 [1] = OBJ_ENEMY_BOMBER
674 const enum objtype enemy_objtype_lut[] = {
675 [ES_JEEP] = OBJ_JEEP,
676 [ES_TANK_SMALL] = OBJ_TANK_SMALL,
677 [ES_TANK_BIG] = OBJ_TANK_BIG,
678 [ES_TRANSPORTER] = OBJ_TRANSPORTER,
679 [ES_GUNTURRET_MOVABLE_MACHINE] = OBJ_GUNTURRET_MOVABLE_MACHINE,
680 [ES_GUNTURRET_MOVABLE_MAN] = OBJ_GUNTURRET_MOVABLE_MAN,
681 [ES_MINE_FLAT] = OBJ_MINE_FLAT,
682 [ES_MINE_CROSS] = OBJ_MINE_CROSSED,
683 [ES_FLAMETURRET] = OBJ_FLAMETURRET,
684 [ES_GUNTURRET_FIXED_SOUTH] = OBJ_GUNTURRET_FIXED_SOUTH,
685 [ES_GUNTURRET_FIXED_NORTH] = OBJ_GUNTURRET_FIXED_NORTH,
686 [ES_BUNKER_1] = OBJ_BUNKER1,
687 [ES_BUNKER_2] = OBJ_BUNKER2,
688 [ES_BUNKER_3] = OBJ_BUNKER3,
689 [ES_BUNKER_4] = OBJ_BUNKER4,
690 [ES_BUNKER_5] = OBJ_BUNKER5,
691 [ES_BOSS] = OBJ_BOSS,
693 const enum animation_id boss_animation_lut[] = {
694 [0] = ANIM_BOSS1,
695 [1] = ANIM_BOSS2,
696 [2] = ANIM_BOSS3,
697 [3] = ANIM_BOSS4,
698 [4] = ANIM_BOSS5,
699 [5] = ANIM_BOSS6,
700 [6] = ANIM_BOSS7,
701 [7] = ANIM_BOSS8,
702 [8] = ANIM_BOSS9,
703 [9] = ANIM_BOSS10,
704 [10] = ANIM_BOSS11,
705 [11] = ANIM_BOSS12,
707 const enum sprite_index enemy_soldier_sprite_lut[] = {
708 [ET_ASIAN] = SI_ENEMY_ASIAN,
709 [ET_WESTERN] = SI_ENEMY_WESTERN,
711 const enum sprite_index enemy_sprite_lut[] = {
712 [ES_JEEP] = SI_VEHICLES_SMALL,
713 [ES_TANK_SMALL] = SI_VEHICLES_MEDIUM,
714 [ES_TANK_BIG] = SI_VEHICLES_BIG,
715 [ES_TRANSPORTER] = SI_VEHICLES_BIG,
716 [ES_BUNKER_1] = SI_BUNKERS,
717 [ES_BUNKER_2] = SI_BUNKERS,
718 [ES_BUNKER_3] = SI_BUNKERS,
719 [ES_BUNKER_4] = SI_BUNKERS,
720 [ES_BUNKER_5] = SI_BUNKERS,
721 [ES_GUNTURRET_MOVABLE_MACHINE] = SI_GUNTURRET,
722 [ES_GUNTURRET_MOVABLE_MAN] = SI_GUNTURRET,
723 [ES_MINE_FLAT] = SI_MINES,
724 [ES_MINE_CROSS] = SI_MINES,
725 [ES_FLAMETURRET] = SI_MINES,
726 [ES_GUNTURRET_FIXED_SOUTH] = SI_MINES,
727 [ES_GUNTURRET_FIXED_NORTH] = SI_MINES,
728 [ES_BOSS] = SI_BOSSES,
731 vec2f spawnpos = VEC(SCREEN_MIN_X + spawn->x*SCALE, SCREEN_MIN_Y + spawn->y*SCALE);
732 int id = gameobj_alloc();
733 if(id == -1) return -1;
734 const struct enemy_route* route_curr = get_enemy_current_route(0, spawn);
735 vec2f vel = get_enemy_vel(0, spawn);
737 int is_soldier = route_curr->shape <= ES_SOLDIER2_RIGHT;
738 int is_boss = route_curr->shape == ES_BOSS;
739 enum sprite_index spriteid;
740 enum objtype objid;
741 enum animation_id animid;
742 if(is_soldier) {
743 spriteid = enemy_soldier_sprite_lut[map->enemy_type];
744 objid = enemy_soldier_objtype_lut[spawn->weapon];
745 } else {
746 spriteid = enemy_sprite_lut[route_curr->shape];
747 objid = enemy_objtype_lut[route_curr->shape];
749 if(is_boss) animid = boss_animation_lut[map->boss_id];
750 else animid = enemy_animation_lut[route_curr->shape];
752 gameobj_init(id, &spawnpos, &vel, spriteid, animid, objid);
753 objs[id].objspecific.enemy.curr_step = 0;
754 objs[id].objspecific.enemy.spawn = spawn;
755 switch(objid) {
756 case OBJ_BOSS:
757 add_boss(id);
758 break;
759 case OBJ_BUNKER1: case OBJ_BUNKER2: case OBJ_BUNKER3:
760 case OBJ_BUNKER4: case OBJ_BUNKER5:
761 add_bunker(id);
762 break;
763 case OBJ_GUNTURRET_MOVABLE_MACHINE:
764 case OBJ_GUNTURRET_MOVABLE_MAN:
765 case OBJ_FLAMETURRET: case OBJ_GUNTURRET_FIXED_NORTH:
766 case OBJ_GUNTURRET_FIXED_SOUTH:
767 add_turret(id);
768 break;
769 case OBJ_MINE_CROSSED: case OBJ_MINE_FLAT:
770 add_mine(id);
771 break;
772 case OBJ_JEEP: case OBJ_TRANSPORTER:
773 case OBJ_TANK_BIG: case OBJ_TANK_SMALL:
774 add_vehicle(id);
775 break;
776 default:
777 add_enemy(id);
779 return id;
782 static void remove_enemy(int id) {
783 enum objtype objid = objs[id].objtype;
784 switch(objid) {
785 case OBJ_JEEP: case OBJ_TRANSPORTER:
786 case OBJ_TANK_BIG: case OBJ_TANK_SMALL:
787 golist_remove(&go_vehicles, id);
788 break;
789 default:
790 golist_remove(&go_enemies, id);
792 gameobj_free(id);
795 static int enemy_fires(struct enemy *e) {
796 int i;
797 for(i = 0; i < ENEMY_MAX_SHOT; i++)
798 if(e->curr_step == e->spawn->shots[i]) return 1;
799 return 0;
802 static enum animation_id get_flash_animation_from_direction(enum direction dir) {
803 #define ANIMF(dir, anim) [dir] = anim
804 static const enum animation_id dir_to_anim[] = {
805 ANIMF(DIR_O, ANIM_FLASH_O),
806 ANIMF(DIR_NO, ANIM_FLASH_NO),
807 ANIMF(DIR_N, ANIM_FLASH_N),
808 ANIMF(DIR_NW, ANIM_FLASH_NW),
809 ANIMF(DIR_W, ANIM_FLASH_W),
810 ANIMF(DIR_SW, ANIM_FLASH_SW),
811 ANIMF(DIR_S, ANIM_FLASH_S),
812 ANIMF(DIR_SO, ANIM_FLASH_SO),
814 #undef ANIMF
815 return dir_to_anim[dir];
818 static int init_flash(vec2f *pos, enum direction dir) {
819 int id = gameobj_alloc();
820 gameobj_init(id, pos, &VEC(0, 0), SI_FLASH, get_flash_animation_from_direction(dir), OBJ_FLASH);
821 gameobj_init_bulletdata(id, 2);
822 return id;
825 static vec2f get_gameobj_pos(int obj_id) {
826 return objs[obj_id].pos;
829 static vec2f get_gameobj_center(int obj_id) {
830 vec2f res = objs[obj_id].pos;
831 vec2f add = get_sprite_center(spritemaps[objs[obj_id].spritemap_id]);
832 return vecadd(&res, &add);
835 static enum direction get_direction_from_vec(vec2f *vel);
836 static enum animation_id get_anim_from_direction(enum direction dir, int player, int throwing);
838 static enum direction16 get_direction16_from_direction(enum direction dir) {
839 static const enum direction16 dir16_transl_tab[] = {
840 [DIR_N] = DIR16_N, [DIR_NO] = DIR16_NO, [DIR_O] = DIR16_O, [DIR_SO] = DIR16_SO,
841 [DIR_S] = DIR16_S, [DIR_SW] = DIR16_SW, [DIR_W] = DIR16_W, [DIR_NW] = DIR16_NW,
843 assert(dir != DIR_INVALID);
844 return dir16_transl_tab[dir];
847 static enum weapon_id get_active_weapon_id(int player_no) {
848 return player_weapons[player_no][weapon_active[player_no]];
851 static const struct weapon* get_active_weapon(int player_no) {
852 return &weapons[get_active_weapon_id(player_no)];
854 static enum direction16 get_shotdirection_from_enemy(int curr_step, const struct enemy_spawn *spawn) {
855 const struct enemy_route* r = get_enemy_current_route(curr_step, spawn);
856 switch(r->shape) {
857 case ES_SOLDIER1_DOWN: case ES_SOLDIER2_DOWN:
858 return DIR16_S;
859 case ES_SOLDIER1_LEFT: case ES_SOLDIER2_LEFT:
860 return DIR16_W;
861 case ES_SOLDIER1_RIGHT: case ES_SOLDIER2_RIGHT:
862 return DIR16_O;
863 default:
864 assert(0);
868 static void enemy_fire_bullet(enum direction16 dir, int steps, enum enemy_weapon wpn, vec2f *pos) {
869 vec2f vel = get_vel_from_direction16(dir, 1.75);
870 int id;
871 if(wpn == EW_GUN) {
872 id = init_bullet(pos, &vel, steps);
873 if(id != -1) add_ebullet(id);
874 } else if (wpn == EW_GRENADE) {
875 id = init_grenade(pos, &vel, steps);
876 if(id != -1) add_enemy_grenade(id);
877 } else {
878 id = init_flame(pos, &vel, steps);
879 if(id != -1) add_enemy_flame(id);
883 static int get_crosshair_id(void) {
884 assert(sblist_getsize(&go_crosshair));
885 uint8_t *id = sblist_get(&go_crosshair, 0);
886 return *id;
889 static void fire_bullet(int player_no) {
890 int id;
891 const struct weapon *pw = get_active_weapon(player_no);
892 if(player_ammo[player_no][pw->ammo] == 0) return;
893 vec2f from = get_gameobj_center(player_ids[player_no]);
894 //get_anim_from_vel(0, objs[player].
895 vec2f to = get_gameobj_center(get_crosshair_id());
896 to.x += 4*SCALE - rand()%8*SCALE;
897 to.y += 4*SCALE - rand()%8*SCALE;
898 vec2f vel = velocity(&from, &to);
899 enum direction dir = get_direction_from_vec(&vel);
900 if(dir != DIR_INVALID) {
901 enum animation_id aid = get_anim_from_direction(dir, player_no, pw->ammo == AMMO_GRENADE);
902 if(aid != ANIM_INVALID) switch_anim(player_ids[player_no], aid);
903 anim_step curranim = objs[player_ids[player_no]].anim_curr;
904 if(curranim == ANIM_STEP_INIT) curranim = get_next_anim_frame(objs[player_ids[player_no]].animid, ANIM_STEP_INIT);
905 vec2f muzzle = muzzle_tab[curranim];
907 from = get_gameobj_pos(player_ids[player_no]);
908 from.x += muzzle.x * SCALE;
909 from.y += muzzle.y * SCALE;
911 if(pw->flags & WF_MUZZLEFLASH) {
912 static const vec2f flash_start[] = {
913 [DIR_O] = { 0.0, 1.0 },
914 [DIR_NO] = { 0.5, 6.0 },
915 [DIR_N] = { 1.0, 7.5 },
916 [DIR_NW] = { 6.0, 6.0 },
917 [DIR_W] = { 7.5, 1.0 },
918 [DIR_SW] = { 4.5, 0.0 },
919 [DIR_S] = { 1.0, 0.0 },
920 [DIR_SO] = { 0.0, 0.0 },
922 vec2f ffrom = from;
923 ffrom.x -= flash_start[dir].x * SCALE;
924 ffrom.y -= flash_start[dir].y * SCALE;
925 id = init_flash(&ffrom, dir);
926 if(id != -1) add_muzzleflash(id);
928 vel = velocity(&from, &to);
930 float dist = veclength(&vel);
931 float speed = pw->bullet_speed * SCALE;
932 const uint16_t range_tab[] = {0, 80, 66, 80, 118, 118, 118, 118, 118, 118,
933 200, 200, 240, 240, 240, 240, 240, 240, 240, 240, 320 };
934 float range = range_tab[pw->range] * SCALE;
935 if(dist > range)
936 dist = range;
937 float steps = dist / speed;
938 float deg = atan2(vel.y, vel.x);
939 vel.x = cos(deg) * speed;
940 vel.y = sin(deg) * speed;
941 switch(pw->shot) {
942 case ST_LAUNCHER:
943 id = init_rocket(dir, &from, &vel, steps);
944 break;
945 case ST_BULLET:
946 id = init_bullet(&from, &vel, steps);
947 if(id != -1) add_pbullet(id);
948 break;
949 case ST_FLAMES:
950 id = init_player_flame(dir, &from, &vel, steps);
951 if(id != -1) add_flame(id);
952 break;
953 case ST_GRENADE:
954 id = init_grenade(&from, &vel, steps);
955 add_grenade(id);
956 break;
957 default:
958 abort();
960 player_ammo[player_no][pw->ammo]--;
961 const WAVE_HEADER_COMPLETE *wf = wavesounds[pw->sound];
962 if(id != -1 && pw->sound != WS_NONE)
963 audio_play_wave_resource(wf);
966 static void init_game_objs() {
967 sblist_init(&go_crosshair, 1, 4);
968 sblist_init(&go_players, 1, 4);
969 sblist_init(&go_muzzleflash, 1, 4);
970 sblist_init(&go_player_bullets, 1, 32);
971 sblist_init(&go_flames, 1, 32);
972 sblist_init(&go_enemy_bullets, 1, 32);
973 sblist_init(&go_explosions, 1, 16);
974 sblist_init(&go_enemy_explosions, 1, 16);
975 sblist_init(&go_grenades, 1, 16);
976 sblist_init(&go_enemy_grenades, 1, 16);
977 sblist_init(&go_rockets, 1, 8);
978 sblist_init(&go_enemies, 1, 32);
979 sblist_init(&go_vehicles, 1, 4);
980 sblist_init(&go_mines, 1, 4);
981 sblist_init(&go_turrets, 1, 8);
982 sblist_init(&go_bunkers, 1, 4);
983 sblist_init(&go_boss, 1, 4);
984 sblist_init(&go_blood, 1, 16);
985 sblist_init(&go_enemy_flames, 1, 16);
986 init_player(0);
987 add_crosshair(init_crosshair());
988 init_map(current_map);
991 static int point_in_mask(vec2f *point, int obj_id) {
992 vec2f pos_in_pic = VEC((point->x - objs[obj_id].pos.x) / SCALE,
993 (point->y - objs[obj_id].pos.y) / SCALE);
994 const struct palpic *p = spritemaps[objs[obj_id].spritemap_id];
995 unsigned h = palpic_getspriteheight(p), w = palpic_getspritewidth(p);
996 if(pos_in_pic.x < 0 || pos_in_pic.y < 0 || pos_in_pic.x > w || pos_in_pic.y > h) return 0;
997 assert(objs[obj_id].anim_curr != ANIM_STEP_INIT);
998 anim_step curranim = objs[obj_id].anim_curr;
999 if(curranim == ANIM_STEP_INIT) curranim = get_next_anim_frame(objs[obj_id].animid, ANIM_STEP_INIT);
1000 uint8_t *data = palpic_getspritedata(p, curranim);
1001 if(data[(unsigned) pos_in_pic.y * w + (unsigned) pos_in_pic.x] != 0) return 1;
1002 return 0;
1005 static enum animation_id get_die_anim(unsigned id) {
1006 switch(objs[id].objtype) {
1007 case OBJ_P1:
1008 return ANIM_P1_DIE;
1009 case OBJ_P2:
1010 return ANIM_P2_DIE;
1011 case OBJ_JEEP:
1012 return ANIM_JEEP_DESTROYED;
1013 case OBJ_TANK_SMALL:
1014 return ANIM_TANK_SMALL_DESTROYED;
1015 case OBJ_TANK_BIG:
1016 return ANIM_TANK_BIG_DESTROYED;
1017 case OBJ_TRANSPORTER:
1018 return ANIM_TRANSPORTER_DESTROYED;
1019 case OBJ_ENEMY_BOMBER:
1020 return ANIM_ENEMY_BOMBER_DIE;
1021 case OBJ_ENEMY_SHOOTER:
1022 return ANIM_ENEMY_GUNNER_DIE;
1023 case OBJ_BUNKER1: case OBJ_BUNKER2: case OBJ_BUNKER3:
1024 case OBJ_BUNKER4: case OBJ_BUNKER5:
1025 return ANIM_BUNKER_DESTROYED;
1026 case OBJ_GUNTURRET_MOVABLE_MACHINE:
1027 return ANIM_GUNTURRET_MOVABLE_MACHINE_DESTROYED;
1028 case OBJ_GUNTURRET_MOVABLE_MAN:
1029 return ANIM_GUNTURRET_MOVABLE_MAN_DESTROYED;
1030 default:
1031 return ANIM_INVALID;
1035 /* remove bullets that have reached their maximum number of steps */
1036 static int remove_bullets(sblist *list) {
1037 int res = 0;
1038 uint8_t *item_id;
1039 ssize_t li;
1040 sblist_iter_counter2s(list, li, item_id) {
1041 struct gameobj *bullet = &objs[*item_id];
1042 if(bullet->objspecific.bullet.step_curr >= bullet->objspecific.bullet.step_max) {
1043 gameobj_free(*item_id);
1044 sblist_delete(list, li);
1045 li--;
1046 } else {
1047 bullet->objspecific.bullet.step_curr++;
1049 res = 1;
1051 return res;
1054 static int remove_explosives(sblist *list) {
1055 int res = 0;
1056 uint8_t *item_id;
1057 ssize_t li;
1058 sblist_iter_counter2s(list, li, item_id) {
1059 struct gameobj *go = &objs[*item_id];
1060 if(go->objspecific.bullet.step_curr >= go->objspecific.bullet.step_max) {
1061 if(go->objtype == OBJ_GRENADE) init_grenade_explosion(&go->pos, list == &go_enemy_grenades);
1062 else init_rocket_explosion(&go->pos);
1063 gameobj_free(*item_id);
1064 sblist_delete(list, li);
1065 li--;
1066 } else {
1067 go->objspecific.bullet.step_curr++;
1068 if(go->objtype == OBJ_GRENADE) {
1069 if(go->objspecific.bullet.step_curr >= 32) go->animid = ANIM_GRENADE_SMALL;
1070 else if(go->objspecific.bullet.step_curr >= 8) go->animid = ANIM_GRENADE_BIG;
1073 res = 1;
1075 return res;
1078 static int remove_offscreen_objects(sblist *list) {
1079 int res = 0;
1080 uint8_t *item_id;
1081 ssize_t li;
1082 sblist_iter_counter2s(list, li, item_id) {
1083 assert(obj_slot_used[*item_id]);
1084 struct gameobj *go = &objs[*item_id];
1085 assert((int) go->spritemap_id < SI_MAX);
1086 const struct palpic *p = spritemaps[go->spritemap_id];
1087 assert(p->spritecount);
1088 int h = palpic_getspriteheight(p), w = palpic_getspritewidth(p);
1089 if(go->pos.x < SCREEN_MIN_X-w*SCALE || go->pos.x > SCREEN_MAX_X ||
1090 go->pos.y < SCREEN_MIN_Y-h*SCALE || go->pos.y > SCREEN_MAX_Y) {
1091 res = 1;
1092 dprintf(2, "offscreen: removed gameobj %d\n", (int) *item_id);
1093 gameobj_free(*item_id);
1094 sblist_delete(list, li);
1095 li--;
1098 return res;
1101 static int get_sprite_number(int id) {
1102 struct gameobj *o = &objs[id];
1103 return o->anim_curr == ANIM_STEP_INIT ? get_next_anim_frame(o->animid, o->anim_curr) : o->anim_curr;
1106 static int is_death_anim(enum animation_id anim) {
1107 switch(anim) {
1108 case ANIM_ENEMY_BOMBER_DIE:
1109 case ANIM_ENEMY_GUNNER_DIE:
1110 case ANIM_ENEMY_BURNT:
1111 case ANIM_P1_DIE:
1112 case ANIM_P2_DIE:
1113 case ANIM_GUNTURRET_MOVABLE_MAN_DESTROYED:
1114 case ANIM_GUNTURRET_MOVABLE_MACHINE_DESTROYED:
1115 case ANIM_BUNKER_DESTROYED:
1116 case ANIM_JEEP_DESTROYED:
1117 case ANIM_TANK_BIG_DESTROYED:
1118 case ANIM_TANK_SMALL_DESTROYED:
1119 case ANIM_TRANSPORTER_DESTROYED:
1120 return 1;
1121 default:
1122 return 0;
1126 static int overlap(int id1, int id2) {
1127 struct gameobj *o1 = &objs[id1];
1128 struct gameobj *o2 = &objs[id2];
1129 const struct palpic* p1 = spritemaps[o1->spritemap_id];
1130 const struct palpic* p2 = spritemaps[o2->spritemap_id];
1132 vec2f vec = velocity(&o1->pos, &o2->pos);
1133 float mh = o1->pos.y < o2->pos.y ? palpic_getspriteheight(p1) : palpic_getspriteheight(p2);
1134 float mw = o1->pos.x < o2->pos.x ? palpic_getspritewidth(p1) : palpic_getspritewidth(p2);
1135 mh*=SCALE, mw*=SCALE;
1136 return fabs(vec.x) < mw && fabs(vec.y) < mh;
1139 static int masks_collide(int id1, int id2) {
1140 struct gameobj *a = &objs[id1];
1141 struct gameobj *b = &objs[id2];
1142 const struct palpic* pa = spritemaps[a->spritemap_id];
1143 const struct palpic* pb = spritemaps[b->spritemap_id];
1144 int ax = a->pos.x/SCALE, ay = a->pos.y/SCALE,
1145 aw = palpic_getspritewidth(pa), ah = palpic_getspriteheight(pa);
1146 int bx = b->pos.x/SCALE, by = b->pos.y/SCALE,
1147 bw = palpic_getspritewidth(pb), bh = palpic_getspriteheight(pb);
1149 /* x: normalized start position */
1150 int xx = MIN(ax, bx), xy = MIN(ay, by);
1151 /* normalize coordinates to 0 */
1152 ax -= xx, ay -= xy, bx -= xx, by -= xy;
1153 /* o: upper left point of the overlapping region */
1154 int ox = MAX(ax, bx), oy = MAX(ay, by);
1155 /* e: lower right point of the overlapping region */
1156 int ex = MIN(ax+aw, bx+bw), ey = MIN(ay+ah, by+bh);
1157 /* vector o -> e */
1158 int oex = ex - ox, oey = ey - oy;
1159 /* (s)tart and (e)nd coordinates of the overlapping regions in both rects */
1160 int asx = ox - ax, asy = oy - ay;
1161 int bsx = ox - bx, bsy = oy - by;
1162 int aex = asx + oex, bex = bsx + oex;
1163 int aey = asy + oey, bey = bsy + oey;
1164 assert(asx >= 0); assert(asy >= 0);
1165 assert(bsx >= 0); assert(bsy >= 0);
1166 if(!aex || !bex || !aey || !bey) return 0;
1167 assert(aex > 0); assert(bex > 0);
1168 assert(aey > 0); assert(bey > 0);
1169 int xa, ya, xb, yb;
1170 const uint8_t *da = palpic_getspritedata(pa, get_sprite_number(id1)),
1171 *db = palpic_getspritedata(pb, get_sprite_number(id2));
1172 assert(da); assert(db);
1173 for(ya = asy, yb = bsy; ya < aey && yb < bey; ya++, yb++)
1174 for(xa = asx, xb = bsx; xa < aex && xb < bex ; xa++, xb++)
1175 if(da[ya*aw+xa] && db[yb*bw+xb]) return 1;
1176 return 0;
1180 // removes bullets and other objects if they collide. return 1 if anything happened
1181 static int hit_bullets(sblist *bullet_list, sblist *target_list) {
1182 uint8_t *bullet_id;
1183 ssize_t li;
1184 int res = 0;
1185 enum bulletsubtype {
1186 BS_BULLET = 0,
1187 BS_FLAME = 1,
1188 BS_GRENADE_EXPL = 2,
1189 BS_BIG_EXPL = 3,
1190 BS_TOUCH,
1191 } bullet_subtybe = BS_BULLET;
1193 sblist_iter_counter2s(bullet_list, li, bullet_id) {
1194 struct gameobj *bullet = &objs[*bullet_id];
1195 if(bullet->objtype == OBJ_FLAME) bullet_subtybe = BS_FLAME;
1196 else if(bullet->objtype == OBJ_GRENADE_EXPLOSION) {
1197 /* grenade kills only in the explosion, not in the smoke phase */
1198 if(bullet->objspecific.bullet.step_curr > 22) continue;
1199 bullet_subtybe = BS_GRENADE_EXPL;
1200 } else if(bullet->objtype == OBJ_BIG_EXPLOSION) {
1201 bullet_subtybe = BS_BIG_EXPL;
1202 } else if(bullet_list == &go_vehicles || bullet_list == &go_enemies ||
1203 bullet_list == &go_boss || bullet_list == &go_mines) {
1204 bullet_subtybe = BS_TOUCH;
1207 vec2f bullet_center = get_gameobj_center(*bullet_id);
1208 if(bullet_list == &go_player_bullets || bullet_list == &go_flames) {
1209 if(is_wall(&bullet_center) == WT_SOLID) goto remove_bullet;
1212 const float bullet_radius[] = { [BS_BULLET] = 1.f, [BS_FLAME] = 6.f,
1213 [BS_GRENADE_EXPL] = 16.f, [BS_BIG_EXPL] = 19.f, [BS_TOUCH] = 8.f, };
1215 size_t lj;
1216 uint8_t *target_id;
1217 sblist_iter_counter2(target_list, lj, target_id) {
1218 struct gameobj *target = &objs[*target_id];
1219 if(is_death_anim(target->animid) && target_list != &go_vehicles) continue;
1220 if(bullet_subtybe == BS_TOUCH &&
1221 overlap(*target_id, *bullet_id) &&
1222 masks_collide(*target_id, *bullet_id)) {
1223 dprintf(2, "hit2\n");
1224 if(bullet_list == &go_boss) return 2;
1225 goto hit;
1228 vec2f temp = get_gameobj_center(*target_id);
1229 float dist1 = vecdist(&bullet_center, &temp) - bullet_radius[bullet_subtybe] * SCALE;
1230 vec2f newpos = vecadd(&bullet_center, &bullet->vel);
1231 float dist2 = vecdist(&newpos, &temp) - bullet_radius[bullet_subtybe] * SCALE;
1233 unsigned w = palpic_getspritewidth(spritemaps[target->spritemap_id]),
1234 h = palpic_getspriteheight(spritemaps[target->spritemap_id]);
1235 float longest_side_div2 = ((float) MAX(h, w) / 2) * SCALE;
1236 if(dist1 < 1.f*SCALE || dist2 < 1.f*SCALE) { dprintf(2, "hit1\n"); goto hit; }
1237 if(dist1 < longest_side_div2 || dist2 < longest_side_div2) {
1238 vec2f velquarter = VEC(bullet->vel.x * 0.25, bullet->vel.y * 0.25);
1239 vec2f point = bullet_center;
1240 size_t k;
1241 for(k = 0; k < 4; k++) {
1242 if(point_in_mask(&point, *target_id)) {
1243 hit:
1245 if(bullet_list == &go_player_bullets) {
1246 if(target_list == &go_vehicles) {
1247 audio_play_wave_resource(wavesounds[WS_DROPSHOT]);
1248 goto remove_bullet;
1250 player_score[0] += 50;
1251 player_kills[0]++;
1252 } else if (bullet_list == &go_rockets) {
1253 init_rocket_explosion(&target->pos);
1254 goto remove_bullet;
1255 } else if(bullet->objtype == OBJ_GRENADE_EXPLOSION) {
1256 // grenade explosion has no effect on vehicles and bunkers.
1257 if(target_list == &go_vehicles || target_list == &go_bunkers)
1258 goto next_bullet;
1259 } else if(bullet->objtype == OBJ_MINE_CROSSED || bullet->objtype == OBJ_MINE_FLAT) {
1260 init_big_explosion(&target->pos);
1261 goto remove_bullet;
1263 enum animation_id death_anim = (bullet_subtybe == BS_FLAME && target_list == &go_enemies) ?
1264 ANIM_ENEMY_BURNT : get_die_anim(*target_id);
1265 if(death_anim == ANIM_INVALID) {
1266 gameobj_free(*target_id);
1267 sblist_delete(target_list, lj);
1268 lj--;
1269 goto next_target;
1271 switch_anim(*target_id, death_anim);
1272 target->vel = VEC(0,0);
1273 if(target->objtype == OBJ_ENEMY_BOMBER || target->objtype == OBJ_ENEMY_SHOOTER) {
1274 const enum wavesound_id wid[] = { WS_SCREAM, WS_SCREAM2 };
1275 audio_play_wave_resource(wavesounds[wid[rand()%2]]);
1276 if(bullet_subtybe == BS_BULLET) {
1277 vec2f bloodpos = vecadd(&target->pos, &VEC(0, (2+rand()%7)*SCALE));
1278 /* original displays at 0,8 but i prefer a random effect */
1279 //vec2f bloodpos = vecadd(&target->pos, &VEC(0, 8*SCALE));
1280 int id = init_blood(&bloodpos);
1281 if(id != -1) add_blood(id);
1284 if(bullet_subtybe == BS_BULLET) {
1285 remove_bullet:
1286 gameobj_free(*bullet_id);
1287 sblist_delete(bullet_list, li);
1288 li--;
1289 goto next_bullet;
1290 } else break;
1292 point = vecadd(&point, &velquarter);
1295 next_target:;
1297 next_bullet:
1298 res = 1;
1300 return res;
1303 uint32_t tickcounter;
1305 static int advance_animations(void) {
1306 size_t i, obj_visited;
1307 int res = 0;
1308 for(i = 0, obj_visited = 0; obj_visited < obj_count && i < OBJ_MAX; i++) {
1309 if(!obj_slot_used[i]) continue;
1310 struct gameobj *go = &objs[i];
1311 if(go->anim_curr == ANIM_STEP_INIT || (go->vel.x != 0 || go->vel.y != 0) || (go->objtype != OBJ_P1 && go->objtype != OBJ_P2) || is_death_anim(go->animid)) {
1312 unsigned anim_delay = go->objtype == OBJ_BIG_EXPLOSION ? 8 : 4;
1313 if(go->anim_curr == ANIM_STEP_INIT || tickcounter % anim_delay == go->anim_frame) {
1314 anim_step last_anim = go->anim_curr;
1315 go->anim_curr = get_next_anim_frame(go->animid, go->anim_curr);
1316 if(last_anim != go->anim_curr) res = 1;
1319 obj_visited++;
1321 return res;
1324 static void switch_enemy_shape(int objid, const struct enemy_route* r) {
1325 switch_anim(objid, enemy_animation_lut[r->shape]);
1328 static void draw_golist(sblist *list) {
1329 size_t i;
1330 uint8_t *itemid;
1331 sblist_iter_counter2(list, i, itemid) {
1332 assert(obj_slot_used[*itemid]);
1333 struct gameobj *o = &objs[*itemid];
1334 const prgb *palette;
1335 switch(o->objtype) {
1336 case OBJ_BLOOD:
1337 // original blood color is bb5511 but that is hardly visible
1338 // palette = (prgb[]) {PRGB(0,0,0), PRGB(0xbb, 0x55, 0x11)};
1339 palette = (const prgb[]) {PRGB(0,0,0), PRGB(0xff, 0x0, 0x0)};
1340 break;
1341 case OBJ_ENEMY_BOMBER: case OBJ_ENEMY_SHOOTER:
1342 palette = map->enemy_palette;
1343 break;
1344 default:
1345 palette = 0;
1347 blit_sprite(o->pos.x, o->pos.y, &video,
1348 SCALE, spritemaps[o->spritemap_id],
1349 get_sprite_number(*itemid),
1350 palette);
1354 static void draw_gameobjs(void) {
1355 draw_golist(&go_mines);
1356 draw_golist(&go_turrets);
1357 draw_golist(&go_bunkers);
1358 draw_golist(&go_vehicles);
1359 draw_golist(&go_enemies);
1360 draw_golist(&go_blood);
1361 draw_golist(&go_enemy_bullets);
1362 draw_golist(&go_boss);
1363 draw_golist(&go_rockets);
1364 draw_golist(&go_players);
1365 draw_golist(&go_player_bullets);
1366 draw_golist(&go_enemy_explosions);
1367 draw_golist(&go_flames);
1368 draw_golist(&go_enemy_flames);
1369 draw_golist(&go_explosions);
1370 draw_golist(&go_grenades);
1371 draw_golist(&go_enemy_grenades);
1372 draw_golist(&go_crosshair);
1373 draw_golist(&go_muzzleflash);
1376 static void process_soldiers(void) {
1377 uint8_t *itemid;
1378 sblist_iter(&go_enemies, itemid) {
1379 struct gameobj *go = &objs[*itemid];
1380 assert(go->objtype == OBJ_ENEMY_SHOOTER || go->objtype == OBJ_ENEMY_BOMBER);
1381 if (tickcounter % 4 == go->anim_frame) {
1382 const struct enemy_route *rc = get_enemy_current_route(go->objspecific.enemy.curr_step, go->objspecific.enemy.spawn);
1383 if(rc->vel) {
1384 go->objspecific.enemy.curr_step++;
1385 if(enemy_fires(&go->objspecific.enemy)) {
1386 vec2f from = get_gameobj_center(*itemid);
1387 enum direction16 dir = get_shotdirection_from_enemy(go->objspecific.enemy.curr_step, go->objspecific.enemy.spawn);
1388 enemy_fire_bullet(dir, 41, go->objspecific.enemy.spawn->weapon, &from);
1390 const struct enemy_route *rn = get_enemy_current_route(go->objspecific.enemy.curr_step, go->objspecific.enemy.spawn);
1391 if(rn->shape != rc->shape) switch_enemy_shape(*itemid, rn);
1394 if(!is_death_anim(go->animid)) go->vel = get_enemy_vel(go->objspecific.enemy.curr_step, go->objspecific.enemy.spawn);
1395 else go->vel = VEC(0, 0);
1399 static enum animation_id get_turret_anim_from_dir(int objid, enum direction dir) {
1400 static const enum animation_id man_anim[] = {
1401 [DIR_N] = ANIM_GUNTURRET_MOVABLE_MAN_N,
1402 [DIR_NO] = ANIM_GUNTURRET_MOVABLE_MAN_NO,
1403 [DIR_O] = ANIM_GUNTURRET_MOVABLE_MAN_O,
1404 [DIR_SO] = ANIM_GUNTURRET_MOVABLE_MAN_SO,
1405 [DIR_S] = ANIM_GUNTURRET_MOVABLE_MAN_S,
1406 [DIR_SW] = ANIM_GUNTURRET_MOVABLE_MAN_SW,
1407 [DIR_W] = ANIM_GUNTURRET_MOVABLE_MAN_W,
1408 [DIR_NW] = ANIM_GUNTURRET_MOVABLE_MAN_NW,
1410 static const enum animation_id mach_anim[] = {
1411 [DIR_N] = ANIM_GUNTURRET_MOVABLE_MACHINE_N,
1412 [DIR_NO] = ANIM_GUNTURRET_MOVABLE_MACHINE_NO,
1413 [DIR_O] = ANIM_GUNTURRET_MOVABLE_MACHINE_O,
1414 [DIR_SO] = ANIM_GUNTURRET_MOVABLE_MACHINE_SO,
1415 [DIR_S] = ANIM_GUNTURRET_MOVABLE_MACHINE_S,
1416 [DIR_SW] = ANIM_GUNTURRET_MOVABLE_MACHINE_SW,
1417 [DIR_W] = ANIM_GUNTURRET_MOVABLE_MACHINE_W,
1418 [DIR_NW] = ANIM_GUNTURRET_MOVABLE_MACHINE_NW,
1420 const enum animation_id *lut = objs[objid].objtype == OBJ_GUNTURRET_MOVABLE_MAN ? man_anim : mach_anim;
1421 return lut[dir];
1424 static int editor_mode;
1425 static int process_turrets(sblist* list) {
1426 if(editor_mode) return 0;
1427 int res = 0;
1428 uint8_t *itemid;
1429 sblist_iter(list, itemid) {
1430 struct gameobj *go = &objs[*itemid];
1431 if(is_death_anim(go->animid)) continue;
1432 enum direction16 dir = DIR16_S;
1433 enum enemy_weapon ew = EW_GUN;
1434 int steps = 92;
1435 vec2f from;
1436 switch(go->objtype) {
1437 case OBJ_GUNTURRET_FIXED_NORTH:
1438 dir = DIR16_N;
1439 case OBJ_GUNTURRET_FIXED_SOUTH:
1440 from = get_gameobj_center(*itemid);
1441 shottest:
1442 if(rand()%8 == 0) {
1443 shot:
1444 enemy_fire_bullet(dir, steps, ew, &from);
1445 res = 1;
1447 break;
1448 case OBJ_FLAMETURRET:
1449 ew = EW_FLAME;
1450 steps = 48;
1452 if(objs[player_ids[0]].pos.y <= (192-25)*SCALE/2) dir = DIR16_N;
1453 from = go->pos;
1454 from.y += (dir == DIR16_N ? -8*SCALE : 8*SCALE);
1456 /* abusing curr_step variable to save position in a burst of flames */
1457 if(go->objspecific.enemy.curr_step && go->objspecific.enemy.curr_step < 16)
1458 goto do_flame;
1459 else if(go->objspecific.enemy.curr_step)
1460 go->objspecific.enemy.curr_step = 0;
1462 if(objs[player_ids[0]].pos.x < go->pos.x-32*SCALE || objs[player_ids[0]].pos.x > go->pos.x + (16+32)*SCALE)
1463 break;
1464 if(rand()%8 == 0) {
1465 do_flame:
1466 go->objspecific.enemy.curr_step++;
1467 goto shot;
1469 break;
1470 case OBJ_GUNTURRET_MOVABLE_MAN:
1471 case OBJ_GUNTURRET_MOVABLE_MACHINE: {
1472 from = get_gameobj_center(*itemid);
1473 vec2f p_center = get_gameobj_center(player_ids[0]);
1474 vec2f vel = velocity(&from, &p_center);
1475 enum direction dir8 = get_direction_from_vec(&vel);
1476 dir = get_direction16_from_direction(dir8);
1477 switch_anim(*itemid, get_turret_anim_from_dir(*itemid, dir8));
1478 goto shottest;
1479 break;
1481 case OBJ_TANK_SMALL:
1482 case OBJ_BUNKER1:
1483 case OBJ_BUNKER2:
1484 case OBJ_BUNKER3:
1485 case OBJ_BUNKER4:
1486 case OBJ_BUNKER5: {
1487 const vec2f *bunkerpos;
1488 const enum direction16 *bunkerdir;
1489 unsigned count, sec;
1490 static const enum direction16 bunker5_dir[] = {
1491 [0] = DIR16_N, [1] = DIR16_NO, [2] = DIR16_O, [3] = DIR16_SO,
1492 [4] = DIR16_S, [5] = DIR16_SW, [6] = DIR16_W, [7] = DIR16_NW,
1494 static const enum direction16 bunker1_dir[] = {
1495 [0] = DIR16_NNW, [1] = DIR16_NNO, [2] = DIR16_NO, [3] = DIR16_O,
1496 [4] = DIR16_SO, [5] = DIR16_SSO, [6] = DIR16_SSW, [7] = DIR16_SW,
1497 [8] = DIR16_W, [9] = DIR16_NW,
1499 static const vec2f bunker5_pos[] = {
1500 [0] = VEC(9,-8), [1] = VEC(18,-6), [2] = VEC(26,2), [3] = VEC(22,10),
1501 [4] = VEC(9,16), [5] = VEC(-5,13), [6] = VEC(-7,2), [7] = VEC(-5,-6),
1503 static const vec2f bunker2_pos[] = {
1504 [0] = VEC(15,-2), [1] = VEC(27,3), [2] = VEC(31,12), [3] = VEC(28,23),
1505 [4] = VEC(15,27), [5] = VEC(2,23), [6] = VEC(0,12), [7] = VEC(4,2),
1507 static const vec2f bunker4_pos[] = {
1508 [0] = VEC(13,0), [1] = VEC(25,1), [2] = VEC(29,10), [3] = VEC(26,21),
1509 [4] = VEC(13,25), [5] = VEC(0,21), [6] = VEC(-2,10), [7] = VEC(2,0),
1511 static const vec2f bunker1_pos[] = {
1512 [0] = VEC(6,-6), [1] = VEC(18,-6), [2] = VEC(26,2), [3] = VEC(26,12),
1513 [4] = VEC(26,16), [5] = VEC(20,24), [6] = VEC(6,24), [7] = VEC(0,20),
1514 [8] = VEC(0,12), [9] = VEC(0,2),
1516 static const enum direction16 tank_dir[] = {
1517 [0] = DIR16_WSW, [1] = DIR16_OSO, [2] = DIR16_WSW, [3] = DIR16_OSO,
1518 [4] = DIR16_WSW, [5] = DIR16_OSO, [6] = DIR16_SSW, [7] = DIR16_SSO,
1520 static const vec2f tank_pos[] = {
1521 [0] = VEC(10,8), [1] = VEC(38,8), [2] = VEC(10,20), [3] = VEC(38,20),
1522 [4] = VEC(10,32), [5] = VEC(38,32), [6] = VEC(16,40), [7] = VEC(32,40),
1524 unsigned b, dist = 28;
1525 switch(go->objtype) {
1526 case OBJ_BUNKER1:
1527 ew = EW_GRENADE;
1528 bunkerpos = bunker1_pos;
1529 bunkerdir = bunker1_dir;
1530 b = 0;
1531 count = 10;
1532 sec = 2;
1533 break;
1534 case OBJ_TANK_SMALL:
1535 if(tickcounter % 8) break;
1536 ew = EW_GUN;
1537 dist = 128;
1538 bunkerpos = tank_pos;
1539 bunkerdir = tank_dir;
1540 // FIXME: the last 2 shoots (6+7) should be fired at the same time
1541 goto bunker_circle_shoot;
1542 case OBJ_BUNKER2:
1543 if(tickcounter % 8) break;
1544 ew = EW_GUN;
1545 dist = 128;
1546 bunkerpos = bunker2_pos;
1547 bunkerdir = bunker5_dir;
1548 goto bunker_circle_shoot;
1549 case OBJ_BUNKER4:
1550 if(tickcounter % /*(fps*3.5)/8*/ 24) break;
1551 ew = EW_GRENADE;
1552 dist = 32;
1553 bunkerpos = bunker4_pos;
1554 bunkerdir = bunker5_dir;
1555 bunker_circle_shoot:;
1556 b = go->objspecific.enemy.curr_step;
1557 count = b+1;
1558 if(++go->objspecific.enemy.curr_step >= 8) go->objspecific.enemy.curr_step = 0;
1559 goto bunkerloop;
1560 case OBJ_BUNKER3:
1561 ew = EW_GUN;
1562 bunkerpos = bunker2_pos;
1563 bunkerdir = bunker5_dir;
1564 b = 0;
1565 count = 8;
1566 dist = 128;
1567 sec = 1;
1568 break;
1569 case OBJ_BUNKER5:
1570 ew = EW_FLAME;
1571 bunkerpos = bunker5_pos;
1572 bunkerdir = bunker5_dir;
1573 b = 0;
1574 count = 8;
1575 sec = 3;
1576 break;
1577 default:;
1579 if(tickcounter > (uint32_t) go->objspecific.enemy.curr_step + fps*sec) {
1580 go->objspecific.enemy.curr_step = tickcounter;
1581 bunkerloop:;
1582 for(; b < count; b++) {
1583 from = go->pos;
1584 from.x += bunkerpos[b].x*SCALE;
1585 from.y += bunkerpos[b].y*SCALE;
1586 enemy_fire_bullet(bunkerdir[b], dist, ew, &from);
1588 res = 1;
1590 break;
1592 default:;
1595 return res;
1598 static int move_gameobjs(void) {
1599 int res = 0;
1600 size_t i, obj_visited;
1601 for(i = 0, obj_visited = 0; obj_visited < obj_count && i < OBJ_MAX; i++) {
1602 if(obj_slot_used[i]) {
1603 struct gameobj *go = &objs[i];
1604 obj_visited++;
1605 if(go->anim_curr == ANIM_STEP_INIT) res = 1;
1606 if(go->vel.x != 0 || go->vel.y != 0) {
1607 vec2f oldpos = go->pos;
1608 go->pos.x += go->vel.x;
1609 go->pos.y += go->vel.y;
1611 if(go->objtype == OBJ_P1 || go->objtype == OBJ_P2) {
1612 if(go->pos.y < SCREEN_MIN_Y) go->pos.y = SCREEN_MIN_Y;
1613 else if(go->pos.y+25*SCALE > SCREEN_MAX_Y) go->pos.y = SCREEN_MAX_Y-25*SCALE;
1614 if(go->pos.x < SCREEN_MIN_X) go->pos.x = SCREEN_MIN_X;
1615 else if(go->pos.x+32*SCALE > SCREEN_MAX_X) go->pos.x = SCREEN_MAX_X-32*SCALE;
1616 vec2f center = get_sprite_center(spritemaps[go->spritemap_id]);
1617 center = vecadd(&center, &go->pos);
1618 if(is_wall(&center)) {
1619 go->pos = oldpos;
1620 go->vel = VEC(0,0);
1624 res = 1;
1626 if((go->objtype == OBJ_ENEMY_BOMBER || go->objtype == OBJ_ENEMY_SHOOTER) &&
1627 go->anim_curr == animations[go->animid].last &&
1628 (go->animid == ANIM_ENEMY_BOMBER_DIE ||
1629 go->animid == ANIM_ENEMY_GUNNER_DIE ||
1630 go->animid == ANIM_ENEMY_BURNT)) {
1631 dprintf(2, "removed enemy from %.2f,%.2f\n", go->pos.x, go->pos.y);
1632 gameobj_free(i);
1633 golist_remove(&go_enemies, i);
1634 res = 1;
1635 continue;
1640 return res;
1643 static void game_update_caption(void) {
1644 char buf [128];
1645 snprintf(buf, 128, "objs: %d, map x,y %d/%d, index %d, xoff %d, yoff %d, spawnscreen %d, line %d", (int) obj_count,
1646 (int)mapsquare.x, (int)mapsquare.y, (int)map->screen_map[mapsquare.y][mapsquare.x],
1647 (int)mapscreen_xoff, (int)mapscreen_yoff, (int)map_spawn_screen_index, (int) map_spawn_line);
1648 SDL_WM_SetCaption(buf, 0);
1650 static void(*update_caption)(void) = game_update_caption;
1652 /* returns 1 if level finished */
1653 static int game_tick(int force_redraw) {
1654 int need_redraw = force_redraw;
1655 if(mousebutton_down[MB_LEFT] > 1) {
1656 const int player_no = 0;
1657 const struct weapon *pw = get_active_weapon(player_no);
1658 //if(get_active_weapon_id(player_no) == WP_M134) __asm__("int3");
1659 if (pw->flags & WF_AUTOMATIC) {
1660 float shots_per_second = pw->rpm / 60.f;
1661 float shotinterval = fps / shots_per_second;
1662 if((int)((float)mousebutton_down[MB_LEFT] / shotinterval) != (int)((float)(mousebutton_down[MB_LEFT]-1) / shotinterval))
1663 fire_bullet(player_no);
1667 if(advance_animations()) need_redraw = 1;
1668 if(hit_bullets(&go_player_bullets, &go_enemies)) need_redraw = 1;
1669 if(hit_bullets(&go_player_bullets, &go_vehicles)) need_redraw = 1;
1670 if(hit_bullets(&go_flames, &go_enemies)) need_redraw = 1;
1671 if(hit_bullets(&go_enemy_flames, &go_enemies)) need_redraw = 1;
1672 if(hit_bullets(&go_rockets, &go_enemies)) need_redraw = 1;
1673 if(hit_bullets(&go_rockets, &go_vehicles)) need_redraw = 1;
1674 if(hit_bullets(&go_explosions, &go_enemies)) need_redraw = 1;
1675 if(hit_bullets(&go_explosions, &go_vehicles)) need_redraw = 1;
1676 if(hit_bullets(&go_explosions, &go_mines)) need_redraw = 1;
1677 if(hit_bullets(&go_explosions, &go_turrets)) need_redraw = 1;
1678 if(hit_bullets(&go_explosions, &go_bunkers)) need_redraw = 1;
1679 if(hit_bullets(&go_explosions, &go_players)) need_redraw = 1;
1680 if(hit_bullets(&go_enemy_explosions, &go_players)) need_redraw = 1;
1681 if(hit_bullets(&go_enemy_bullets, &go_players)) need_redraw = 1;
1682 if(hit_bullets(&go_enemy_flames, &go_players)) need_redraw = 1;
1683 if(hit_bullets(&go_vehicles, &go_players)) need_redraw = 1;
1684 if(hit_bullets(&go_enemies, &go_players)) need_redraw = 1;
1685 if(hit_bullets(&go_mines, &go_players)) need_redraw = 1;
1687 int ret, level_finished = 0;
1688 if((ret = hit_bullets(&go_boss, &go_players)) == 2) level_finished = 1;
1689 else if(ret == 1) need_redraw = 1;
1691 if(remove_bullets(&go_player_bullets)) need_redraw = 1;
1692 if(remove_bullets(&go_flames)) need_redraw = 1;
1693 if(remove_bullets(&go_explosions)) need_redraw = 1;
1694 if(remove_bullets(&go_enemy_explosions)) need_redraw = 1;
1695 if(remove_bullets(&go_enemy_bullets)) need_redraw = 1;
1696 if(remove_bullets(&go_enemy_flames)) need_redraw = 1;
1697 if(remove_bullets(&go_muzzleflash)) need_redraw = 1;
1698 if(remove_bullets(&go_blood)) need_redraw = 1;
1699 if(remove_explosives(&go_grenades)) need_redraw = 1;
1700 if(remove_explosives(&go_enemy_grenades)) need_redraw = 1;
1701 if(remove_explosives(&go_rockets)) need_redraw = 1;
1702 if(tickcounter % 2 == 0 && scroll_map()) need_redraw = 1;
1704 process_soldiers();
1705 if(tickcounter % 4 == 0 && process_turrets(&go_turrets)) need_redraw = 1;
1706 if(tickcounter % 4 == 0 && process_turrets(&go_bunkers)) need_redraw = 1;
1707 if(tickcounter % 4 == 0 && process_turrets(&go_vehicles)) need_redraw = 1;
1709 if(move_gameobjs()) need_redraw = 1;
1711 if(remove_offscreen_objects(&go_enemies)) need_redraw = 1;
1712 if(remove_offscreen_objects(&go_vehicles)) need_redraw = 1;
1713 if(remove_offscreen_objects(&go_mines)) need_redraw = 1;
1714 if(remove_offscreen_objects(&go_turrets)) need_redraw = 1;
1715 if(remove_offscreen_objects(&go_bunkers)) need_redraw = 1;
1717 long ms_used = 0;
1718 struct timeval timer;
1719 gettimestamp(&timer);
1720 if(need_redraw) {
1721 draw_map();
1722 draw_gameobjs();
1723 draw_status_bar();
1724 video_update_region(SCREEN_MIN_X ,SCREEN_MIN_Y , SCREEN_MAX_X - SCREEN_MIN_X, VMODE_H);
1727 ms_used = mspassed(&timer);
1728 //if(ms_used) printf("repaint took: ms_used %ld\n", ms_used);
1729 int res = audio_process();
1730 if(res == -1) music_restart();
1731 ms_used = mspassed(&timer);
1732 //if(ms_used) printf("audio processed: %d, ms_used %ld\n", res, ms_used);
1734 long sleepms = 1000/fps - ms_used;
1735 if(sleepms >= 0) SDL_Delay(sleepms);
1736 if(mousebutton_down[MB_LEFT]) mousebutton_down[MB_LEFT]++;
1738 tickcounter++;
1739 update_caption();
1741 return level_finished;
1744 enum cursor {
1745 c_down = 0,
1746 c_up,
1747 c_left,
1748 c_right,
1751 static enum cursor cursor_lut[] = {
1752 [SDLK_UP] = c_up,
1753 [SDLK_DOWN] = c_down,
1754 [SDLK_LEFT] = c_left,
1755 [SDLK_RIGHT] = c_right,
1756 [SDLK_w] = c_up,
1757 [SDLK_a] = c_left,
1758 [SDLK_s] = c_down,
1759 [SDLK_d] = c_right,
1762 static char cursors_pressed[] = {
1763 [c_up] = 0,
1764 [c_down] = 0,
1765 [c_left] = 0,
1766 [c_right] = 0,
1769 static vec2f get_vel_from_direction(enum direction dir, float speed) {
1770 #define VELLUT(a, b, c) [a] = VEC(b, c)
1771 static const vec2f vel_lut[] = {
1772 VELLUT(DIR_O, 1, 0),
1773 VELLUT(DIR_NO, 0.7071067769704655, -0.7071067769704655),
1774 VELLUT(DIR_N, 0, -1),
1775 VELLUT(DIR_NW, -0.7071067769704655, -0.7071067769704655),
1776 VELLUT(DIR_W, -1, 0),
1777 VELLUT(DIR_SW, -0.7071067769704655, 0.7071067769704655),
1778 VELLUT(DIR_S, 0, 1),
1779 VELLUT(DIR_SO, 0.7071067769704655, 0.7071067769704655),
1781 #undef VELLUT
1782 vec2f v = vel_lut[dir];
1783 v.x *= speed * SCALE;
1784 v.y *= speed * SCALE;
1785 return v;
1788 static vec2f get_vel_from_direction16(enum direction16 dir, float speed) {
1789 #define VELLUT(a, b, c) [a] = VEC(b, c)
1790 #define ANK90 0.7071067769704655
1791 #define GK90 ANK90
1792 #define ANK45 0.9238795042037964
1793 #define GK45 0.3826834261417389
1794 static const vec2f vel_lut[] = {
1795 VELLUT(DIR16_O, 1, 0),
1796 VELLUT(DIR16_ONO, ANK45, -GK45),
1797 VELLUT(DIR16_NO, ANK90, -ANK90),
1798 VELLUT(DIR16_NNO, GK45, -ANK45),
1799 VELLUT(DIR16_N, 0, -1),
1800 VELLUT(DIR16_NNW, -GK45, -ANK45),
1801 VELLUT(DIR16_NW, -ANK90, -ANK90),
1802 VELLUT(DIR16_WNW, -ANK45, -GK45),
1803 VELLUT(DIR16_W, -1, 0),
1804 VELLUT(DIR16_WSW, -ANK45, GK45),
1805 VELLUT(DIR16_SW, -ANK90, ANK90),
1806 VELLUT(DIR16_SSW, -GK45, ANK45),
1807 VELLUT(DIR16_S, 0, 1),
1808 VELLUT(DIR16_SSO, GK45, ANK45),
1809 VELLUT(DIR16_SO, ANK90, ANK90),
1810 VELLUT(DIR16_OSO, ANK45, GK45),
1812 #undef VELLUT
1813 vec2f v = vel_lut[dir];
1814 v.x *= speed * SCALE;
1815 v.y *= speed * SCALE;
1816 return v;
1820 static enum direction get_direction_from_vec(vec2f *vel) {
1821 float deg_org, deg = atan2(vel->y, vel->x);
1822 deg_org = deg;
1823 if(deg < 0) deg *= -1.f;
1824 else if(deg > 0) deg = M_PI + (M_PI - deg); // normalize atan2 result to scale from 0 to 2 pi
1825 int hexadrant = (int)(deg / ((1.0/16.0f)*2*M_PI));
1826 assert(hexadrant >= 0 && hexadrant < 16);
1827 static const enum direction rad_lut[] = {
1828 DIR_O,
1829 DIR_NO, DIR_NO,
1830 DIR_N, DIR_N,
1831 DIR_NW, DIR_NW,
1832 DIR_W, DIR_W,
1833 DIR_SW, DIR_SW,
1834 DIR_S, DIR_S,
1835 DIR_SO, DIR_SO,
1836 DIR_O
1838 return rad_lut[hexadrant];
1841 static enum direction get_direction_from_cursor(void) {
1842 enum direction dir = DIR_INVALID;
1843 if(cursors_pressed[c_up]) {
1844 if(cursors_pressed[c_left]) dir = DIR_NW;
1845 else if(cursors_pressed[c_right]) dir = DIR_NO;
1846 else dir = DIR_N;
1847 } else if (cursors_pressed[c_down]) {
1848 if(cursors_pressed[c_left]) dir = DIR_SW;
1849 else if(cursors_pressed[c_right]) dir = DIR_SO;
1850 else dir = DIR_S;
1851 } else if (cursors_pressed[c_left]) {
1852 dir = DIR_W;
1853 } else if (cursors_pressed[c_right]) {
1854 dir = DIR_O;
1856 return dir;
1859 static enum animation_id get_anim_from_direction(enum direction dir, int player_no, int throwing) {
1860 #define DIRMAP(a, b) [a] = b
1861 if(!throwing) {
1862 static const enum animation_id dir_map_p1[] = {
1863 DIRMAP(DIR_N, ANIM_P1_MOVE_N),
1864 DIRMAP(DIR_NW, ANIM_P1_MOVE_NW),
1865 DIRMAP(DIR_W, ANIM_P1_MOVE_W),
1866 DIRMAP(DIR_SW, ANIM_P1_MOVE_SW),
1867 DIRMAP(DIR_S, ANIM_P1_MOVE_S),
1868 DIRMAP(DIR_SO, ANIM_P1_MOVE_SO),
1869 DIRMAP(DIR_O, ANIM_P1_MOVE_O),
1870 DIRMAP(DIR_NO, ANIM_P1_MOVE_NO),
1872 static const enum animation_id dir_map_p2[] = {
1873 DIRMAP(DIR_N, ANIM_P2_MOVE_N),
1874 DIRMAP(DIR_NW, ANIM_P2_MOVE_NW),
1875 DIRMAP(DIR_W, ANIM_P2_MOVE_W),
1876 DIRMAP(DIR_SW, ANIM_P2_MOVE_SW),
1877 DIRMAP(DIR_S, ANIM_P2_MOVE_S),
1878 DIRMAP(DIR_SO, ANIM_P2_MOVE_SO),
1879 DIRMAP(DIR_O, ANIM_P2_MOVE_O),
1880 DIRMAP(DIR_NO, ANIM_P2_MOVE_NO),
1882 const enum animation_id *dir_map = player_no == 0 ? dir_map_p1 : dir_map_p2;
1883 return dir_map[dir];
1884 } else {
1885 static const enum animation_id dir_map_p1_g[] = {
1886 DIRMAP(DIR_N, ANIM_P1_THROW_N),
1887 DIRMAP(DIR_NW, ANIM_P1_THROW_NW),
1888 DIRMAP(DIR_W, ANIM_P1_THROW_W),
1889 DIRMAP(DIR_SW, ANIM_P1_THROW_SW),
1890 DIRMAP(DIR_S, ANIM_P1_THROW_S),
1891 DIRMAP(DIR_SO, ANIM_P1_THROW_SO),
1892 DIRMAP(DIR_O, ANIM_P1_THROW_O),
1893 DIRMAP(DIR_NO, ANIM_P1_THROW_NO),
1895 static const enum animation_id dir_map_p2_g[] = {
1896 DIRMAP(DIR_N, ANIM_P2_THROW_N),
1897 DIRMAP(DIR_NW, ANIM_P2_THROW_NW),
1898 DIRMAP(DIR_W, ANIM_P2_THROW_W),
1899 DIRMAP(DIR_SW, ANIM_P2_THROW_SW),
1900 DIRMAP(DIR_S, ANIM_P2_THROW_S),
1901 DIRMAP(DIR_SO, ANIM_P2_THROW_SO),
1902 DIRMAP(DIR_O, ANIM_P2_THROW_O),
1903 DIRMAP(DIR_NO, ANIM_P2_THROW_NO),
1905 const enum animation_id *dir_map = player_no == 0 ? dir_map_p1_g : dir_map_p2_g;
1906 return dir_map[dir];
1908 #undef DIRMAP
1911 #if 0
1912 static enum animation_id get_anim_from_cursor(void) {
1913 enum direction dir = get_direction_from_cursor();
1914 if(dir == DIR_INVALID) return ANIM_INVALID;
1915 return get_anim_from_direction(dir, 0, 0);
1917 /* playerno is either 0 or 1, not player_id! */
1918 static enum animation_id get_anim_from_vel(int player_no, vec2f *vel) {
1919 enum direction dir = get_direction_from_vec(vel);
1920 if(dir == DIR_INVALID) return ANIM_INVALID;
1921 return get_anim_from_direction(dir, player_no, 0);
1923 #endif
1925 static void switch_anim(int obj_id, int aid) {
1926 if(objs[obj_id].animid == aid) return;
1927 gameobj_start_anim(obj_id, aid);
1930 enum map_index choose_mission(uint8_t* completed);
1931 #pragma RcB2 DEP "mission_select.c"
1932 #include "enemytag.c"
1934 static uint8_t mission_completed[MI_MAX];
1936 #include <limits.h>
1938 static void game_delay(int s) {
1939 /* wait for s frames while continuing to play music,
1940 * if s == -1 return as soon as music finished */
1941 int x = s;
1942 if(x == -1) x = INT_MAX;
1943 while(x){
1944 int r = audio_process();
1945 if(s == -1 && r == -1) return;
1946 SDL_Delay(1000/fps);
1947 x--;
1948 tickcounter++;
1952 static void finish_level(void) {
1953 music_play(TUNE_TITLE);
1954 game_delay(2*fps);
1955 video_darken_screen();
1956 video_update_region(SCREEN_MIN_X, SCREEN_MIN_Y, SCREEN_MAX_X - SCREEN_MIN_X, VMODE_H);
1957 #define STRLSZ(x) (x), sizeof(x)-1
1958 font_print(SCREEN_MIN_X+40*SCALE, SCREEN_MIN_Y+13*SCALE, STRLSZ("congratulations"), SCALE, PRGB(255,255,255));
1959 font_print(SCREEN_MIN_X+24*SCALE, SCREEN_MIN_Y+24*SCALE, STRLSZ("you have completed"), SCALE, PRGB(255,255,255));
1960 font_print(SCREEN_MIN_X+48*SCALE, SCREEN_MIN_Y+35*SCALE, STRLSZ("your mission"), SCALE, PRGB(255,255,255));
1961 video_update_region(SCREEN_MIN_X, SCREEN_MIN_Y, SCREEN_MAX_X - SCREEN_MIN_X, VMODE_H);
1962 game_delay(-1);
1963 /* FIXME: for some reason there's a crack noise when switching to "empty" */
1964 music_play(TUNE_EMPTY);
1966 uint32_t bgbuf0[8*SCALE*((95-40)*SCALE)];
1967 video_save_rect(SCREEN_MIN_X+40*SCALE,SCREEN_MIN_Y+65*SCALE,(95-40)*SCALE,8*SCALE,bgbuf0);
1969 font_print(SCREEN_MIN_X+40*SCALE, SCREEN_MIN_Y+65*SCALE, STRLSZ("mission bonus"), SCALE, PRGB(255,255,255));
1971 font_print(SCREEN_MIN_X+32*SCALE, SCREEN_MIN_Y+89*SCALE, STRLSZ("1."), SCALE, PRGB(255,255,255));
1972 uint32_t bgbuf[8*SCALE*((159-64)*SCALE)];
1973 video_save_rect(SCREEN_MIN_X+64*SCALE,SCREEN_MIN_Y+89*SCALE,(159-64)*SCALE,8*SCALE,bgbuf);
1975 char buf[16];
1976 int dollars = player_cash[0];
1977 snprintf(buf, sizeof buf, " $%.6d", dollars);
1978 font_print(SCREEN_MIN_X+64*SCALE, SCREEN_MIN_Y+89*SCALE, buf, 12, SCALE, PRGB(255,255,255));
1979 video_update_region(SCREEN_MIN_X, SCREEN_MIN_Y, SCREEN_MAX_X - SCREEN_MIN_X, VMODE_H);
1981 game_delay(2*fps);
1984 dollars += maps[current_map]->rewardk*500;
1985 video_restore_rect(SCREEN_MIN_X+64*SCALE,SCREEN_MIN_Y+89*SCALE,(159-64)*SCALE,8*SCALE,bgbuf);
1986 snprintf(buf, sizeof buf, " $%.6d", dollars);
1987 font_print(SCREEN_MIN_X+64*SCALE, SCREEN_MIN_Y+89*SCALE, buf, 12, SCALE, PRGB(255,255,255));
1988 video_update_region(SCREEN_MIN_X, SCREEN_MIN_Y, SCREEN_MAX_X - SCREEN_MIN_X, VMODE_H);
1991 audio_play_wave_resource(wavesounds[WS_MISSIONBONUS]);
1993 game_delay(4.2*fps);
1996 video_restore_rect(SCREEN_MIN_X+40*SCALE,SCREEN_MIN_Y+65*SCALE,(95-40)*SCALE,8*SCALE,bgbuf0);
1997 font_print(SCREEN_MIN_X+40*SCALE, SCREEN_MIN_Y+65*SCALE, STRLSZ("revenge bonus"), SCALE, PRGB(255,255,255));
1999 snprintf(buf, sizeof buf, "%.4d $%.6d", player_kills[0], dollars);
2000 font_print(SCREEN_MIN_X+64*SCALE, SCREEN_MIN_Y+89*SCALE, buf, 12, SCALE, PRGB(255,255,255));
2001 video_update_region(SCREEN_MIN_X, SCREEN_MIN_Y, SCREEN_MAX_X - SCREEN_MIN_X, VMODE_H);
2002 game_delay(1*fps);
2003 tickcounter = 0;
2004 while(player_kills[0]) {
2005 dollars+=10;
2006 player_kills[0]--;
2007 video_restore_rect(SCREEN_MIN_X+64*SCALE,SCREEN_MIN_Y+89*SCALE,(159-64)*SCALE,8*SCALE,bgbuf);
2008 snprintf(buf, sizeof buf, "%.4d $%.6d", player_kills[0], dollars);
2009 font_print(SCREEN_MIN_X+64*SCALE, SCREEN_MIN_Y+89*SCALE, buf, 12, SCALE, PRGB(255,255,255));
2010 video_update_region(SCREEN_MIN_X, SCREEN_MIN_Y, SCREEN_MAX_X - SCREEN_MIN_X, VMODE_H);
2011 if(tickcounter % 4 == 0) audio_play_wave_resource(wavesounds[WS_COUNTDOWN]);
2012 // FIXME: when no music is played, sound does not play either... maybe add "empty song"?
2013 game_delay(1);
2015 music_play(TUNE_LEVEL_FINISHED);
2016 game_delay(3*fps);
2018 mission_completed[current_map] = 1;
2019 player_cash[0] = dollars;
2022 #include "weapon_shop.c"
2024 int main() {
2025 video_init();
2026 clear_screen();
2027 //SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
2028 SDL_EnableKeyRepeat(100, 20);
2030 audio_init();
2031 init_player_once(0);
2033 mission_select:
2035 SDL_ShowCursor(1);
2037 /* background music for mission selection screen */
2038 music_play(TUNE_MAP);
2040 if((current_map = choose_mission(mission_completed)) == MI_INVALID) goto dun_goofed;
2042 player_cash[0] += maps[current_map]->rewardk*500;
2044 weapon_shop();
2046 music_play(TUNE_FIGHTING);
2048 SDL_ShowCursor(0);
2050 int startx = 10;
2051 int starty = 10;
2053 //redraw(surface, startx, starty);
2054 const float player_speed = 1.25f;
2055 const struct palpic* spritemap = spritemaps[SI_PLAYERS];
2056 struct { int *target; int dir; int max;} moves[] = {
2057 [c_up] = {&starty, SCALE * -1, VMODE_H - (palpic_getspriteheight(spritemap) * SCALE)},
2058 [c_down] = {&starty, SCALE, VMODE_H - (palpic_getspriteheight(spritemap) * SCALE)},
2059 [c_left] = {&startx, SCALE * -1, VMODE_W - (palpic_getspritewidth(spritemap) * SCALE)},
2060 [c_right] = {&startx, SCALE, VMODE_W - (palpic_getspritewidth(spritemap) * SCALE)},
2063 init_game_objs();
2064 int player_no = 0;
2065 int player_id = player_ids[player_no];
2067 game_tick(1);
2069 SDL_Event sdl_event;
2070 while(1) {
2071 unsigned need_redraw = 0;
2072 int weapon_inc = 0;
2073 //if(tickcounter>64) { player_kills[0] = 150; finish_level();}
2074 while (SDL_PollEvent(&sdl_event)) {
2075 need_redraw = 0;
2076 switch (sdl_event.type) {
2077 case SDL_MOUSEMOTION:
2078 if((int)mousepos->x != sdl_event.motion.x || (int)mousepos->y != sdl_event.motion.y)
2079 need_redraw = 1;
2080 mousepos->x = sdl_event.motion.x;
2081 mousepos->y = sdl_event.motion.y;
2082 break;
2083 case SDL_MOUSEBUTTONDOWN:
2084 mousepos->x = sdl_event.button.x;
2085 mousepos->y = sdl_event.button.y;
2086 mousebutton_down[MB_LEFT] = 1;
2087 fire_bullet(player_no);
2088 break;
2089 case SDL_MOUSEBUTTONUP:
2090 mousepos->x = sdl_event.button.x;
2091 mousepos->y = sdl_event.button.y;
2092 mousebutton_down[MB_LEFT] = 0;
2093 break;
2094 case SDL_QUIT:
2095 dun_goofed:
2096 video_cleanup();
2097 return 0;
2098 case SDL_KEYDOWN:
2099 switch(sdl_event.key.keysym.sym) {
2100 case SDLK_w: case SDLK_a: case SDLK_s: case SDLK_d:
2101 case SDLK_UP:
2102 case SDLK_DOWN:
2103 case SDLK_RIGHT:
2104 case SDLK_LEFT:
2105 cursors_pressed[cursor_lut[sdl_event.key.keysym.sym]] = 1;
2106 check_anim: {
2107 enum direction dir = get_direction_from_cursor();
2108 if(dir != DIR_INVALID) {
2109 if(!mousebutton_down[MB_LEFT]) {
2110 // change animation according to cursors,
2111 // unless we're in automatic fire mode.
2112 enum animation_id aid = get_anim_from_direction(dir, player_no, 0);
2113 if(aid != ANIM_INVALID) switch_anim(player_id, aid);
2115 objs[player_id].vel = get_vel_from_direction(dir, player_speed);
2116 } else {
2117 objs[player_id].vel = VEC(0,0);
2120 break;
2121 case SDLK_RETURN:
2122 if((sdl_event.key.keysym.mod & KMOD_LALT) ||
2123 (sdl_event.key.keysym.mod & KMOD_RALT)) {
2124 toggle_fullscreen();
2125 SDL_Delay(1);
2126 game_tick(1);
2127 need_redraw = 1;
2129 break;
2130 case SDLK_KP_PLUS:
2131 weapon_inc = 1;
2132 goto toggle_weapon;
2133 case SDLK_KP_MINUS:
2134 weapon_inc = -1;
2135 toggle_weapon:
2136 weapon_active[player_no] += weapon_inc;
2137 if(weapon_active[player_no] < 0)
2138 weapon_active[player_no] = weapon_count[player_no] - 1;
2139 else if(weapon_active[player_no] >= weapon_count[player_no])
2140 weapon_active[player_no] = 0;
2141 printf("%s\n", weapon_name(player_weapons[player_no][weapon_active[player_no]]));
2142 need_redraw = 1;
2143 break;
2144 default:
2145 break;
2147 break;
2148 case SDL_KEYUP:
2149 switch(sdl_event.key.keysym.sym) {
2150 case SDLK_e:
2151 editor_mode = 1;
2152 enemy_tag_loop();
2153 editor_mode = 0;
2154 update_caption = game_update_caption;
2155 break;
2156 case SDLK_c:
2157 clear_screen();
2158 video_update();
2159 need_redraw = 1;
2160 break;
2161 case SDLK_w: case SDLK_a: case SDLK_s: case SDLK_d:
2162 case SDLK_UP:
2163 case SDLK_DOWN:
2164 case SDLK_RIGHT:
2165 case SDLK_LEFT:
2166 cursors_pressed[cursor_lut[sdl_event.key.keysym.sym]] = 0;
2167 goto check_anim;
2168 case SDLK_ESCAPE:
2169 goto dun_goofed;
2170 default:
2171 break;
2173 default:
2174 break;
2177 unsigned i;
2178 for (i = 0; i < ARRAY_SIZE(cursors_pressed); i++) {
2179 if(cursors_pressed[i]) {
2180 *moves[i].target += moves[i].dir;
2181 if(*moves[i].target < 0) *moves[i].target = 0;
2182 if(*moves[i].target > moves[i].max) *moves[i].target = moves[i].max;
2183 need_redraw = 1;
2186 if(game_tick(need_redraw)) {
2187 /* completed map */
2188 finish_level();
2189 goto mission_select;
2193 return 0;