implement shooting gunturrets
[rofl0r-openDOW.git] / spriteview.c
blobbc4f8e6d42b19bf42753af4abe1aa4317bc383c3
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"
32 #include "weapon_sprites.c"
34 #endif
36 enum mousebutton {
37 MB_LEFT = 0,
38 MB_RIGHT,
41 // 1 if button down, 0 if not, >1 to count ms pressed
42 unsigned long mousebutton_down[] = {
43 [MB_LEFT] = 0,
44 [MB_RIGHT] = 0,
47 //RcB: LINK "-lSDL"
48 #if 0
49 static void get_last_move_event(SDL_Event* e) {
50 #define numpeek 32
51 SDL_Event peek[numpeek];
52 SDL_Event* last_event = NULL;
53 int i, results;
54 results = SDL_PeepEvents(peek, numpeek, SDL_PEEKEVENT, (uint32_t) ~0);
55 if(results == -1) return;
56 for(i = 0; i < results; i++) {
57 if(peek[i].type == SDL_MOUSEMOTION)
58 last_event = &peek[i];
59 else
60 break;
62 if(last_event) {
63 *e = *last_event;
64 SDL_PeepEvents(peek, i + 1, SDL_GETEVENT, (uint32_t) ~0);
66 #undef numpeek
68 #endif
70 static vec2f get_sprite_center(const struct palpic *p) {
71 assert(p->spritecount);
72 vec2f res;
73 res.x = palpic_getspritewidth(p) * SCALE / 2;
74 res.y = palpic_getspriteheight(p) * SCALE / 2;
75 return res;
78 static int player_ids[2];
79 static enum weapon_id player_weapons[2][WP_MAX];
80 static int weapon_count[2];
81 static enum weapon_id weapon_active[2]; // index into player_weapons[playerno]
82 static int player_ammo[2][AMMO_MAX];
83 static enum weapon_id get_active_weapon_id(int player_no);
84 static void switch_anim(int obj_id, int aid);
85 static vec2f get_vel_from_direction(enum direction dir, float speed);
86 static vec2f get_vel_from_direction16(enum direction16 dir, float speed);
87 // used by game_tick
88 static sblist go_player_bullets;
89 static sblist go_enemy_bullets;
90 static sblist go_explosions;
91 static sblist go_enemy_explosions;
92 static sblist go_enemies;
93 static sblist go_players;
94 static sblist go_flames;
95 static sblist go_rockets;
96 static sblist go_grenades;
97 static sblist go_enemy_grenades;
98 static sblist go_vehicles;
99 static sblist go_mines;
100 static sblist go_turrets;
101 static sblist go_bunkers;
102 static sblist go_boss;
103 static sblist go_crosshair;
104 static sblist go_muzzleflash;
105 static sblist go_blood;
106 static void add_pbullet(uint8_t bullet_id) {
107 sblist_add(&go_player_bullets, &bullet_id);
109 static void add_ebullet(uint8_t bullet_id) {
110 sblist_add(&go_enemy_bullets, &bullet_id);
112 static void add_player(uint8_t player_id) {
113 sblist_add(&go_players, &player_id);
115 static void add_enemy(uint8_t enem_id) {
116 sblist_add(&go_enemies, &enem_id);
118 static void add_explosion(uint8_t expl_id) {
119 sblist_add(&go_explosions, &expl_id);
121 static void add_enemy_explosion(uint8_t expl_id) {
122 sblist_add(&go_enemy_explosions, &expl_id);
124 static void add_flame(uint8_t id) {
125 sblist_add(&go_flames, &id);
127 static void add_grenade(uint8_t id) {
128 sblist_add(&go_grenades, &id);
130 static void add_enemy_grenade(uint8_t id) {
131 sblist_add(&go_enemy_grenades, &id);
133 static void add_rocket(uint8_t id) {
134 sblist_add(&go_rockets, &id);
136 static void add_vehicle(uint8_t id) {
137 sblist_add(&go_vehicles, &id);
139 static void add_mine(uint8_t id) {
140 sblist_add(&go_mines, &id);
142 static void add_turret(uint8_t id) {
143 sblist_add(&go_turrets, &id);
145 static void add_bunker(uint8_t id) {
146 sblist_add(&go_bunkers, &id);
148 static void add_boss(uint8_t id) {
149 sblist_add(&go_boss, &id);
151 static void add_crosshair(uint8_t id) {
152 sblist_add(&go_crosshair, &id);
154 static void add_muzzleflash(uint8_t id) {
155 sblist_add(&go_muzzleflash, &id);
157 static void add_blood(uint8_t id) {
158 sblist_add(&go_blood, &id);
160 static void golist_remove(sblist *l, uint8_t objid) {
161 size_t i;
162 uint8_t *itemid;
163 sblist_iter_counter2(l, i, itemid) {
164 if(*itemid == objid) {
165 sblist_delete(l, i);
166 return;
171 static int get_next_anim_frame(enum animation_id aid, anim_step curr) {
172 if(curr == ANIM_STEP_INIT) return animations[aid].first;
173 curr++;
174 if(curr > animations[aid].last) return animations[aid].first;
175 return curr;
178 #define SCREEN_MIN_X 64*SCALE
179 #define SCREEN_MAX_X VMODE_W - 64*SCALE
180 #define SCREEN_MIN_Y 0
181 #define SCREEN_MAX_Y 200*SCALE
183 static void draw_status_bar(void) {
184 enum weapon_id wid = get_active_weapon_id(0);
185 int x, y;
186 sdl_rgb_t *ptr = (sdl_rgb_t *) video.mem;
187 unsigned pitch = video.pitch/4;
188 for(y = SCREEN_MAX_Y; y < VMODE_H; y++)
189 for (x = SCREEN_MIN_X; x < SCREEN_MAX_X; x++)
190 ptr[y*pitch + x] = SRGB_BLACK;
192 blit_sprite(((320 / 2) - (59 / 2)) * SCALE, (200 + (40/2) - (16/2)) * SCALE,
193 &video, SCALE, &weapon_sprites.header, wid, 0);
195 char buf[16];
196 snprintf(buf, 16, "%.6u", objs[player_ids[0]].objspecific.playerdata.score);
197 font_print(SCREEN_MIN_X + 8, SCREEN_MAX_Y + 8, buf, 6, 1 * SCALE, PRGB(255,255,255));
200 enum map_index current_map = MI_VIETNAM;
201 const struct map *map;
202 const struct map_screen* map_scr;
203 const struct palpic *map_bg;
204 const struct palpic *map_fg;
205 const mapscreen_index *map_bonus_indices;
206 const struct map_fglayer *map_bonus_scr;
208 int mapscreen_yoff, mapscreen_xoff;
209 struct { int x,y; } mapsquare;
210 enum map_scrolldir mapscrolldir;
211 unsigned map_spawn_screen_index;
212 unsigned map_spawn_line;
213 unsigned map_spawn_current;
215 static void init_map(enum map_index mapindex) {
216 map = maps[mapindex];
217 map_scr = map_screens[mapindex];
218 map_bg = map_bg_sprites[map->maptype];
219 map_fg = map_fg_sprites[map->maptype];
220 map_bonus_scr = map_bonus_screens[mapindex];
221 map_bonus_indices = map_bonus_layer_indices[mapindex];
222 mapscreen_yoff = 0;
223 mapscreen_xoff = 0;
224 mapsquare.x = 5;
225 mapsquare.y = 26;
226 mapscrolldir = MS_UP;
227 map_spawn_screen_index = 0;
228 map_spawn_line = 0;
229 map_spawn_current = 0;
232 static mapscreen_index get_bonus_layer_index(mapscreen_index screen) {
233 unsigned i;
234 for (i = 0; i < map->bonuslayer_count; i++)
235 if(map_bonus_indices[i] == screen) return i;
236 return MAPSCREEN_BLOCKED;
239 static mapscreen_index screen_to_mapscreen(int *x, int *y) {
240 *x = ((int) *x - SCREEN_MIN_X) / SCALE;
241 *y = ((int) *y - SCREEN_MIN_Y) / SCALE;
242 int yscr = (*y + mapscreen_yoff) / 192;
243 *y = (*y + mapscreen_yoff) - yscr*192;
244 int xscr = (*x + mapscreen_xoff) / 192;
245 *x = (*x + mapscreen_xoff) - xscr*192;
246 return map->screen_map[mapsquare.y + yscr][mapsquare.x + xscr];
249 static enum walltype is_wall(vec2f *pos) {
250 int x = pos->x;
251 int y = pos->y;
252 mapscreen_index scr_idx = screen_to_mapscreen(&x, &y);
253 /* can happen when a bullet goes partially off-screen */
254 if(scr_idx == MAPSCREEN_BLOCKED) return WT_NONE;
255 uint8_t spriteno = map_scr[scr_idx].fg.fg[y/16][x/16];
256 if(spriteno && walls[map->maptype][spriteno]) return walls[map->maptype][spriteno];
257 scr_idx = get_bonus_layer_index(scr_idx);
258 if(scr_idx == MAPSCREEN_BLOCKED) return WT_NONE;
259 spriteno = map_bonus_scr[scr_idx].fg[y/16][x/16];
260 if(spriteno && walls[map->maptype][spriteno]) return walls[map->maptype][spriteno];
261 return WT_NONE;
264 static void draw_map() {
265 int y, x, my, mx;
266 unsigned map_off = 192-mapscreen_yoff;
267 unsigned vis_x = 192-mapscreen_xoff;
268 int x_iter_max16 = 192/16;
269 int x_iter_max64 = 192/64;
270 unsigned x_iter_start64 = x_iter_max64 - (vis_x / 64 + !!(vis_x % 64));
271 unsigned x_iter_start16 = x_iter_max16 - (vis_x / 16 + !!(vis_x % 16));
273 int x_screen_start64 = -(!!(vis_x%64)*64-(vis_x%64));
274 int x_screen_start16 = -(!!(vis_x%16)*16-(vis_x%16));
276 unsigned x_screen_iter;
277 for(x_screen_iter = 0; x_screen_iter <= !!mapscreen_xoff; x_screen_iter++) {
278 mapscreen_index bonus_layer;
279 if(x_screen_iter) {
280 x_screen_start16 = x_screen_start16+(x_iter_max16-x_iter_start16)*16;
281 x_screen_start64 = x_screen_start64+(x_iter_max64-x_iter_start64)*64;
282 x_iter_max16 = mapscreen_xoff / 16 + !!(mapscreen_xoff % 16);
283 x_iter_max64 = mapscreen_xoff / 64 + !!(mapscreen_xoff % 64);
284 x_iter_start16 = 0;
285 x_iter_start64 = 0;
286 assert(map->screen_map[mapsquare.y][mapsquare.x+x_screen_iter] != MAPSCREEN_BLOCKED);
288 uint8_t spriteno;
290 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)
291 for(mx = x_iter_start64, x = SCREEN_MIN_X + x_screen_start64*SCALE; mx < x_iter_max64; mx++, x += 64*SCALE) {
292 spriteno = map_scr[map->screen_map[mapsquare.y][mapsquare.x+x_screen_iter]].bg.bg[my][mx];
293 blit_sprite(x, y, &video,
294 SCALE, map_bg, spriteno, 0);
296 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)
297 for(mx = x_iter_start16, x = SCREEN_MIN_X + x_screen_start16*SCALE; mx < x_iter_max16; mx++, x += 16*SCALE) {
298 spriteno = map_scr[map->screen_map[mapsquare.y][mapsquare.x+x_screen_iter]].fg.fg[my][mx];
299 if(spriteno) blit_sprite(x, y, &video, SCALE, map_fg, spriteno, 0);
301 bonus_layer = get_bonus_layer_index(map->screen_map[mapsquare.y][mapsquare.x+x_screen_iter]);
302 if(bonus_layer != MAPSCREEN_BLOCKED) {
303 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)
304 for(mx = x_iter_start16, x = SCREEN_MIN_X + x_screen_start16*SCALE; mx < x_iter_max16; mx++, x += 16*SCALE) {
305 spriteno = map_bonus_scr[bonus_layer].fg[my][mx];
306 if(spriteno) blit_sprite(x, y, &video, SCALE, map_fg, spriteno, 0);
311 int yleft = 200-map_off;
312 if(yleft > 192) yleft = 192;
313 for(my = 0, y = SCREEN_MIN_Y + (map_off * SCALE); my < yleft/32+!!(yleft%32); my++, y+=32*SCALE)
314 for(mx = x_iter_start64, x = SCREEN_MIN_X + x_screen_start64*SCALE; mx < x_iter_max64; mx++, x += 64*SCALE) {
315 spriteno = map_scr[map->screen_map[mapsquare.y+1][mapsquare.x+x_screen_iter]].bg.bg[my][mx];
316 blit_sprite(x, y, &video, SCALE, map_bg, spriteno, 0);
318 for(my = 0, y = SCREEN_MIN_Y + (map_off * SCALE); my < yleft/16+!!(yleft%16); my++, y+=16*SCALE)
319 for(mx = x_iter_start16, x = SCREEN_MIN_X + x_screen_start16*SCALE; mx < x_iter_max16; mx++, x += 16*SCALE) {
320 spriteno = map_scr[map->screen_map[mapsquare.y+1][mapsquare.x+x_screen_iter]].fg.fg[my][mx];
321 if(spriteno) blit_sprite(x, y, &video, SCALE, map_fg, spriteno, 0);
324 bonus_layer = get_bonus_layer_index(map->screen_map[mapsquare.y+1][mapsquare.x+x_screen_iter]);
325 if(bonus_layer != MAPSCREEN_BLOCKED) {
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_bonus_scr[bonus_layer].fg[my][mx];
329 if(spriteno) blit_sprite(x, y, &video, SCALE, map_fg, spriteno, 0);
333 /* this is never triggered when mapscreen_xoff != 0 */
334 if(mapscreen_yoff > 192 - 8) {
335 for(mx = 0, x = SCREEN_MIN_X + x_screen_start64*SCALE; mx < 3; mx++, x += 64*SCALE)
336 blit_sprite(x, SCALE*(192*2-mapscreen_yoff), &video,
337 SCALE, map_bg, map_scr[map->screen_map[mapsquare.y+2][mapsquare.x]].bg.bg[0][mx], 0);
338 for(mx = 0, x = SCREEN_MIN_X + x_screen_start16*SCALE; mx < 12; mx++, x += 16*SCALE) {
339 spriteno = map_scr[map->screen_map[mapsquare.y+2][mapsquare.x]].fg.fg[0][mx];
340 if(spriteno) blit_sprite(x, SCALE*(192*2-mapscreen_yoff), &video, SCALE, map_fg, spriteno, 0);
342 bonus_layer = get_bonus_layer_index(map->screen_map[mapsquare.y+2][mapsquare.x]);
343 if(bonus_layer != MAPSCREEN_BLOCKED) {
344 for(mx = 0, x = SCREEN_MIN_X + x_screen_start16*SCALE; mx < 12; mx++, x += 16*SCALE) {
345 spriteno = map_bonus_scr[bonus_layer].fg[0][mx];
346 if(spriteno) blit_sprite(x, SCALE*(192*2-mapscreen_yoff), &video, SCALE, map_fg, spriteno, 0);
353 #define VSCROLL_TRESHOLD (200-74)
354 #define HSCROLLR_TRESHOLD (54-6)
355 #define HSCROLLL_TRESHOLD (192-(78+3))
356 static int scroll_needed() {
357 struct gameobj *player = &objs[player_ids[0]];
358 if((mapscrolldir == MS_UP && player->pos.y - SCREEN_MIN_Y < VSCROLL_TRESHOLD*SCALE) ||
359 (mapscrolldir == MS_RIGHT && player->pos.x - SCREEN_MIN_X > HSCROLLR_TRESHOLD*SCALE) ||
360 (mapscrolldir == MS_LEFT && player->pos.x - SCREEN_MIN_X < HSCROLLL_TRESHOLD*SCALE))
361 return 1;
362 return 0;
365 static void scroll_gameobjs(int scroll_step) {
366 if(!scroll_step) return;
367 unsigned i, avail = obj_count;
368 for(i = 0; i < OBJ_MAX && avail; i++) {
369 if(!obj_slot_used[i]) continue;
370 avail--;
371 if(objs[i].objtype == OBJ_CROSSHAIR) continue;
373 if(mapscrolldir == MS_UP)
374 objs[i].pos.y += scroll_step*SCALE;
375 else if(mapscrolldir == MS_LEFT)
376 objs[i].pos.x += scroll_step*SCALE;
377 else if(mapscrolldir == MS_RIGHT)
378 objs[i].pos.x -= scroll_step*SCALE;
382 static void next_screen() {
383 map_spawn_screen_index++;
384 map_spawn_line = 0;
385 map_spawn_current = 0;
388 static int init_enemy(const struct enemy_spawn *spawn);
389 static void handle_spawns(unsigned scrollstep) {
390 assert(scrollstep <= 192);
391 unsigned i;
392 const struct enemy_spawn_screen *spawn_map = spawn_maps[current_map];
393 if(!spawn_map[map_spawn_screen_index].spawns) goto done;
394 for(i = 0; i < scrollstep; i++) {
395 while(map_spawn_current < spawn_map[map_spawn_screen_index].num_spawns &&
396 map_spawn_line+i >= spawn_map[map_spawn_screen_index].spawns[map_spawn_current].scroll_line) {
397 init_enemy(&spawn_map[map_spawn_screen_index].spawns[map_spawn_current]);
398 map_spawn_current++;
401 done:
402 map_spawn_line += scrollstep;
405 static int scroll_map() {
406 int ret = 0;
407 int scroll_step = 1;
408 if(scroll_needed()) {
409 if(mapscrolldir == MS_UP) {
410 mapscreen_yoff -= scroll_step;
411 if(mapscreen_yoff < 0) {
412 mapsquare.y--;
413 if(map->screen_map[mapsquare.y][mapsquare.x] == MAPSCREEN_BLOCKED) {
414 scroll_step = -mapscreen_yoff;
415 mapscreen_yoff = 0;
416 mapsquare.y++;
417 scroll_gameobjs(scroll_step);
418 if(map->screen_map[mapsquare.y][mapsquare.x+1] == MAPSCREEN_BLOCKED) {
419 mapscrolldir = MS_LEFT;
420 } else {
421 mapscrolldir = MS_RIGHT;
422 next_screen();
424 scroll_step = 0;
425 } else {
426 next_screen();
427 mapscreen_yoff += 192;
430 handle_objs:;
431 handle_spawns(scroll_step);
432 scroll_gameobjs(scroll_step);
433 ret = 1;
434 } else if(mapscrolldir == MS_LEFT) {
435 mapscreen_xoff -= scroll_step;
436 if(mapscreen_xoff < 0) {
437 mapsquare.x--;
438 if(map->screen_map[mapsquare.y][mapsquare.x] == MAPSCREEN_BLOCKED) {
439 scroll_step = -mapscreen_xoff;
440 mapscreen_xoff = 0;
441 mapscreen_yoff = 0;
442 mapsquare.x++;
443 scroll_gameobjs(scroll_step);
444 mapscrolldir = MS_UP;
445 scroll_step = 0;
446 } else {
447 mapscreen_xoff += 192;
448 next_screen();
451 goto handle_objs;
452 } else if(mapscrolldir == MS_RIGHT) {
453 mapscreen_xoff += scroll_step;
454 if(mapscreen_xoff >= 192) {
455 mapsquare.x++;
456 if(map->screen_map[mapsquare.y][mapsquare.x+1] == MAPSCREEN_BLOCKED) {
457 scroll_step = mapscreen_xoff - 192;
458 mapscreen_xoff = 0;
459 mapscreen_yoff = 0;
460 scroll_gameobjs(scroll_step);
461 mapscrolldir = MS_UP;
462 scroll_step = 0;
463 } else {
464 next_screen();
465 mapscreen_xoff -= 192;
468 goto handle_objs;
471 return ret;
474 static int init_player(int player_no) {
475 assert(player_no == 0 || player_no == 1);
476 int pid = gameobj_alloc();
477 gameobj_init(pid, &VEC( SCREEN_MIN_X, SCREEN_MAX_Y - (25 * SCALE) ), &VEC( 0, 0 ),
478 SI_PLAYERS, player_no == 0 ? ANIM_P1_MOVE_N : ANIM_P2_MOVE_N, player_no == 0 ? OBJ_P1 : OBJ_P2);
479 if(pid == -1) return -1;
480 player_ids[player_no] = pid;
481 objs[pid].objspecific.playerdata.score = 0;
482 player_weapons[player_no][0] = WP_COLT45;
483 weapon_count[player_no] = 1;
484 weapon_active[player_no] = 0;
485 size_t i = 0;
486 for(; i < AMMO_MAX; i++)
487 player_ammo[player_no][i] = 50000;
488 add_player(pid);
489 return pid;
492 static vec2f *mousepos;
493 static int init_crosshair() {
494 int id = gameobj_alloc();
495 gameobj_init(id, &VEC(VMODE_W/2, VMODE_H/2), &VEC(0,0), SI_CROSSHAIR, ANIM_CROSSHAIR, OBJ_CROSSHAIR);
496 if(id == -1) return -1;
497 mousepos = &objs[id].pos;
498 return id;
501 static int init_blood(vec2f *pos) {
502 int id = gameobj_alloc();
503 gameobj_init(id, pos, &VEC(0,0), SI_MISC, ANIM_BLOOD, OBJ_BLOOD);
504 gameobj_init_bulletdata(id, 4);
505 return id;
508 static int init_bullet(vec2f *pos, vec2f *vel, int steps) {
509 int id = gameobj_alloc();
510 gameobj_init(id, pos, vel, SI_BULLET, ANIM_BULLET, OBJ_BULLET);
511 gameobj_init_bulletdata(id, steps);
512 return id;
515 static int init_grenade(vec2f *pos, vec2f *vel, int steps) {
516 int id = gameobj_alloc();
517 gameobj_init(id, pos, vel, SI_GRENADE, ANIM_GRENADE_SMALL, OBJ_GRENADE);
518 gameobj_init_bulletdata(id, steps);
519 return id;
522 static int init_grenade_explosion(vec2f *pos, int from_enemy) {
523 const int ticks_per_anim_frame = 4;
524 const int expl_anim_frames = 11;
525 vec2f grenade_center = get_sprite_center(spritemaps[SI_GRENADE_EXPLOSION]);
526 vec2f mypos = vecsub(pos, &grenade_center);
527 int id = gameobj_alloc();
528 if(id == -1) return -1;
529 gameobj_init(id, &mypos, &VEC(0,0), SI_GRENADE_EXPLOSION, ANIM_GRENADE_EXPLOSION, OBJ_GRENADE_EXPLOSION);
530 gameobj_init_bulletdata(id, expl_anim_frames*ticks_per_anim_frame -1);
531 audio_play_wave_resource(wavesounds[WS_GRENADE_EXPLOSION]);
532 if(!from_enemy) add_explosion(id);
533 else add_enemy_explosion(id);
534 return id;
537 static int init_big_explosion(vec2f *pos) {
538 const int ticks_per_anim_frame = 8;
539 const int expl_anim_frames = 5;
540 vec2f rocket_center = get_sprite_center(spritemaps[SI_BIG_EXPLOSION]);
541 vec2f mypos = vecsub(pos, &rocket_center);
542 int id = gameobj_alloc();
543 if(id == -1) return -1;
544 gameobj_init(id, &mypos, &VEC(0,0), SI_BIG_EXPLOSION, ANIM_BIG_EXPLOSION, OBJ_BIG_EXPLOSION);
545 gameobj_init_bulletdata(id, expl_anim_frames*ticks_per_anim_frame -1);
546 audio_play_wave_resource(wavesounds[WS_GRENADE_EXPLOSION]);
547 add_explosion(id);
548 return id;
551 static int init_rocket_explosion(vec2f *pos) {
552 vec2f ax = vecadd(pos, &VEC(-15*SCALE, 9*SCALE));
553 vec2f bx = vecadd(pos, &VEC(1*SCALE, -6*SCALE));
554 vec2f cx = vecadd(pos, &VEC(-8*SCALE, -8*SCALE));
555 vec2f dx = vecadd(pos, &VEC(8*SCALE, 8*SCALE));
556 int ret = 0;
557 ret += init_grenade_explosion(&ax, 0) != -1;
558 ret += init_grenade_explosion(&bx, 0) != -1;
559 ret += init_big_explosion(&cx) != -1;
560 ret += init_big_explosion(&dx) != -1;
561 return ret;
564 static int init_flame(enum direction dir, vec2f *pos, vec2f *vel, int steps) {
565 static const vec2f flame_origin[] = {
566 [DIR_O] = { 4.0, 8.0 },
567 [DIR_NO] = { 5.0, 11.0 },
568 [DIR_N] = { 7.5, 12.0 },
569 [DIR_NW] = { 10.0, 11.0 },
570 [DIR_W] = { 11.0, 8.0 },
571 [DIR_SW] = { 10.0, 5.0 },
572 [DIR_S] = { 7.5, 3.0 },
573 [DIR_SO] = { 4.0, 4.0 },
575 vec2f mypos = *pos;
576 mypos.x -= flame_origin[dir].x * SCALE;
577 mypos.y -= flame_origin[dir].y * SCALE;
578 int id = gameobj_alloc();
579 if(id == -1) return -1;
580 gameobj_init(id, &mypos, vel, SI_FLAME, ANIM_FLAME, OBJ_FLAME);
581 gameobj_init_bulletdata(id, steps);
582 add_flame(id);
583 return id;
586 static int init_rocket(enum direction dir, vec2f *pos, vec2f *vel, int steps) {
587 static const vec2f rocket_origin[] = {
588 [DIR_N] = { 1.0, 10.0 },
589 [DIR_S] = { 1.0, 0.0 },
590 [DIR_O] = { 0.0, 1.0 },
591 [DIR_W] = { 10.0, 1.0 },
592 [DIR_NO] = { 0.0, 7.0 },
593 [DIR_SO] = { 0.0, 0.0 },
594 [DIR_SW] = { 7.0, 0.0 },
595 [DIR_NW] = { 7.0, 7.0 },
597 static const enum animation_id rocket_anim[] = {
598 [DIR_N] = ANIM_ROCKET_N,
599 [DIR_S] = ANIM_ROCKET_S,
600 [DIR_O] = ANIM_ROCKET_O,
601 [DIR_W] = ANIM_ROCKET_W,
602 [DIR_NO] = ANIM_ROCKET_NO,
603 [DIR_SO] = ANIM_ROCKET_SO,
604 [DIR_SW] = ANIM_ROCKET_SW,
605 [DIR_NW] = ANIM_ROCKET_NW,
607 vec2f mypos = *pos;
608 mypos.x -= rocket_origin[dir].x * SCALE;
609 mypos.y -= rocket_origin[dir].y * SCALE;
610 int id = gameobj_alloc();
611 if(id == -1) return -1;
612 gameobj_init(id, &mypos, vel, SI_ROCKET, rocket_anim[dir], OBJ_ROCKET);
613 gameobj_init_bulletdata(id, steps);
614 add_rocket(id);
615 return id;
619 static const struct enemy_route* get_enemy_current_route(int curr_step, const struct enemy_spawn *spawn) {
620 int i = ENEMY_MAX_ROUTE -1;
621 for(; i >= 0; i--)
622 if(spawn->route[i].shape != ES_INVALID &&
623 curr_step >= spawn->route[i].start_step)
624 return &spawn->route[i];
625 return 0;
628 static vec2f get_enemy_vel(int curr_step, const struct enemy_spawn *spawn) {
629 const struct enemy_route *route = get_enemy_current_route(curr_step, spawn);
630 return get_vel_from_direction16(route->dir, (float)route->vel/8.f);
633 static const enum animation_id enemy_animation_lut[] = {
634 [ES_SOLDIER1_DOWN] = ANIM_ENEMY_GUNNER_DOWN,
635 [ES_SOLDIER1_RIGHT] = ANIM_ENEMY_GUNNER_RIGHT,
636 [ES_SOLDIER1_LEFT] = ANIM_ENEMY_GUNNER_LEFT,
637 [ES_SOLDIER2_DOWN] = ANIM_ENEMY_BOMBER_DOWN,
638 [ES_SOLDIER2_RIGHT] = ANIM_ENEMY_BOMBER_RIGHT,
639 [ES_SOLDIER2_LEFT] = ANIM_ENEMY_BOMBER_LEFT,
640 [ES_JEEP] = ANIM_JEEP,
641 [ES_TANK_SMALL] = ANIM_TANK_SMALL,
642 [ES_TANK_BIG] = ANIM_TANK_BIG,
643 [ES_TRANSPORTER] = ANIM_TRANSPORTER,
644 [ES_GUNTURRET_MOVABLE_MACHINE] = ANIM_GUNTURRET_MOVABLE_MACHINE_S,
645 [ES_GUNTURRET_MOVABLE_MAN] = ANIM_GUNTURRET_MOVABLE_MAN_S,
646 [ES_MINE_FLAT] = ANIM_MINE_FLAT,
647 [ES_MINE_CROSS] = ANIM_MINE_CROSSED,
648 [ES_FLAMETURRET] = ANIM_FLAMETURRET,
649 [ES_GUNTURRET_FIXED_SOUTH] = ANIM_GUNTURRET_FIXED_SOUTH,
650 [ES_GUNTURRET_FIXED_NORTH] = ANIM_GUNTURRET_FIXED_NORTH,
651 [ES_BUNKER_1] = ANIM_BUNKER1,
652 [ES_BUNKER_2] = ANIM_BUNKER2,
653 [ES_BUNKER_3] = ANIM_BUNKER3,
654 [ES_BUNKER_4] = ANIM_BUNKER4,
655 [ES_BUNKER_5] = ANIM_BUNKER5,
658 static int init_enemy(const struct enemy_spawn *spawn) {
659 const enum objtype enemy_soldier_objtype_lut[] = {
660 [0] = OBJ_ENEMY_SHOOTER,
661 [1] = OBJ_ENEMY_BOMBER
663 const enum objtype enemy_objtype_lut[] = {
664 [ES_JEEP] = OBJ_JEEP,
665 [ES_TANK_SMALL] = OBJ_TANK_SMALL,
666 [ES_TANK_BIG] = OBJ_TANK_BIG,
667 [ES_TRANSPORTER] = OBJ_TRANSPORTER,
668 [ES_GUNTURRET_MOVABLE_MACHINE] = OBJ_GUNTURRET_MOVABLE_MACHINE,
669 [ES_GUNTURRET_MOVABLE_MAN] = OBJ_GUNTURRET_MOVABLE_MAN,
670 [ES_MINE_FLAT] = OBJ_MINE_FLAT,
671 [ES_MINE_CROSS] = OBJ_MINE_CROSSED,
672 [ES_FLAMETURRET] = OBJ_FLAMETURRET,
673 [ES_GUNTURRET_FIXED_SOUTH] = OBJ_GUNTURRET_FIXED_SOUTH,
674 [ES_GUNTURRET_FIXED_NORTH] = OBJ_GUNTURRET_FIXED_NORTH,
675 [ES_BUNKER_1] = OBJ_BUNKER1,
676 [ES_BUNKER_2] = OBJ_BUNKER2,
677 [ES_BUNKER_3] = OBJ_BUNKER3,
678 [ES_BUNKER_4] = OBJ_BUNKER4,
679 [ES_BUNKER_5] = OBJ_BUNKER5,
680 [ES_BOSS] = OBJ_BOSS,
682 const enum animation_id boss_animation_lut[] = {
683 [0] = ANIM_BOSS1,
684 [1] = ANIM_BOSS2,
685 [2] = ANIM_BOSS3,
686 [3] = ANIM_BOSS4,
687 [4] = ANIM_BOSS5,
688 [5] = ANIM_BOSS6,
689 [6] = ANIM_BOSS7,
690 [7] = ANIM_BOSS8,
691 [8] = ANIM_BOSS9,
692 [9] = ANIM_BOSS10,
693 [10] = ANIM_BOSS11,
694 [11] = ANIM_BOSS12,
696 const enum sprite_index enemy_soldier_sprite_lut[] = {
697 [ET_ASIAN] = SI_ENEMY_ASIAN,
698 [ET_WESTERN] = SI_ENEMY_WESTERN,
700 const enum sprite_index enemy_sprite_lut[] = {
701 [ES_JEEP] = SI_VEHICLES_SMALL,
702 [ES_TANK_SMALL] = SI_VEHICLES_MEDIUM,
703 [ES_TANK_BIG] = SI_VEHICLES_BIG,
704 [ES_TRANSPORTER] = SI_VEHICLES_BIG,
705 [ES_BUNKER_1] = SI_BUNKERS,
706 [ES_BUNKER_2] = SI_BUNKERS,
707 [ES_BUNKER_3] = SI_BUNKERS,
708 [ES_BUNKER_4] = SI_BUNKERS,
709 [ES_BUNKER_5] = SI_BUNKERS,
710 [ES_GUNTURRET_MOVABLE_MACHINE] = SI_GUNTURRET,
711 [ES_GUNTURRET_MOVABLE_MAN] = SI_GUNTURRET,
712 [ES_MINE_FLAT] = SI_MINES,
713 [ES_MINE_CROSS] = SI_MINES,
714 [ES_FLAMETURRET] = SI_MINES,
715 [ES_GUNTURRET_FIXED_SOUTH] = SI_MINES,
716 [ES_GUNTURRET_FIXED_NORTH] = SI_MINES,
717 [ES_BOSS] = SI_BOSSES,
720 vec2f spawnpos = VEC(SCREEN_MIN_X + spawn->x*SCALE, SCREEN_MIN_Y + spawn->y*SCALE);
721 int id = gameobj_alloc();
722 if(id == -1) return -1;
723 const struct enemy_route* route_curr = get_enemy_current_route(0, spawn);
724 vec2f vel = get_enemy_vel(0, spawn);
726 int is_soldier = route_curr->shape <= ES_SOLDIER2_RIGHT;
727 int is_boss = route_curr->shape == ES_BOSS;
728 enum sprite_index spriteid;
729 enum objtype objid;
730 enum animation_id animid;
731 if(is_soldier) {
732 spriteid = enemy_soldier_sprite_lut[map->enemy_type];
733 objid = enemy_soldier_objtype_lut[spawn->weapon];
734 } else {
735 spriteid = enemy_sprite_lut[route_curr->shape];
736 objid = enemy_objtype_lut[route_curr->shape];
738 if(is_boss) animid = boss_animation_lut[map->boss_id];
739 else animid = enemy_animation_lut[route_curr->shape];
741 gameobj_init(id, &spawnpos, &vel, spriteid, animid, objid);
742 objs[id].objspecific.enemy.curr_step = 0;
743 objs[id].objspecific.enemy.spawn = spawn;
744 switch(objid) {
745 case OBJ_BOSS:
746 add_boss(id);
747 break;
748 case OBJ_BUNKER1: case OBJ_BUNKER2: case OBJ_BUNKER3:
749 case OBJ_BUNKER4: case OBJ_BUNKER5:
750 add_bunker(id);
751 break;
752 case OBJ_FLAMETURRET: case OBJ_GUNTURRET_FIXED_NORTH:
753 case OBJ_GUNTURRET_FIXED_SOUTH:
754 add_turret(id);
755 break;
756 case OBJ_MINE_CROSSED: case OBJ_MINE_FLAT:
757 add_mine(id);
758 break;
759 case OBJ_JEEP: case OBJ_TRANSPORTER:
760 case OBJ_TANK_BIG: case OBJ_TANK_SMALL:
761 add_vehicle(id);
762 break;
763 default:
764 add_enemy(id);
766 return id;
769 static void remove_enemy(int id) {
770 enum objtype objid = objs[id].objtype;
771 switch(objid) {
772 case OBJ_JEEP: case OBJ_TRANSPORTER:
773 case OBJ_TANK_BIG: case OBJ_TANK_SMALL:
774 golist_remove(&go_vehicles, id);
775 break;
776 default:
777 golist_remove(&go_enemies, id);
779 gameobj_free(id);
782 static int enemy_fires(struct enemy *e) {
783 int i;
784 for(i = 0; i < ENEMY_MAX_SHOT; i++)
785 if(e->curr_step == e->spawn->shots[i]) return 1;
786 return 0;
789 static enum animation_id get_flash_animation_from_direction(enum direction dir) {
790 #define ANIMF(dir, anim) [dir] = anim
791 static const enum animation_id dir_to_anim[] = {
792 ANIMF(DIR_O, ANIM_FLASH_O),
793 ANIMF(DIR_NO, ANIM_FLASH_NO),
794 ANIMF(DIR_N, ANIM_FLASH_N),
795 ANIMF(DIR_NW, ANIM_FLASH_NW),
796 ANIMF(DIR_W, ANIM_FLASH_W),
797 ANIMF(DIR_SW, ANIM_FLASH_SW),
798 ANIMF(DIR_S, ANIM_FLASH_S),
799 ANIMF(DIR_SO, ANIM_FLASH_SO),
801 #undef ANIMF
802 return dir_to_anim[dir];
805 static int init_flash(vec2f *pos, enum direction dir) {
806 int id = gameobj_alloc();
807 gameobj_init(id, pos, &VEC(0, 0), SI_FLASH, get_flash_animation_from_direction(dir), OBJ_FLASH);
808 gameobj_init_bulletdata(id, 2);
809 return id;
812 static vec2f get_gameobj_pos(int obj_id) {
813 return objs[obj_id].pos;
816 static vec2f get_gameobj_center(int obj_id) {
817 vec2f res = objs[obj_id].pos;
818 vec2f add = get_sprite_center(spritemaps[objs[obj_id].spritemap_id]);
819 return vecadd(&res, &add);
822 static enum direction get_direction_from_vec(vec2f *vel);
823 static enum animation_id get_anim_from_direction(enum direction dir, int player, int throwing);
825 static enum weapon_id get_active_weapon_id(int player_no) {
826 return player_weapons[player_no][weapon_active[player_no]];
829 static const struct weapon* get_active_weapon(int player_no) {
830 return &weapons[get_active_weapon_id(player_no)];
832 static enum direction get_shotdirection_from_enemy(int curr_step, const struct enemy_spawn *spawn) {
833 const struct enemy_route* r = get_enemy_current_route(curr_step, spawn);
834 switch(r->shape) {
835 case ES_SOLDIER1_DOWN: case ES_SOLDIER2_DOWN:
836 return DIR_S;
837 case ES_SOLDIER1_LEFT: case ES_SOLDIER2_LEFT:
838 return DIR_W;
839 case ES_SOLDIER1_RIGHT: case ES_SOLDIER2_RIGHT:
840 return DIR_O;
841 default:
842 assert(0);
846 static void enemy_fire_bullet(int objid, enum direction16 dir16, int steps) {
847 struct gameobj* go = &objs[objid];
848 enum direction16 dir = dir16;
849 if(dir == DIR16_INVALID)
850 dir = get_shotdirection_from_enemy(go->objspecific.enemy.curr_step, go->objspecific.enemy.spawn);
851 vec2f from = get_gameobj_center(objid);
852 vec2f vel = get_vel_from_direction16(dir, 1.75);
853 int id;
854 if(go->objspecific.enemy.spawn->weapon == EW_GUN) {
855 id = init_bullet(&from, &vel, steps);
856 if(id != -1) add_ebullet(id);
857 } else {
858 id = init_grenade(&from, &vel, steps);
859 if(id != -1) add_enemy_grenade(id);
863 static int get_crosshair_id(void) {
864 assert(sblist_getsize(&go_crosshair));
865 uint8_t *id = sblist_get(&go_crosshair, 0);
866 return *id;
869 static void fire_bullet(int player_no) {
870 int id;
871 const struct weapon *pw = get_active_weapon(player_no);
872 if(player_ammo[player_no][pw->ammo] == 0) return;
873 vec2f from = get_gameobj_center(player_ids[player_no]);
874 //get_anim_from_vel(0, objs[player].
875 vec2f to = get_gameobj_center(get_crosshair_id());
876 to.x += 4*SCALE - rand()%8*SCALE;
877 to.y += 4*SCALE - rand()%8*SCALE;
878 vec2f vel = velocity(&from, &to);
879 enum direction dir = get_direction_from_vec(&vel);
880 if(dir != DIR_INVALID) {
881 enum animation_id aid = get_anim_from_direction(dir, player_no, pw->ammo == AMMO_GRENADE);
882 if(aid != ANIM_INVALID) switch_anim(player_ids[player_no], aid);
883 anim_step curranim = objs[player_ids[player_no]].anim_curr;
884 if(curranim == ANIM_STEP_INIT) curranim = get_next_anim_frame(objs[player_ids[player_no]].animid, ANIM_STEP_INIT);
885 vec2f muzzle = muzzle_tab[curranim];
887 from = get_gameobj_pos(player_ids[player_no]);
888 from.x += muzzle.x * SCALE;
889 from.y += muzzle.y * SCALE;
891 if(pw->flags & WF_MUZZLEFLASH) {
892 static const vec2f flash_start[] = {
893 [DIR_O] = { 0.0, 1.0 },
894 [DIR_NO] = { 0.5, 6.0 },
895 [DIR_N] = { 1.0, 7.5 },
896 [DIR_NW] = { 6.0, 6.0 },
897 [DIR_W] = { 7.5, 1.0 },
898 [DIR_SW] = { 4.5, 0.0 },
899 [DIR_S] = { 1.0, 0.0 },
900 [DIR_SO] = { 0.0, 0.0 },
902 vec2f ffrom = from;
903 ffrom.x -= flash_start[dir].x * SCALE;
904 ffrom.y -= flash_start[dir].y * SCALE;
905 id = init_flash(&ffrom, dir);
906 if(id != -1) add_muzzleflash(id);
908 vel = velocity(&from, &to);
910 float dist = veclength(&vel);
911 float speed = pw->bullet_speed * SCALE;
912 const uint16_t range_tab[] = {0, 80, 66, 80, 118, 118, 118, 118, 118, 118,
913 200, 200, 240, 240, 240, 240, 240, 240, 240, 240, 320 };
914 float range = range_tab[pw->range] * SCALE;
915 if(dist > range)
916 dist = range;
917 float steps = dist / speed;
918 float deg = atan2(vel.y, vel.x);
919 vel.x = cos(deg) * speed;
920 vel.y = sin(deg) * speed;
921 switch(pw->shot) {
922 case ST_LAUNCHER:
923 id = init_rocket(dir, &from, &vel, steps);
924 break;
925 case ST_BULLET:
926 id = init_bullet(&from, &vel, steps);
927 if(id != -1) add_pbullet(id);
928 break;
929 case ST_FLAMES:
930 id = init_flame(dir, &from, &vel, steps);
931 break;
932 case ST_GRENADE:
933 id = init_grenade(&from, &vel, steps);
934 add_grenade(id);
935 break;
936 default:
937 abort();
939 player_ammo[player_no][pw->ammo]--;
940 const WAVE_HEADER_COMPLETE *wf = wavesounds[pw->sound];
941 if(id != -1 && pw->sound != WS_NONE)
942 audio_play_wave_resource(wf);
945 static void init_game_objs() {
946 sblist_init(&go_crosshair, 1, 4);
947 sblist_init(&go_players, 1, 4);
948 sblist_init(&go_muzzleflash, 1, 4);
949 sblist_init(&go_player_bullets, 1, 32);
950 sblist_init(&go_flames, 1, 32);
951 sblist_init(&go_enemy_bullets, 1, 32);
952 sblist_init(&go_explosions, 1, 16);
953 sblist_init(&go_enemy_explosions, 1, 16);
954 sblist_init(&go_grenades, 1, 16);
955 sblist_init(&go_enemy_grenades, 1, 16);
956 sblist_init(&go_rockets, 1, 8);
957 sblist_init(&go_enemies, 1, 32);
958 sblist_init(&go_vehicles, 1, 4);
959 sblist_init(&go_mines, 1, 4);
960 sblist_init(&go_turrets, 1, 8);
961 sblist_init(&go_bunkers, 1, 4);
962 sblist_init(&go_boss, 1, 4);
963 sblist_init(&go_blood, 1, 16);
964 init_player(0);
965 add_crosshair(init_crosshair());
966 init_map(current_map);
969 static int point_in_mask(vec2f *point, int obj_id) {
970 vec2f pos_in_pic = VEC((point->x - objs[obj_id].pos.x) / SCALE,
971 (point->y - objs[obj_id].pos.y) / SCALE);
972 const struct palpic *p = spritemaps[objs[obj_id].spritemap_id];
973 unsigned h = palpic_getspriteheight(p), w = palpic_getspritewidth(p);
974 if(pos_in_pic.x < 0 || pos_in_pic.y < 0 || pos_in_pic.x > w || pos_in_pic.y > h) return 0;
975 assert(objs[obj_id].anim_curr != ANIM_STEP_INIT);
976 anim_step curranim = objs[obj_id].anim_curr;
977 if(curranim == ANIM_STEP_INIT) curranim = get_next_anim_frame(objs[obj_id].animid, ANIM_STEP_INIT);
978 uint8_t *data = palpic_getspritedata(p, curranim);
979 if(data[(unsigned) pos_in_pic.y * w + (unsigned) pos_in_pic.x] != 0) return 1;
980 return 0;
983 static enum animation_id get_die_anim(unsigned id) {
984 switch(objs[id].objtype) {
985 case OBJ_P1:
986 return ANIM_P1_DIE;
987 case OBJ_P2:
988 return ANIM_P2_DIE;
989 case OBJ_JEEP:
990 return ANIM_JEEP_DESTROYED;
991 case OBJ_TANK_SMALL:
992 return ANIM_TANK_SMALL_DESTROYED;
993 case OBJ_TANK_BIG:
994 return ANIM_TANK_BIG_DESTROYED;
995 case OBJ_TRANSPORTER:
996 return ANIM_TRANSPORTER_DESTROYED;
997 case OBJ_ENEMY_BOMBER:
998 return ANIM_ENEMY_BOMBER_DIE;
999 case OBJ_ENEMY_SHOOTER:
1000 return ANIM_ENEMY_GUNNER_DIE;
1001 case OBJ_BUNKER1: case OBJ_BUNKER2: case OBJ_BUNKER3:
1002 case OBJ_BUNKER4: case OBJ_BUNKER5:
1003 return ANIM_BUNKER_DESTROYED;
1004 case OBJ_GUNTURRET_MOVABLE_MACHINE:
1005 return ANIM_GUNTURRET_MOVABLE_MACHINE_DESTROYED;
1006 case OBJ_GUNTURRET_MOVABLE_MAN:
1007 return ANIM_GUNTURRET_MOVABLE_MAN_DESTROYED;
1008 default:
1009 return ANIM_INVALID;
1013 /* remove bullets that have reached their maximum number of steps */
1014 static int remove_bullets(sblist *list) {
1015 int res = 0;
1016 uint8_t *item_id;
1017 ssize_t li;
1018 sblist_iter_counter2s(list, li, item_id) {
1019 struct gameobj *bullet = &objs[*item_id];
1020 if(bullet->objspecific.bullet.step_curr >= bullet->objspecific.bullet.step_max) {
1021 gameobj_free(*item_id);
1022 sblist_delete(list, li);
1023 li--;
1024 } else {
1025 bullet->objspecific.bullet.step_curr++;
1027 res = 1;
1029 return res;
1032 static int remove_explosives(sblist *list) {
1033 int res = 0;
1034 uint8_t *item_id;
1035 ssize_t li;
1036 sblist_iter_counter2s(list, li, item_id) {
1037 struct gameobj *go = &objs[*item_id];
1038 if(go->objspecific.bullet.step_curr >= go->objspecific.bullet.step_max) {
1039 if(go->objtype == OBJ_GRENADE) init_grenade_explosion(&go->pos, list == &go_enemy_grenades);
1040 else init_rocket_explosion(&go->pos);
1041 gameobj_free(*item_id);
1042 sblist_delete(list, li);
1043 li--;
1044 } else {
1045 go->objspecific.bullet.step_curr++;
1046 if(go->objtype == OBJ_GRENADE) {
1047 if(go->objspecific.bullet.step_curr >= 32) go->animid = ANIM_GRENADE_SMALL;
1048 else if(go->objspecific.bullet.step_curr >= 8) go->animid = ANIM_GRENADE_BIG;
1051 res = 1;
1053 return res;
1056 static int remove_offscreen_objects(sblist *list) {
1057 int res = 0;
1058 uint8_t *item_id;
1059 ssize_t li;
1060 sblist_iter_counter2s(list, li, item_id) {
1061 assert(obj_slot_used[*item_id]);
1062 struct gameobj *go = &objs[*item_id];
1063 assert((int) go->spritemap_id < SI_MAX);
1064 const struct palpic *p = spritemaps[go->spritemap_id];
1065 assert(p->spritecount);
1066 int h = palpic_getspriteheight(p), w = palpic_getspritewidth(p);
1067 if(go->pos.x < SCREEN_MIN_X-w*SCALE || go->pos.x > SCREEN_MAX_X ||
1068 go->pos.y < SCREEN_MIN_Y-h*SCALE || go->pos.y > SCREEN_MAX_Y) {
1069 res = 1;
1070 dprintf(2, "offscreen: removed gameobj %d\n", (int) *item_id);
1071 gameobj_free(*item_id);
1072 sblist_delete(list, li);
1073 li--;
1076 return res;
1079 static int is_death_anim(enum animation_id anim) {
1080 return anim == ANIM_ENEMY_BOMBER_DIE || anim == ANIM_ENEMY_GUNNER_DIE ||
1081 anim == ANIM_ENEMY_BURNT || anim == ANIM_P1_DIE || anim == ANIM_P2_DIE;
1084 // removes bullets and other objects if they collide. return 1 if anything happened
1085 static int hit_bullets(sblist *bullet_list, sblist *target_list) {
1086 uint8_t *bullet_id;
1087 ssize_t li;
1088 int res = 0;
1089 enum bulletsubtype {
1090 BS_BULLET = 0,
1091 BS_FLAME = 1,
1092 BS_GRENADE_EXPL = 2,
1093 BS_BIG_EXPL = 3,
1094 } bullet_subtybe = BS_BULLET;
1096 sblist_iter_counter2s(bullet_list, li, bullet_id) {
1097 struct gameobj *bullet = &objs[*bullet_id];
1098 if(bullet->objtype == OBJ_FLAME) bullet_subtybe = BS_FLAME;
1099 else if(bullet->objtype == OBJ_GRENADE_EXPLOSION) {
1100 /* grenade kills only in the explosion, not in the smoke phase */
1101 if(bullet->objspecific.bullet.step_curr > 22) continue;
1102 bullet_subtybe = BS_GRENADE_EXPL;
1103 } else if(bullet->objtype == OBJ_BIG_EXPLOSION) {
1104 bullet_subtybe = BS_BIG_EXPL;
1107 vec2f bullet_center = get_gameobj_center(*bullet_id);
1108 if(bullet_list == &go_player_bullets || bullet_list == &go_flames) {
1109 if(is_wall(&bullet_center) == WT_SOLID) goto remove_bullet;
1112 const float bullet_radius[] = { [BS_BULLET] = 1.f, [BS_FLAME] = 6.f,
1113 [BS_GRENADE_EXPL] = 16.f, [BS_BIG_EXPL] = 19.f };
1115 size_t lj;
1116 uint8_t *target_id;
1117 sblist_iter_counter2(target_list, lj, target_id) {
1118 struct gameobj *target = &objs[*target_id];
1119 if(is_death_anim(target->animid)) continue;
1120 vec2f temp = get_gameobj_center(*target_id);
1121 float dist1 = vecdist(&bullet_center, &temp) - bullet_radius[bullet_subtybe] * SCALE;
1122 vec2f newpos = vecadd(&bullet_center, &bullet->vel);
1123 float dist2 = vecdist(&newpos, &temp) - bullet_radius[bullet_subtybe] * SCALE;
1125 unsigned w = palpic_getspritewidth(spritemaps[target->spritemap_id]),
1126 h = palpic_getspriteheight(spritemaps[target->spritemap_id]);
1127 float longest_side_div2 = ((float) MAX(h, w) / 2) * SCALE;
1128 if(dist1 < 1.f*SCALE || dist2 < 1.f*SCALE) { dprintf(2, "hit1\n"); goto hit; }
1129 if(dist1 < longest_side_div2 || dist2 < longest_side_div2) {
1130 vec2f velquarter = VEC(bullet->vel.x * 0.25, bullet->vel.y * 0.25);
1131 vec2f point = bullet_center;
1132 size_t k;
1133 for(k = 0; k < 4; k++) {
1134 if(point_in_mask(&point, *target_id)) {
1135 hit:
1137 if(bullet_list == &go_player_bullets) {
1138 if(target_list == &go_vehicles) {
1139 audio_play_wave_resource(wavesounds[WS_DROPSHOT]);
1140 goto remove_bullet;
1142 objs[player_ids[0]].objspecific.playerdata.score += 50;
1143 } else if (bullet_list == &go_rockets) {
1144 init_rocket_explosion(&target->pos);
1145 goto remove_bullet;
1146 } else if(bullet->objtype == OBJ_GRENADE_EXPLOSION) {
1147 // grenade explosion has no effect on vehicles and bunkers.
1148 if(target_list == &go_vehicles || target_list == &go_bunkers)
1149 goto next_bullet;
1151 enum animation_id death_anim = bullet_subtybe == BS_FLAME ? ANIM_ENEMY_BURNT : get_die_anim(*target_id);
1152 if(death_anim == ANIM_INVALID) {
1153 gameobj_free(*target_id);
1154 sblist_delete(target_list, lj);
1155 lj--;
1156 goto next_target;
1158 switch_anim(*target_id, death_anim);
1159 target->vel = VEC(0,0);
1160 if(target->objtype == OBJ_ENEMY_BOMBER || target->objtype == OBJ_ENEMY_SHOOTER) {
1161 const enum wavesound_id wid[] = { WS_SCREAM, WS_SCREAM2 };
1162 audio_play_wave_resource(wavesounds[wid[rand()%2]]);
1163 if(bullet_subtybe == BS_BULLET) {
1164 vec2f bloodpos = vecadd(&target->pos, &VEC(0, (2+rand()%7)*SCALE));
1165 /* original displays at 0,8 but i prefer a random effect */
1166 //vec2f bloodpos = vecadd(&target->pos, &VEC(0, 8*SCALE));
1167 int id = init_blood(&bloodpos);
1168 if(id != -1) add_blood(id);
1171 if(bullet_subtybe == BS_BULLET) {
1172 remove_bullet:
1173 gameobj_free(*bullet_id);
1174 sblist_delete(bullet_list, li);
1175 li--;
1176 goto next_bullet;
1177 } else break;
1179 point = vecadd(&point, &velquarter);
1182 next_target:;
1184 next_bullet:
1185 res = 1;
1187 return res;
1190 uint32_t tickcounter;
1192 static int advance_animations(void) {
1193 size_t i, obj_visited;
1194 int res = 0;
1195 for(i = 0, obj_visited = 0; obj_visited < obj_count && i < OBJ_MAX; i++) {
1196 if(!obj_slot_used[i]) continue;
1197 struct gameobj *go = &objs[i];
1198 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)) {
1199 unsigned anim_delay = go->objtype == OBJ_BIG_EXPLOSION ? 8 : 4;
1200 if(go->anim_curr == ANIM_STEP_INIT || tickcounter % anim_delay == go->anim_frame) {
1201 anim_step last_anim = go->anim_curr;
1202 go->anim_curr = get_next_anim_frame(go->animid, go->anim_curr);
1203 if(last_anim != go->anim_curr) res = 1;
1206 obj_visited++;
1208 return res;
1211 static void switch_enemy_shape(int objid, const struct enemy_route* r) {
1212 switch_anim(objid, enemy_animation_lut[r->shape]);
1215 static void draw_golist(sblist *list) {
1216 size_t i;
1217 uint8_t *itemid;
1218 sblist_iter_counter2(list, i, itemid) {
1219 assert(obj_slot_used[*itemid]);
1220 struct gameobj *o = &objs[*itemid];
1221 const prgb *palette;
1222 switch(o->objtype) {
1223 case OBJ_BLOOD:
1224 // original blood color is bb5511 but that is hardly visible
1225 // palette = (prgb[]) {PRGB(0,0,0), PRGB(0xbb, 0x55, 0x11)};
1226 palette = (const prgb[]) {PRGB(0,0,0), PRGB(0xff, 0x0, 0x0)};
1227 break;
1228 case OBJ_ENEMY_BOMBER: case OBJ_ENEMY_SHOOTER:
1229 palette = map->enemy_palette;
1230 break;
1231 default:
1232 palette = 0;
1234 blit_sprite(o->pos.x, o->pos.y, &video,
1235 SCALE, spritemaps[o->spritemap_id],
1236 o->anim_curr == ANIM_STEP_INIT ? get_next_anim_frame(o->animid, o->anim_curr) : o->anim_curr,
1237 palette);
1241 static void draw_gameobjs(void) {
1242 draw_golist(&go_mines);
1243 draw_golist(&go_turrets);
1244 draw_golist(&go_bunkers);
1245 draw_golist(&go_vehicles);
1246 draw_golist(&go_enemies);
1247 draw_golist(&go_blood);
1248 draw_golist(&go_enemy_bullets);
1249 draw_golist(&go_boss);
1250 draw_golist(&go_rockets);
1251 draw_golist(&go_players);
1252 draw_golist(&go_player_bullets);
1253 draw_golist(&go_enemy_explosions);
1254 draw_golist(&go_flames);
1255 draw_golist(&go_explosions);
1256 draw_golist(&go_grenades);
1257 draw_golist(&go_enemy_grenades);
1258 draw_golist(&go_crosshair);
1259 draw_golist(&go_muzzleflash);
1262 static void process_soldiers(void) {
1263 uint8_t *itemid;
1264 sblist_iter(&go_enemies, itemid) {
1265 struct gameobj *go = &objs[*itemid];
1266 assert(go->objtype == OBJ_ENEMY_SHOOTER || go->objtype == OBJ_ENEMY_BOMBER);
1267 if (tickcounter % 4 == go->anim_frame) {
1268 const struct enemy_route *rc = get_enemy_current_route(go->objspecific.enemy.curr_step, go->objspecific.enemy.spawn);
1269 if(rc->vel) {
1270 go->objspecific.enemy.curr_step++;
1271 if(enemy_fires(&go->objspecific.enemy)) {
1272 enemy_fire_bullet(*itemid, DIR16_INVALID, 41);
1274 const struct enemy_route *rn = get_enemy_current_route(go->objspecific.enemy.curr_step, go->objspecific.enemy.spawn);
1275 if(rn->shape != rc->shape) switch_enemy_shape(*itemid, rn);
1278 if(!is_death_anim(go->animid)) go->vel = get_enemy_vel(go->objspecific.enemy.curr_step, go->objspecific.enemy.spawn);
1279 else go->vel = VEC(0, 0);
1283 static int process_turrets(void) {
1284 int res = 0;
1285 uint8_t *itemid;
1286 sblist_iter(&go_turrets, itemid) {
1287 struct gameobj *go = &objs[*itemid];
1288 enum direction16 dir = DIR16_S;
1289 switch(go->objtype) {
1290 case OBJ_GUNTURRET_FIXED_NORTH:
1291 dir = DIR16_N;
1292 case OBJ_GUNTURRET_FIXED_SOUTH:
1293 if(rand()%8 == 0) {
1294 enemy_fire_bullet(*itemid, dir, 92);
1295 res = 1;
1297 break;
1298 default:;
1301 return res;
1304 static int move_gameobjs(void) {
1305 int res = 0;
1306 size_t i, obj_visited;
1307 for(i = 0, obj_visited = 0; obj_visited < obj_count && i < OBJ_MAX; i++) {
1308 if(obj_slot_used[i]) {
1309 struct gameobj *go = &objs[i];
1310 obj_visited++;
1311 if(go->anim_curr == ANIM_STEP_INIT) res = 1;
1312 if(go->vel.x != 0 || go->vel.y != 0) {
1313 vec2f oldpos = go->pos;
1314 go->pos.x += go->vel.x;
1315 go->pos.y += go->vel.y;
1317 if(go->objtype == OBJ_P1 || go->objtype == OBJ_P2) {
1318 if(go->pos.y < SCREEN_MIN_Y) go->pos.y = SCREEN_MIN_Y;
1319 else if(go->pos.y+25*SCALE > SCREEN_MAX_Y) go->pos.y = SCREEN_MAX_Y-25*SCALE;
1320 if(go->pos.x < SCREEN_MIN_X) go->pos.x = SCREEN_MIN_X;
1321 else if(go->pos.x+32*SCALE > SCREEN_MAX_X) go->pos.x = SCREEN_MAX_X-32*SCALE;
1322 vec2f center = get_sprite_center(spritemaps[go->spritemap_id]);
1323 center = vecadd(&center, &go->pos);
1324 if(is_wall(&center)) {
1325 go->pos = oldpos;
1326 go->vel = VEC(0,0);
1330 res = 1;
1332 if((go->objtype == OBJ_ENEMY_BOMBER || go->objtype == OBJ_ENEMY_SHOOTER) &&
1333 go->anim_curr == animations[go->animid].last &&
1334 (go->animid == ANIM_ENEMY_BOMBER_DIE ||
1335 go->animid == ANIM_ENEMY_GUNNER_DIE ||
1336 go->animid == ANIM_ENEMY_BURNT)) {
1337 dprintf(2, "removed enemy from %.2f,%.2f\n", go->pos.x, go->pos.y);
1338 gameobj_free(i);
1339 golist_remove(&go_enemies, i);
1340 res = 1;
1341 continue;
1346 return res;
1349 static void game_update_caption(void) {
1350 char buf [128];
1351 snprintf(buf, 128, "objs: %d, map x,y %d/%d, index %d, xoff %d, yoff %d, spawnscreen %d, line %d", (int) obj_count,
1352 (int)mapsquare.x, (int)mapsquare.y, (int)map->screen_map[mapsquare.y][mapsquare.x],
1353 (int)mapscreen_xoff, (int)mapscreen_yoff, (int)map_spawn_screen_index, (int) map_spawn_line);
1354 SDL_WM_SetCaption(buf, 0);
1356 static void(*update_caption)(void) = game_update_caption;
1358 static void game_tick(int force_redraw) {
1359 int need_redraw = force_redraw;
1360 const int fps = 64;
1361 if(mousebutton_down[MB_LEFT] > 1) {
1362 const int player_no = 0;
1363 const struct weapon *pw = get_active_weapon(player_no);
1364 //if(get_active_weapon_id(player_no) == WP_M134) __asm__("int3");
1365 if (pw->flags & WF_AUTOMATIC) {
1366 float shots_per_second = pw->rpm / 60.f;
1367 float shotinterval = fps / shots_per_second;
1368 if((int)((float)mousebutton_down[MB_LEFT] / shotinterval) != (int)((float)(mousebutton_down[MB_LEFT]-1) / shotinterval))
1369 fire_bullet(player_no);
1373 if(advance_animations()) need_redraw = 1;
1374 if(hit_bullets(&go_player_bullets, &go_enemies)) need_redraw = 1;
1375 if(hit_bullets(&go_player_bullets, &go_vehicles)) need_redraw = 1;
1376 if(hit_bullets(&go_flames, &go_enemies)) need_redraw = 1;
1377 if(hit_bullets(&go_rockets, &go_enemies)) need_redraw = 1;
1378 if(hit_bullets(&go_rockets, &go_vehicles)) need_redraw = 1;
1379 if(hit_bullets(&go_explosions, &go_enemies)) need_redraw = 1;
1380 if(hit_bullets(&go_explosions, &go_vehicles)) need_redraw = 1;
1381 if(hit_bullets(&go_explosions, &go_mines)) need_redraw = 1;
1382 if(hit_bullets(&go_explosions, &go_turrets)) need_redraw = 1;
1383 if(hit_bullets(&go_explosions, &go_bunkers)) need_redraw = 1;
1384 if(hit_bullets(&go_explosions, &go_players)) need_redraw = 1;
1385 if(hit_bullets(&go_enemy_explosions, &go_players)) need_redraw = 1;
1386 if(hit_bullets(&go_enemy_bullets, &go_players)) need_redraw = 1;
1387 if(remove_bullets(&go_player_bullets)) need_redraw = 1;
1388 if(remove_bullets(&go_flames)) need_redraw = 1;
1389 if(remove_bullets(&go_explosions)) need_redraw = 1;
1390 if(remove_bullets(&go_enemy_explosions)) need_redraw = 1;
1391 if(remove_bullets(&go_enemy_bullets)) need_redraw = 1;
1392 if(remove_bullets(&go_muzzleflash)) need_redraw = 1;
1393 if(remove_bullets(&go_blood)) need_redraw = 1;
1394 if(remove_explosives(&go_grenades)) need_redraw = 1;
1395 if(remove_explosives(&go_enemy_grenades)) need_redraw = 1;
1396 if(remove_explosives(&go_rockets)) need_redraw = 1;
1397 if(tickcounter % 2 == 0 && scroll_map()) need_redraw = 1;
1399 process_soldiers();
1400 if(tickcounter % 4 == 0 && process_turrets()) need_redraw = 1;
1402 if(move_gameobjs()) need_redraw = 1;
1404 if(remove_offscreen_objects(&go_enemies)) need_redraw = 1;
1405 if(remove_offscreen_objects(&go_vehicles)) need_redraw = 1;
1406 if(remove_offscreen_objects(&go_mines)) need_redraw = 1;
1407 if(remove_offscreen_objects(&go_turrets)) need_redraw = 1;
1408 if(remove_offscreen_objects(&go_bunkers)) need_redraw = 1;
1410 long ms_used = 0;
1411 struct timeval timer;
1412 gettimestamp(&timer);
1413 if(need_redraw) {
1414 draw_map();
1415 draw_gameobjs();
1416 draw_status_bar();
1417 video_update_region(SCREEN_MIN_X ,SCREEN_MIN_Y , SCREEN_MAX_X - SCREEN_MIN_X, VMODE_H);
1420 ms_used = mspassed(&timer);
1421 //if(ms_used) printf("repaint took: ms_used %ld\n", ms_used);
1422 int res = audio_process();
1423 if(res == -1) music_restart();
1424 ms_used = mspassed(&timer);
1425 //if(ms_used) printf("audio processed: %d, ms_used %ld\n", res, ms_used);
1427 long sleepms = 1000/fps - ms_used;
1428 if(sleepms >= 0) SDL_Delay(sleepms);
1429 if(mousebutton_down[MB_LEFT]) mousebutton_down[MB_LEFT]++;
1431 tickcounter++;
1432 update_caption();
1435 enum cursor {
1436 c_down = 0,
1437 c_up,
1438 c_left,
1439 c_right,
1442 static enum cursor cursor_lut[] = {
1443 [SDLK_UP] = c_up,
1444 [SDLK_DOWN] = c_down,
1445 [SDLK_LEFT] = c_left,
1446 [SDLK_RIGHT] = c_right,
1447 [SDLK_w] = c_up,
1448 [SDLK_a] = c_left,
1449 [SDLK_s] = c_down,
1450 [SDLK_d] = c_right,
1453 static char cursors_pressed[] = {
1454 [c_up] = 0,
1455 [c_down] = 0,
1456 [c_left] = 0,
1457 [c_right] = 0,
1460 static vec2f get_vel_from_direction(enum direction dir, float speed) {
1461 #define VELLUT(a, b, c) [a] = VEC(b, c)
1462 static const vec2f vel_lut[] = {
1463 VELLUT(DIR_O, 1, 0),
1464 VELLUT(DIR_NO, 0.7071067769704655, -0.7071067769704655),
1465 VELLUT(DIR_N, 0, -1),
1466 VELLUT(DIR_NW, -0.7071067769704655, -0.7071067769704655),
1467 VELLUT(DIR_W, -1, 0),
1468 VELLUT(DIR_SW, -0.7071067769704655, 0.7071067769704655),
1469 VELLUT(DIR_S, 0, 1),
1470 VELLUT(DIR_SO, 0.7071067769704655, 0.7071067769704655),
1472 #undef VELLUT
1473 vec2f v = vel_lut[dir];
1474 v.x *= speed * SCALE;
1475 v.y *= speed * SCALE;
1476 return v;
1479 static vec2f get_vel_from_direction16(enum direction16 dir, float speed) {
1480 #define VELLUT(a, b, c) [a] = VEC(b, c)
1481 #define ANK90 0.7071067769704655
1482 #define GK90 ANK90
1483 #define ANK45 0.9238795042037964
1484 #define GK45 0.3826834261417389
1485 static const vec2f vel_lut[] = {
1486 VELLUT(DIR16_O, 1, 0),
1487 VELLUT(DIR16_ONO, ANK45, -GK45),
1488 VELLUT(DIR16_NO, ANK90, -ANK90),
1489 VELLUT(DIR16_NNO, GK45, -ANK45),
1490 VELLUT(DIR16_N, 0, -1),
1491 VELLUT(DIR16_NNW, -GK45, -ANK45),
1492 VELLUT(DIR16_NW, -ANK90, -ANK90),
1493 VELLUT(DIR16_WNW, -ANK45, -GK45),
1494 VELLUT(DIR16_W, -1, 0),
1495 VELLUT(DIR16_WSW, -ANK45, GK45),
1496 VELLUT(DIR16_SW, -ANK90, ANK90),
1497 VELLUT(DIR16_SSW, -GK45, ANK45),
1498 VELLUT(DIR16_S, 0, 1),
1499 VELLUT(DIR16_SSO, GK45, ANK45),
1500 VELLUT(DIR16_SO, ANK90, ANK90),
1501 VELLUT(DIR16_OSO, ANK45, GK45),
1503 #undef VELLUT
1504 vec2f v = vel_lut[dir];
1505 v.x *= speed * SCALE;
1506 v.y *= speed * SCALE;
1507 return v;
1511 static enum direction get_direction_from_vec(vec2f *vel) {
1512 float deg_org, deg = atan2(vel->y, vel->x);
1513 deg_org = deg;
1514 if(deg < 0) deg *= -1.f;
1515 else if(deg > 0) deg = M_PI + (M_PI - deg); // normalize atan2 result to scale from 0 to 2 pi
1516 int hexadrant = (int)(deg / ((1.0/16.0f)*2*M_PI));
1517 assert(hexadrant >= 0 && hexadrant < 16);
1518 static const enum direction rad_lut[] = {
1519 DIR_O,
1520 DIR_NO, DIR_NO,
1521 DIR_N, DIR_N,
1522 DIR_NW, DIR_NW,
1523 DIR_W, DIR_W,
1524 DIR_SW, DIR_SW,
1525 DIR_S, DIR_S,
1526 DIR_SO, DIR_SO,
1527 DIR_O
1529 return rad_lut[hexadrant];
1532 static enum direction get_direction_from_cursor(void) {
1533 enum direction dir = DIR_INVALID;
1534 if(cursors_pressed[c_up]) {
1535 if(cursors_pressed[c_left]) dir = DIR_NW;
1536 else if(cursors_pressed[c_right]) dir = DIR_NO;
1537 else dir = DIR_N;
1538 } else if (cursors_pressed[c_down]) {
1539 if(cursors_pressed[c_left]) dir = DIR_SW;
1540 else if(cursors_pressed[c_right]) dir = DIR_SO;
1541 else dir = DIR_S;
1542 } else if (cursors_pressed[c_left]) {
1543 dir = DIR_W;
1544 } else if (cursors_pressed[c_right]) {
1545 dir = DIR_O;
1547 return dir;
1550 static enum animation_id get_anim_from_direction(enum direction dir, int player_no, int throwing) {
1551 #define DIRMAP(a, b) [a] = b
1552 if(!throwing) {
1553 static const enum animation_id dir_map_p1[] = {
1554 DIRMAP(DIR_N, ANIM_P1_MOVE_N),
1555 DIRMAP(DIR_NW, ANIM_P1_MOVE_NW),
1556 DIRMAP(DIR_W, ANIM_P1_MOVE_W),
1557 DIRMAP(DIR_SW, ANIM_P1_MOVE_SW),
1558 DIRMAP(DIR_S, ANIM_P1_MOVE_S),
1559 DIRMAP(DIR_SO, ANIM_P1_MOVE_SO),
1560 DIRMAP(DIR_O, ANIM_P1_MOVE_O),
1561 DIRMAP(DIR_NO, ANIM_P1_MOVE_NO),
1563 static const enum animation_id dir_map_p2[] = {
1564 DIRMAP(DIR_N, ANIM_P2_MOVE_N),
1565 DIRMAP(DIR_NW, ANIM_P2_MOVE_NW),
1566 DIRMAP(DIR_W, ANIM_P2_MOVE_W),
1567 DIRMAP(DIR_SW, ANIM_P2_MOVE_SW),
1568 DIRMAP(DIR_S, ANIM_P2_MOVE_S),
1569 DIRMAP(DIR_SO, ANIM_P2_MOVE_SO),
1570 DIRMAP(DIR_O, ANIM_P2_MOVE_O),
1571 DIRMAP(DIR_NO, ANIM_P2_MOVE_NO),
1573 const enum animation_id *dir_map = player_no == 0 ? dir_map_p1 : dir_map_p2;
1574 return dir_map[dir];
1575 } else {
1576 static const enum animation_id dir_map_p1_g[] = {
1577 DIRMAP(DIR_N, ANIM_P1_THROW_N),
1578 DIRMAP(DIR_NW, ANIM_P1_THROW_NW),
1579 DIRMAP(DIR_W, ANIM_P1_THROW_W),
1580 DIRMAP(DIR_SW, ANIM_P1_THROW_SW),
1581 DIRMAP(DIR_S, ANIM_P1_THROW_S),
1582 DIRMAP(DIR_SO, ANIM_P1_THROW_SO),
1583 DIRMAP(DIR_O, ANIM_P1_THROW_O),
1584 DIRMAP(DIR_NO, ANIM_P1_THROW_NO),
1586 static const enum animation_id dir_map_p2_g[] = {
1587 DIRMAP(DIR_N, ANIM_P2_THROW_N),
1588 DIRMAP(DIR_NW, ANIM_P2_THROW_NW),
1589 DIRMAP(DIR_W, ANIM_P2_THROW_W),
1590 DIRMAP(DIR_SW, ANIM_P2_THROW_SW),
1591 DIRMAP(DIR_S, ANIM_P2_THROW_S),
1592 DIRMAP(DIR_SO, ANIM_P2_THROW_SO),
1593 DIRMAP(DIR_O, ANIM_P2_THROW_O),
1594 DIRMAP(DIR_NO, ANIM_P2_THROW_NO),
1596 const enum animation_id *dir_map = player_no == 0 ? dir_map_p1_g : dir_map_p2_g;
1597 return dir_map[dir];
1599 #undef DIRMAP
1602 #if 0
1603 static enum animation_id get_anim_from_cursor(void) {
1604 enum direction dir = get_direction_from_cursor();
1605 if(dir == DIR_INVALID) return ANIM_INVALID;
1606 return get_anim_from_direction(dir, 0, 0);
1608 /* playerno is either 0 or 1, not player_id! */
1609 static enum animation_id get_anim_from_vel(int player_no, vec2f *vel) {
1610 enum direction dir = get_direction_from_vec(vel);
1611 if(dir == DIR_INVALID) return ANIM_INVALID;
1612 return get_anim_from_direction(dir, player_no, 0);
1614 #endif
1616 static void switch_anim(int obj_id, int aid) {
1617 if(objs[obj_id].animid == aid) return;
1618 gameobj_start_anim(obj_id, aid);
1621 enum map_index choose_mission(void);
1622 //RcB: DEP "mission_select.c"
1623 #include "enemytag.c"
1624 int main() {
1625 video_init();
1626 clear_screen();
1627 //SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
1628 SDL_EnableKeyRepeat(100, 20);
1630 audio_init();
1632 /* background music for mission selection screen */
1633 music_play(TUNE_MAP);
1635 if((current_map = choose_mission()) == MI_INVALID) goto dun_goofed;
1637 music_play(TUNE_FIGHTING);
1639 SDL_ShowCursor(0);
1642 int startx = 10;
1643 int starty = 10;
1645 //redraw(surface, startx, starty);
1646 const float player_speed = 1.25f;
1647 const struct palpic* spritemap = spritemaps[SI_PLAYERS];
1648 struct { int *target; int dir; int max;} moves[] = {
1649 [c_up] = {&starty, SCALE * -1, VMODE_H - (palpic_getspriteheight(spritemap) * SCALE)},
1650 [c_down] = {&starty, SCALE, VMODE_H - (palpic_getspriteheight(spritemap) * SCALE)},
1651 [c_left] = {&startx, SCALE * -1, VMODE_W - (palpic_getspritewidth(spritemap) * SCALE)},
1652 [c_right] = {&startx, SCALE, VMODE_W - (palpic_getspritewidth(spritemap) * SCALE)},
1655 init_game_objs();
1656 int player_no = 0;
1657 int player_id = player_ids[player_no];
1659 game_tick(1);
1661 SDL_Event sdl_event;
1662 while(1) {
1663 unsigned need_redraw = 0;
1664 int weapon_inc = 0;
1665 while (SDL_PollEvent(&sdl_event)) {
1666 need_redraw = 0;
1667 switch (sdl_event.type) {
1668 case SDL_MOUSEMOTION:
1669 if((int)mousepos->x != sdl_event.motion.x || (int)mousepos->y != sdl_event.motion.y)
1670 need_redraw = 1;
1671 mousepos->x = sdl_event.motion.x;
1672 mousepos->y = sdl_event.motion.y;
1673 break;
1674 case SDL_MOUSEBUTTONDOWN:
1675 mousepos->x = sdl_event.button.x;
1676 mousepos->y = sdl_event.button.y;
1677 mousebutton_down[MB_LEFT] = 1;
1678 fire_bullet(player_no);
1679 break;
1680 case SDL_MOUSEBUTTONUP:
1681 mousepos->x = sdl_event.button.x;
1682 mousepos->y = sdl_event.button.y;
1683 mousebutton_down[MB_LEFT] = 0;
1684 break;
1685 case SDL_QUIT:
1686 dun_goofed:
1687 video_cleanup();
1688 return 0;
1689 case SDL_KEYDOWN:
1690 switch(sdl_event.key.keysym.sym) {
1691 case SDLK_w: case SDLK_a: case SDLK_s: case SDLK_d:
1692 case SDLK_UP:
1693 case SDLK_DOWN:
1694 case SDLK_RIGHT:
1695 case SDLK_LEFT:
1696 cursors_pressed[cursor_lut[sdl_event.key.keysym.sym]] = 1;
1697 check_anim: {
1698 enum direction dir = get_direction_from_cursor();
1699 if(dir != DIR_INVALID) {
1700 if(!mousebutton_down[MB_LEFT]) {
1701 // change animation according to cursors,
1702 // unless we're in automatic fire mode.
1703 enum animation_id aid = get_anim_from_direction(dir, player_no, 0);
1704 if(aid != ANIM_INVALID) switch_anim(player_id, aid);
1706 objs[player_id].vel = get_vel_from_direction(dir, player_speed);
1707 } else {
1708 objs[player_id].vel = VEC(0,0);
1711 break;
1712 case SDLK_RETURN:
1713 if((sdl_event.key.keysym.mod & KMOD_LALT) ||
1714 (sdl_event.key.keysym.mod & KMOD_RALT)) {
1715 toggle_fullscreen();
1716 SDL_Delay(1);
1717 game_tick(1);
1718 need_redraw = 1;
1720 break;
1721 case SDLK_KP_PLUS:
1722 weapon_inc = 1;
1723 goto toggle_weapon;
1724 case SDLK_KP_MINUS:
1725 if((int)player_weapons[player_no][0] == 0)
1726 weapon_inc = WP_MAX-1;
1727 else weapon_inc = -1;
1728 toggle_weapon:
1729 player_weapons[player_no][0] += weapon_inc;
1730 if(player_weapons[player_no][0] == WP_INVALID)
1731 player_weapons[player_no][0] = 0;
1732 printf("%s\n", weapon_name(player_weapons[player_no][0]));
1733 need_redraw = 1;
1734 break;
1735 default:
1736 break;
1738 break;
1739 case SDL_KEYUP:
1740 switch(sdl_event.key.keysym.sym) {
1741 case SDLK_e:
1742 enemy_tag_loop();
1743 update_caption = game_update_caption;
1744 break;
1745 case SDLK_c:
1746 clear_screen();
1747 video_update();
1748 need_redraw = 1;
1749 break;
1750 case SDLK_w: case SDLK_a: case SDLK_s: case SDLK_d:
1751 case SDLK_UP:
1752 case SDLK_DOWN:
1753 case SDLK_RIGHT:
1754 case SDLK_LEFT:
1755 cursors_pressed[cursor_lut[sdl_event.key.keysym.sym]] = 0;
1756 goto check_anim;
1757 case SDLK_ESCAPE:
1758 goto dun_goofed;
1759 default:
1760 break;
1762 default:
1763 break;
1766 unsigned i;
1767 for (i = 0; i < ARRAY_SIZE(cursors_pressed); i++) {
1768 if(cursors_pressed[i]) {
1769 *moves[i].target += moves[i].dir;
1770 if(*moves[i].target < 0) *moves[i].target = 0;
1771 if(*moves[i].target > moves[i].max) *moves[i].target = moves[i].max;
1772 need_redraw = 1;
1775 game_tick(need_redraw);
1778 return 0;