├── .gitattributes ├── .gitignore ├── LEVEL.c ├── README.md ├── TILES.C ├── lmdave.c └── lmdave.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /LEVEL.c: -------------------------------------------------------------------------------- 1 | /* Exracts level information from the Dangerous Dave binary 2 | * Data is stored in levelx.dat 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /* Level format structure */ 11 | struct dave_level { 12 | uint8_t path[256]; 13 | uint8_t tiles[1000]; 14 | uint8_t padding[24]; 15 | }; 16 | 17 | int main(int argc, char* argv[]) 18 | { 19 | const uint32_t level_addr = 0x26e0a; 20 | 21 | /* Allocate space for 10 game levels */ 22 | struct dave_level level[10]; 23 | 24 | /* Open EXE file and go to level data */ 25 | FILE *fin; 26 | fin = fopen("DAVE.EXE","rb"); 27 | fseek (fin, level_addr, SEEK_SET); 28 | 29 | /* Stream level data to memory and output files */ 30 | FILE *fout; 31 | char file_num[4]; 32 | char fname[12]; 33 | uint32_t i,j,k; 34 | 35 | for (j=0; j<10; j++) 36 | { 37 | /* Make new file */ 38 | fname[0]='\0'; 39 | strcat(fname, "level"); 40 | sprintf(&file_num[0], "%u", j); 41 | strcat(fname, file_num); 42 | strcat(fname, ".dat"); 43 | 44 | fout = fopen(fname, "wb"); 45 | 46 | /* Stream path data */ 47 | for (i=0; i< sizeof(level[j].path); i++) 48 | { 49 | level[j].path[i] = fgetc(fin); 50 | fputc(level[j].path[i], fout); 51 | } 52 | 53 | /* Stream tile indices */ 54 | for (i=0; i< sizeof(level[j].tiles); i++) 55 | { 56 | level[j].tiles[i] = fgetc(fin); 57 | fputc(level[j].tiles[i], fout); 58 | } 59 | 60 | /* Stream padding */ 61 | for (i=0; i< sizeof(level[j].padding); i++) 62 | { 63 | level[j].padding[i] = fgetc(fin); 64 | fputc(level[j].padding[i], fout); 65 | } 66 | 67 | printf("Saving %s as level data", fname); 68 | fclose(fout); 69 | } 70 | fclose(fin); 71 | 72 | /* Load tileset */ 73 | SDL_Surface *tiles[158]; 74 | for (i=0; i<158; i++) 75 | { 76 | fname[0]='\0'; 77 | strcat(fname, "tile"); 78 | sprintf(&file_num[0], "%u", i); 79 | strcat(fname, file_num); 80 | strcat(fname, ".bmp"); 81 | 82 | tiles[i] = SDL_LoadBMP(fname); 83 | } 84 | 85 | /* Create map of the world by level, row, and column */ 86 | SDL_Surface *map; 87 | SDL_Rect dest; 88 | uint8_t tile_index; 89 | map = SDL_CreateRGBSurface(0, 1600, 1600, 32, 0, 0, 0, 0); 90 | for (k=0; k<10; k++) 91 | { 92 | for (j=0; j<10; j++) 93 | { 94 | for (i=0; i<100; i++) 95 | { 96 | tile_index = level[k].tiles[j*100+i]; 97 | dest.x = i*16; 98 | dest.y = k*160+j*16; 99 | dest.w = 16; 100 | dest.h = 16; 101 | SDL_BlitSurface(tiles[tile_index], NULL, map, &dest); 102 | } 103 | } 104 | } 105 | SDL_SaveBMP(map, "map.bmp"); 106 | 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Let's Make: Dangerous Dave 2 | Project recreating this classic game using C and SDL. This code is here as-is from a 10-part video series. Code commented afterwards 3 | 4 | Video series: https://www.youtube.com/playlist?list=PLSkJey49cOgTSj465v2KbLZ7LMn10bCF9 5 | 6 | Project Website: http://www.maizure.org/projects/lets-make-dangerous-dave 7 | 8 | ## Build Notes 9 | 10 | #### You must have SDL2 installed on your system and know how to include/link/compile. 11 | i.e. gcc -I/usr/include/SDL2 lmdave.c -Wall -lSDL2 -o lmdave 12 | 13 | #### You must have access to the original Dangerous Dave binary and extract the assets yourself 14 | Use the included TILES.c and LEVEL.c 15 | -------------------------------------------------------------------------------- /TILES.C: -------------------------------------------------------------------------------- 1 | /* Extract VGA tileset from the uncompressed Dangerous Dave binary. 2 | * Each tile will be output as its own bitmap in the format tilexxx.bmp 3 | */ 4 | 5 | #include /* Opening Files */ 6 | #include /* Manipulating Strings */ 7 | #include /* Fixed-width data types (C99) */ 8 | #include 9 | #include /* Using SDL data structure */ 10 | 11 | int main(int argc, char* argv[]) 12 | { 13 | const uint32_t vga_data_addr = 0x120f0; 14 | const uint32_t vga_pal_addr = 0x26b0a; 15 | 16 | /* Open EXE File and go to VGA pixel data*/ 17 | FILE *fin; 18 | fin = fopen("DAVE.EXE","rb"); 19 | fseek (fin, vga_data_addr, SEEK_SET); 20 | 21 | /* Undo RLE and read all pixel data*/ 22 | /* Read file length - first 4 bytes LE */ 23 | uint32_t final_length; 24 | final_length = 0; 25 | 26 | final_length |= fgetc(fin); 27 | final_length |= fgetc(fin)<<8; 28 | final_length |= fgetc(fin)<<16; 29 | final_length |= fgetc(fin)<<24; 30 | 31 | /* Read each byte and un-encode */ 32 | uint32_t current_length; 33 | unsigned char out_data[150000]; 34 | uint8_t byte_buffer; 35 | 36 | current_length = 0; 37 | memset(&out_data, 0, sizeof(out_data)); 38 | 39 | while (current_length < final_length) 40 | { 41 | byte_buffer=fgetc(fin); 42 | if (byte_buffer & 0x80) 43 | { 44 | byte_buffer &= 0x7F; 45 | byte_buffer++; 46 | while (byte_buffer) 47 | { 48 | out_data[current_length++] = fgetc(fin); 49 | byte_buffer--; 50 | } 51 | } 52 | else 53 | { 54 | byte_buffer+=3; 55 | char next = fgetc(fin); 56 | while (byte_buffer) 57 | { 58 | out_data[current_length++] = next; 59 | byte_buffer--; 60 | } 61 | } 62 | } 63 | 64 | /* Read in VGA Palette. 256-color of 3 bytes (RGB) */ 65 | fseek (fin, vga_pal_addr, SEEK_SET); 66 | uint8_t palette[768]; 67 | uint32_t i,j; 68 | for (i=0;i<256;i++) 69 | { 70 | for (j=0;j<3;j++) 71 | { 72 | palette[i*3+j]=fgetc(fin); 73 | palette[i*3+j] <<= 2; 74 | } 75 | } 76 | 77 | fclose(fin); 78 | 79 | /* Get total tile count from first byte */ 80 | uint32_t tile_count; 81 | tile_count = 0; 82 | tile_count |= out_data[3]<<24; 83 | tile_count |= out_data[2]<<16; 84 | tile_count |= out_data[1]<<8; 85 | tile_count |= out_data[0]; 86 | 87 | /* Each in offset index for each tile */ 88 | uint32_t tile_index[500]; 89 | for (i=0;i 65280) 119 | current_byte++; 120 | 121 | /* Read first 4 bytes for possible custom dimensions */ 122 | if (out_data[current_byte+1] == 0 && out_data[current_byte+3] == 0) 123 | { 124 | if (out_data[current_byte] > 0 && out_data[current_byte] < 0xBf && 125 | out_data[current_byte+2] > 0 && out_data[current_byte+2] < 0x64) 126 | { 127 | tile_width = out_data[current_byte]; 128 | tile_height = out_data[current_byte+2]; 129 | current_byte+=4; 130 | } 131 | } 132 | 133 | /* Create an SDL Surface */ 134 | SDL_Surface *surface; 135 | uint8_t *dst_byte; 136 | surface = SDL_CreateRGBSurface(0, tile_width, tile_height, 32, 0, 0, 0 ,0); 137 | dst_byte = (uint8_t*)surface->pixels; 138 | 139 | /* Go through the data, matching to palette and write to surface */ 140 | uint8_t src_byte; 141 | uint8_t red_p, green_p, blue_p; 142 | for (; current_byte < tile_index[current_tile+1]; current_byte++) 143 | { 144 | src_byte = out_data[current_byte]; 145 | red_p = palette[src_byte*3]; 146 | green_p = palette[src_byte*3+1]; 147 | blue_p = palette[src_byte*3+2]; 148 | 149 | dst_byte[current_tile_byte*4] = blue_p; 150 | dst_byte[current_tile_byte*4+1]= green_p; 151 | dst_byte[current_tile_byte*4+2]= red_p; 152 | dst_byte[current_tile_byte*4+3]=0xff; 153 | 154 | current_tile_byte++; 155 | } 156 | 157 | /* Create output file name */ 158 | char file_num[4]; 159 | char fout[12]; 160 | fout[0]='\0'; 161 | strcat(fout, "tile"); 162 | sprintf(&file_num[0],"%u",current_tile); 163 | strcat(fout, file_num); 164 | strcat(fout, ".bmp"); 165 | 166 | printf("Saving %s as a bitmap (%d x %d)\n",fout,tile_width,tile_height); 167 | /* Save and free */ 168 | SDL_SaveBMP(surface, fout); 169 | SDL_FreeSurface(surface); 170 | } 171 | 172 | return 0; 173 | } 174 | -------------------------------------------------------------------------------- /lmdave.c: -------------------------------------------------------------------------------- 1 | /******************************************* 2 | * Let's Make: Dangerous Dave 3 | * An instructional and independently developed interpretation of 4 | * the John Romero classic of the same name. 5 | * Copyright (c) MaiZure, 2017. 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "lmdave.h" 27 | 28 | /* Quick conversion between grid and pixel basis */ 29 | const uint8_t TILE_SIZE = 16; 30 | 31 | /* Entry point */ 32 | int main(int argc, char* argv[]) 33 | { 34 | SDL_Window *window; 35 | SDL_Renderer *renderer; 36 | const uint8_t DISPLAY_SCALE = 3; 37 | uint32_t timer_begin; 38 | uint32_t timer_end; 39 | uint32_t delay; 40 | 41 | struct game_state *game; 42 | struct game_assets *assets; 43 | 44 | /* Initialize game state */ 45 | game = malloc(sizeof(struct game_state)); 46 | init_game(game); 47 | 48 | /* Initialize SDL */ 49 | if (SDL_Init(SDL_INIT_VIDEO)) 50 | SDL_Log("SDL error: %s", SDL_GetError()); 51 | 52 | if (SDL_CreateWindowAndRenderer(320 * DISPLAY_SCALE, 200 * DISPLAY_SCALE, 0, &window, &renderer)) 53 | SDL_Log("Window/Renderer error: %s", SDL_GetError()); 54 | 55 | /* Easy onversion between original world (320x200) and current screen size */ 56 | SDL_RenderSetScale(renderer, DISPLAY_SCALE, DISPLAY_SCALE); 57 | 58 | /* Initialize assets */ 59 | assets = malloc(sizeof(struct game_assets)); 60 | init_assets(assets, renderer); 61 | 62 | /* Clear screen */ 63 | SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00); 64 | SDL_RenderClear(renderer); 65 | 66 | /* Start level 1 */ 67 | start_level(game); 68 | 69 | /* Game loop with fixed time step at 30 FPS*/ 70 | while (!game->quit) 71 | { 72 | timer_begin = SDL_GetTicks(); 73 | 74 | check_input(game); 75 | update_game(game); 76 | render(game, renderer, assets); 77 | 78 | timer_end = SDL_GetTicks(); 79 | 80 | delay = 33 - (timer_end-timer_begin); 81 | delay = delay > 33 ? 0 : delay; 82 | SDL_Delay(delay); 83 | } 84 | 85 | /* Clean up and quit */ 86 | SDL_Quit(); 87 | free(game); 88 | free(assets); 89 | 90 | return 0; 91 | } 92 | 93 | /* Set game and monster properties to default values */ 94 | void init_game(struct game_state *game) 95 | { 96 | int i,j; 97 | FILE *file_level; 98 | char fname[13]; 99 | char file_num[4]; 100 | 101 | game->quit = 0; 102 | game->tick = 0; 103 | game->current_level = 0; 104 | game->lives = 3; 105 | game->score = 0; 106 | game->view_x = 0; 107 | game->view_y = 0; 108 | game->scroll_x = 0; 109 | game->dave_x = 2; 110 | game->dave_y = 8; 111 | game->dbullet_px = 0; 112 | game->dbullet_py = 0; 113 | game->dbullet_dir = 0; 114 | game->ebullet_px = 0; 115 | game->ebullet_py = 0; 116 | game->ebullet_dir = 0; 117 | game->try_right = 0; 118 | game->try_left = 0; 119 | game->try_jump = 0; 120 | game->try_fire = 0; 121 | game->try_jetpack = 0; 122 | game->try_down = 0; 123 | game->dave_right = 0; 124 | game->dave_left = 0; 125 | game->dave_jump = 0; 126 | game->dave_fire = 0; 127 | game->dave_down = 0; 128 | game->dave_up = 0; 129 | game->dave_climb = 0; 130 | game->dave_jetpack = 0; 131 | game->jetpack_delay = 0; 132 | game->last_dir = 0; 133 | game->jump_timer = 0; 134 | game->on_ground = 1; 135 | game->dave_px = game->dave_x * TILE_SIZE; 136 | game->dave_py = game->dave_y * TILE_SIZE; 137 | game->check_pickup_x = 0; 138 | game->check_pickup_y = 0; 139 | game->check_door = 0; 140 | 141 | /* Deactivate all monsters */ 142 | for (j=0;j<5;j++) 143 | game->monster[j].type = 0; 144 | 145 | /* Load each level from level.dat. (see LEVEL.c utility) */ 146 | for (j=0; j<10; j++) 147 | { 148 | fname[0]='\0'; 149 | strcat(fname, "level"); 150 | sprintf(&file_num[0],"%u",j); 151 | strcat(fname, file_num); 152 | strcat(fname, ".dat"); 153 | 154 | file_level = fopen(fname, "rb"); 155 | 156 | for (i=0; ilevel[j].path); i++) 157 | game->level[j].path[i] = fgetc(file_level); 158 | 159 | for (i=0; ilevel[j].tiles); i++) 160 | game->level[j].tiles[i] = fgetc(file_level); 161 | 162 | for (i=0; ilevel[j].padding); i++) 163 | game->level[j].padding[i] = fgetc(file_level); 164 | 165 | fclose(file_level); 166 | } 167 | } 168 | 169 | /* Bring in tileset from tile.bmp files from original binary (see TILES.C)*/ 170 | void init_assets(struct game_assets *assets, SDL_Renderer *renderer) 171 | { 172 | int i,j; 173 | char fname[13]; 174 | char file_num[4]; 175 | char mname[13]; 176 | char mask_num[4]; 177 | SDL_Surface *surface; 178 | SDL_Surface *mask; 179 | uint8_t *surf_p; 180 | uint8_t *mask_p; 181 | uint8_t mask_offset; 182 | 183 | for (i=0; i<158; i++) 184 | { 185 | fname[0]='\0'; 186 | strcat(fname, "tile"); 187 | sprintf(&file_num[0],"%u",i); 188 | strcat(fname, file_num); 189 | strcat(fname, ".bmp"); 190 | 191 | /* Handle Dave tile masks */ 192 | if ((i >= 53 && i <= 59) || i == 67 || i == 68 || (i >= 71 && i <= 73) || (i >= 77 && i <= 82)) 193 | { 194 | if (i >= 53 && i <= 59) 195 | mask_offset = 7; 196 | if (i >= 67 && i <= 68) 197 | mask_offset = 2; 198 | if (i >= 71 && i <= 73) 199 | mask_offset = 3; 200 | if (i >= 77 && i <= 82) 201 | mask_offset = 6; 202 | 203 | mname[0]='\0'; 204 | strcat(mname, "tile"); 205 | sprintf(&mask_num[0],"%u",i+mask_offset); 206 | strcat(mname, mask_num); 207 | strcat(mname, ".bmp"); 208 | 209 | surface = SDL_LoadBMP(fname); 210 | mask = SDL_LoadBMP(mname); 211 | 212 | surf_p = (uint8_t*) surface->pixels; 213 | mask_p = (uint8_t*) mask->pixels; 214 | 215 | /* Write mask white background to dave tile */ 216 | for (j=0;j<(mask->pitch*mask->h);j++) 217 | surf_p[j] = mask_p[j] ? 0xFF : surf_p[j]; 218 | 219 | /* Make white mask transparent */ 220 | SDL_SetColorKey(surface, 1, SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF)); 221 | assets->graphics_tiles[i] = SDL_CreateTextureFromSurface(renderer, surface); 222 | SDL_FreeSurface(surface); 223 | SDL_FreeSurface(mask); 224 | } 225 | else 226 | { 227 | surface = SDL_LoadBMP(fname); 228 | 229 | /* Monster tiles should use black transparency */ 230 | if ((i >= 89 && i <= 120 ) || (i >= 129 && i <= 132 )) 231 | SDL_SetColorKey(surface, 1, SDL_MapRGB(surface->format, 0x00, 0x00, 0x00)); 232 | assets->graphics_tiles[i] = SDL_CreateTextureFromSurface(renderer, surface); 233 | } 234 | } 235 | } 236 | 237 | /* Checks input and sets flags. First step of the game loop */ 238 | void check_input(struct game_state *game) 239 | { 240 | SDL_Event event; 241 | SDL_PollEvent(&event); 242 | const uint8_t *keystate = SDL_GetKeyboardState(NULL); 243 | if ( keystate[SDL_SCANCODE_RIGHT] ) 244 | game->try_right = 1; 245 | if ( keystate[SDL_SCANCODE_LEFT] ) 246 | game->try_left = 1; 247 | if ( keystate[SDL_SCANCODE_UP] ) 248 | game->try_jump = 1; 249 | if ( keystate[SDL_SCANCODE_DOWN] ) 250 | game->try_down = 1; 251 | if ( keystate[SDL_SCANCODE_LCTRL] ) 252 | game->try_fire = 1; 253 | if ( keystate[SDL_SCANCODE_LALT] ) 254 | game->try_jetpack = 1; 255 | if (event.type == SDL_QUIT) 256 | game->quit = 1; 257 | } 258 | 259 | /* Updates world, entities, and handles input flags . 260 | Second step of the game loop */ 261 | void update_game(struct game_state *game) 262 | { 263 | check_collision(game); 264 | pickup_item(game, game->check_pickup_x, game->check_pickup_y); 265 | update_dbullet(game); 266 | update_ebullet(game); 267 | verify_input(game); 268 | move_dave(game); 269 | move_monsters(game); 270 | fire_monsters(game); 271 | scroll_screen(game); 272 | apply_gravity(game); 273 | update_level(game); 274 | clear_input(game); 275 | } 276 | 277 | /* Renders the world. First step of the game loop */ 278 | void render(struct game_state *game, SDL_Renderer *renderer, struct game_assets *assets) 279 | { 280 | /* Clear back buffer with black */ 281 | SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00); 282 | SDL_RenderClear(renderer); 283 | 284 | /* Draw world elements */ 285 | draw_world(game, assets, renderer); 286 | draw_dave(game, assets, renderer); 287 | draw_dave_bullet(game, assets, renderer); 288 | draw_monster_bullet(game, assets, renderer); 289 | draw_monsters(game, assets, renderer); 290 | draw_ui(game, assets, renderer); 291 | 292 | /* Swaps display buffers (puts above drawing on the screen)*/ 293 | SDL_RenderPresent(renderer); 294 | } 295 | 296 | /* Updates dave's collision point state */ 297 | void check_collision(struct game_state *game) 298 | { 299 | uint8_t grid_x, grid_y; 300 | uint8_t type; 301 | 302 | /* Updates 8 points around Dave */ 303 | game->collision_point[0] = is_clear(game, game->dave_px+4, game->dave_py-1, 1); 304 | game->collision_point[1] = is_clear(game, game->dave_px+10, game->dave_py-1, 1); 305 | game->collision_point[2] = is_clear(game, game->dave_px+11, game->dave_py+4, 1); 306 | game->collision_point[3] = is_clear(game, game->dave_px+11, game->dave_py+12, 1); 307 | game->collision_point[4] = is_clear(game, game->dave_px+10, game->dave_py+16, 1); 308 | game->collision_point[5] = is_clear(game, game->dave_px+4, game->dave_py+16, 1); 309 | game->collision_point[6] = is_clear(game, game->dave_px+3, game->dave_py+12, 1); 310 | game->collision_point[7] = is_clear(game, game->dave_px+3, game->dave_py+4, 1); 311 | 312 | /* Is dave on the ground? */ 313 | game->on_ground = ((!game->collision_point[4] && !game->collision_point[5]) || game->dave_climb); 314 | 315 | grid_x = (game->dave_px+6) / TILE_SIZE; 316 | grid_y = (game->dave_py+8) / TILE_SIZE; 317 | 318 | /* Don't check outside the room */ 319 | if (grid_x < 100 && grid_y < 10) 320 | type = game->level[game->current_level].tiles[grid_y*100+grid_x]; 321 | else 322 | type = 0; 323 | 324 | /* Can dave climb something in his current location (Trees? Stars?) */ 325 | if ((type >= 33 && type <= 35) || type == 41) 326 | game->can_climb = 1; 327 | else 328 | { 329 | game->can_climb = 0; 330 | game->dave_climb = 0; 331 | } 332 | } 333 | 334 | /* Clear flags set by keyboard input */ 335 | void clear_input(struct game_state *game) 336 | { 337 | game->try_jump = 0; 338 | game->try_right = 0; 339 | game->try_left = 0; 340 | game->try_fire = 0; 341 | game->try_jetpack = 0; 342 | game->try_down = 0; 343 | game->try_up = 0; 344 | } 345 | 346 | /* Pickup item at input location */ 347 | void pickup_item(struct game_state *game, uint8_t grid_x, uint8_t grid_y) 348 | { 349 | uint8_t type; 350 | 351 | /* No pickups outside of the world (or you'll lbe eaten by the grue) */ 352 | if (!grid_x || !grid_y || grid_x > 100 || grid_y > 10) 353 | return; 354 | 355 | /* Get the type */ 356 | type = game->level[game->current_level].tiles[grid_y*100+grid_x]; 357 | 358 | /* Handle the type */ 359 | switch (type) 360 | { 361 | /* Jetpack pickup */ 362 | case 4: 363 | { 364 | game->jetpack = 0xFF; 365 | } break; 366 | /* Trophy pickup */ 367 | case 10: 368 | { 369 | add_score(game,1000); 370 | game->trophy = 1; 371 | } break; 372 | /* Gun pickup */ 373 | case 20: game->gun = 1; break; 374 | /* Collectibls pickup */ 375 | case 47: add_score(game,100); break; 376 | case 48: add_score(game,50); break; 377 | case 49: add_score(game,150); break; 378 | case 50: add_score(game,300); break; 379 | case 51: add_score(game,200); break; 380 | case 52: add_score(game,500); break; 381 | default: break; 382 | } 383 | 384 | /* Clear the pickup tile */ 385 | game->level[game->current_level].tiles[grid_y*100+grid_x] = 0; 386 | 387 | /* Clear the pickup handler */ 388 | game->check_pickup_x = 0; 389 | game->check_pickup_y = 0; 390 | } 391 | 392 | /* Move Dave's bullets */ 393 | void update_dbullet(struct game_state *game) 394 | { 395 | uint8_t i, grid_x, grid_y, mx, my; 396 | 397 | grid_x = game->dbullet_px / TILE_SIZE; 398 | grid_y = game->dbullet_py / TILE_SIZE; 399 | 400 | /* Not active */ 401 | if (!game->dbullet_px || !game->dbullet_py) 402 | return; 403 | 404 | /* Bullet hit something - deactivate */ 405 | if (!is_clear(game, game->dbullet_px, game->dbullet_py, 0)) 406 | game->dbullet_px = game->dbullet_py = 0; 407 | 408 | /* Bullet left room - deactivate */ 409 | if (grid_x-game->view_x < 1 || grid_x - game->view_x > 20) 410 | game->dbullet_px = game->dbullet_py = 0; 411 | 412 | if (game->dbullet_px) 413 | { 414 | game->dbullet_px += game->dbullet_dir * 4; 415 | 416 | /* Check all monster positions */ 417 | for (i=0;i<5;i++) 418 | { 419 | if (game->monster[i].type) 420 | { 421 | mx = game->monster[i].monster_x; 422 | my = game->monster[i].monster_y; 423 | 424 | if ((grid_y == my || grid_y == my + 1) && (grid_x == mx || grid_x == mx+1)) 425 | { 426 | /* Dave's bullet hits monster */ 427 | game->dbullet_px = game->dbullet_py = 0; 428 | game->monster[i].dead_timer = 30; 429 | add_score(game, 300); 430 | } 431 | } 432 | } 433 | } 434 | } 435 | 436 | void update_ebullet(struct game_state *game) 437 | { 438 | if (!game->ebullet_px || !game->ebullet_py) 439 | return; 440 | 441 | if (!is_clear(game, game->ebullet_px, game->ebullet_py, 0)) 442 | game->ebullet_px = game->ebullet_py = 0; 443 | 444 | if (!is_visible(game, game->ebullet_px)) 445 | game->ebullet_px = game->ebullet_py = 0; 446 | 447 | if (game->ebullet_px) 448 | { 449 | uint8_t grid_x, grid_y; 450 | game->ebullet_px += game->ebullet_dir * 4; 451 | 452 | grid_x = game->ebullet_px / TILE_SIZE; 453 | grid_y = game->ebullet_py / TILE_SIZE; 454 | 455 | /* Compare with Dave's position */ 456 | if (grid_y == game->dave_y && grid_x == game->dave_x) 457 | { 458 | /* Monster's bullet hits Dave */ 459 | game->ebullet_px = game->ebullet_py = 0; 460 | game->dave_dead_timer = 30; 461 | } 462 | } 463 | } 464 | 465 | /* Start a new level */ 466 | void start_level(struct game_state *game) 467 | { 468 | uint8_t i; 469 | restart_level(game); 470 | 471 | /* Deactivate monsters */ 472 | for (i=0;i<5;i++) 473 | { 474 | game->monster[i].type = 0; 475 | game->monster[i].path_index = 0; 476 | game->monster[i].dead_timer = 0; 477 | game->monster[i].next_px = 0; 478 | game->monster[i].next_py = 0; 479 | } 480 | 481 | /* Activate monsters based on level 482 | current_level counting starts at 0 483 | (i.e Level 3 is case 2) */ 484 | switch (game->current_level) 485 | { 486 | case 2: 487 | { 488 | game->monster[0].type = 89; 489 | game->monster[0].monster_px = 44 * TILE_SIZE; 490 | game->monster[0].monster_py = 4 * TILE_SIZE; 491 | 492 | game->monster[1].type = 89; 493 | game->monster[1].monster_px = 59 * TILE_SIZE; 494 | game->monster[1].monster_py = 4 * TILE_SIZE; 495 | } break; 496 | case 3: 497 | { 498 | game->monster[0].type = 93; 499 | game->monster[0].monster_px = 32 * TILE_SIZE; 500 | game->monster[0].monster_py = 2 * TILE_SIZE; 501 | } break; 502 | case 4: 503 | { 504 | game->monster[0].type = 97; 505 | game->monster[0].monster_px = 15 * TILE_SIZE; 506 | game->monster[0].monster_py = 3 * TILE_SIZE; 507 | game->monster[1].type = 97; 508 | game->monster[1].monster_px = 33 * TILE_SIZE; 509 | game->monster[1].monster_py = 3 * TILE_SIZE; 510 | game->monster[2].type = 97; 511 | game->monster[2].monster_px = 49 * TILE_SIZE; 512 | game->monster[2].monster_py = 3 * TILE_SIZE; 513 | } break; 514 | case 5: 515 | { 516 | game->monster[0].type = 101; 517 | game->monster[0].monster_px = 10 * TILE_SIZE; 518 | game->monster[0].monster_py = 8 * TILE_SIZE; 519 | game->monster[1].type = 101; 520 | game->monster[1].monster_px = 28 * TILE_SIZE; 521 | game->monster[1].monster_py = 8 * TILE_SIZE; 522 | game->monster[2].type = 101; 523 | game->monster[2].monster_px = 45 * TILE_SIZE; 524 | game->monster[2].monster_py = 2 * TILE_SIZE; 525 | game->monster[3].type = 101; 526 | game->monster[3].monster_px = 40 * TILE_SIZE; 527 | game->monster[3].monster_py = 8 * TILE_SIZE; 528 | } break; 529 | case 6: 530 | { 531 | game->monster[0].type = 105; 532 | game->monster[0].monster_px = 5 * TILE_SIZE; 533 | game->monster[0].monster_py = 2 * TILE_SIZE; 534 | game->monster[1].type = 105; 535 | game->monster[1].monster_px = 16 * TILE_SIZE; 536 | game->monster[1].monster_py = 1 * TILE_SIZE; 537 | game->monster[2].type = 105; 538 | game->monster[2].monster_px = 46 * TILE_SIZE; 539 | game->monster[2].monster_py = 2 * TILE_SIZE; 540 | game->monster[3].type = 105; 541 | game->monster[3].monster_px = 56 * TILE_SIZE; 542 | game->monster[3].monster_py = 3 * TILE_SIZE; 543 | } break; 544 | case 7: 545 | { 546 | game->monster[0].type = 109; 547 | game->monster[0].monster_px = 53 * TILE_SIZE; 548 | game->monster[0].monster_py = 5 * TILE_SIZE; 549 | game->monster[1].type = 109; 550 | game->monster[1].monster_px = 72 * TILE_SIZE; 551 | game->monster[1].monster_py = 2 * TILE_SIZE; 552 | game->monster[2].type = 109; 553 | game->monster[2].monster_px = 84 * TILE_SIZE; 554 | game->monster[2].monster_py = 1 * TILE_SIZE; 555 | } break; 556 | case 8: 557 | { 558 | game->monster[0].type = 113; 559 | game->monster[0].monster_px = 35 * TILE_SIZE; 560 | game->monster[0].monster_py = 8 * TILE_SIZE; 561 | game->monster[1].type = 113; 562 | game->monster[1].monster_px = 41 * TILE_SIZE; 563 | game->monster[1].monster_py = 8 * TILE_SIZE; 564 | game->monster[2].type = 113; 565 | game->monster[2].monster_px = 49 * TILE_SIZE; 566 | game->monster[2].monster_py = 8 * TILE_SIZE; 567 | game->monster[3].type = 113; 568 | game->monster[3].monster_px = 65 * TILE_SIZE; 569 | game->monster[3].monster_py = 8 * TILE_SIZE; 570 | } break; 571 | case 9: 572 | { 573 | game->monster[0].type = 117; 574 | game->monster[0].monster_px = 45 * TILE_SIZE; 575 | game->monster[0].monster_py = 8 * TILE_SIZE; 576 | game->monster[1].type = 117; 577 | game->monster[1].monster_px = 51 * TILE_SIZE; 578 | game->monster[1].monster_py = 2 * TILE_SIZE; 579 | game->monster[2].type = 117; 580 | game->monster[2].monster_px = 65 * TILE_SIZE; 581 | game->monster[2].monster_py = 3 * TILE_SIZE; 582 | game->monster[3].type = 117; 583 | game->monster[3].monster_px = 82 * TILE_SIZE; 584 | game->monster[3].monster_py = 5 * TILE_SIZE; 585 | } break; 586 | } 587 | 588 | /* Reset various state variables at the start of each level */ 589 | game->dave_dead_timer = 0; 590 | game->trophy = 0; 591 | game->gun = 0; 592 | game->jetpack = 0; 593 | game->dave_jetpack = 0; 594 | game->check_door = 0; 595 | game->view_x = 0; 596 | game->view_y = 0; 597 | game->last_dir = 0; 598 | game->dbullet_px = 0; 599 | game->dbullet_py = 0; 600 | game->ebullet_px = 0; 601 | game->ebullet_py = 0; 602 | game->jump_timer = 0; 603 | } 604 | 605 | /* Check if keyboard input is valid. If so, set action variable */ 606 | void verify_input(struct game_state *game) 607 | { 608 | /* Dave is dead. No input is valid */ 609 | if (game->dave_dead_timer) 610 | return; 611 | 612 | /* Dave can move right if there are no obstructions */ 613 | if (game->try_right && game->collision_point[2] && game->collision_point[3]) 614 | game->dave_right = 1; 615 | 616 | /* Dave can move left if there are no obstructions */ 617 | if (game->try_left && game->collision_point[6] && game->collision_point[7]) 618 | game->dave_left = 1; 619 | 620 | /* Dave can jump if he's on the ground and not using the jeypack */ 621 | if (game->try_jump && game->on_ground && !game->dave_jump && !game->dave_jetpack && !game->can_climb && game->collision_point[0] && game->collision_point[1]) 622 | game->dave_jump = 1; 623 | 624 | /* Dave should climb rather than jump if he's in front of a climbable tile */ 625 | if (game->try_jump && game->can_climb) 626 | { 627 | game->dave_up = 1; 628 | game->dave_climb = 1; 629 | } 630 | 631 | /* Dave and fire if he has the gun and isn't already firing */ 632 | if (game->try_fire && game->gun && !game->dbullet_py && !game->dbullet_px) 633 | game->dave_fire = 1; 634 | 635 | /* Dave can toggle the jetpack if hehas one and he didn't recently toggle it */ 636 | if (game->try_jetpack && game->jetpack && !game->jetpack_delay) 637 | { 638 | game->dave_jetpack = !game->dave_jetpack; 639 | game->jetpack_delay = 10; 640 | } 641 | 642 | /* Dave can move downward if he is climbing or has a jetpack */ 643 | if (game->try_down && (game->dave_jetpack || game->dave_climb) && game->collision_point[4] && game->collision_point[5]) 644 | game->dave_down = 1; 645 | 646 | /* Dave can move up if he has jetpack */ 647 | if (game->try_jump && game->dave_jetpack && game->collision_point[0] && game->collision_point[1]) 648 | game->dave_up = 1; 649 | } 650 | 651 | /* Move dave around the world */ 652 | void move_dave(struct game_state *game) 653 | { 654 | game->dave_x = game->dave_px / TILE_SIZE; 655 | game->dave_y = game->dave_py / TILE_SIZE; 656 | 657 | /* Wrap Dave to the top of the level when he falls through the floor */ 658 | if (game->dave_y > 9) 659 | { 660 | game->dave_y = 0; 661 | game->dave_py = -16; 662 | } 663 | 664 | /* Move Dave right */ 665 | if (game->dave_right) 666 | { 667 | game->dave_px += 2; 668 | game->last_dir = 1; 669 | game->dave_tick++; 670 | game->dave_right = 0; 671 | } 672 | 673 | /* Move Dave left */ 674 | if (game->dave_left) 675 | { 676 | game->dave_px -= 2; 677 | game->last_dir = -1; 678 | game->dave_tick++; 679 | game->dave_left = 0; 680 | } 681 | 682 | /* Move Dave down */ 683 | if (game->dave_down) 684 | { 685 | game->dave_tick++; 686 | game->dave_py += 2; 687 | game->dave_down = 0; 688 | } 689 | 690 | /* Move Dave up */ 691 | if (game->dave_up) 692 | { 693 | game->dave_tick++; 694 | game->dave_py -= 2; 695 | game->dave_up = 0; 696 | } 697 | 698 | /* Jetpack usage cancels jump effects */ 699 | if (game->dave_jetpack) 700 | { 701 | game->dave_jump = 0; 702 | game->jump_timer = 0; 703 | } 704 | 705 | /* Make Dave jump */ 706 | if (game->dave_jump) 707 | { 708 | if (!game->jump_timer) 709 | { 710 | game->jump_timer = 30; 711 | game->last_dir = 0; 712 | } 713 | 714 | if (game->collision_point[0] && game->collision_point[1]) 715 | { 716 | /* Dave should move up at a decreasing rate, then float for a moment */ 717 | if (game->jump_timer > 16) 718 | game->dave_py -= 2; 719 | if (game->jump_timer >= 12 && game->jump_timer <= 15) 720 | game->dave_py -= 1; 721 | } 722 | 723 | game->jump_timer--; 724 | 725 | if (!game->jump_timer) 726 | game->dave_jump = 0; 727 | 728 | } 729 | 730 | /* Fire Dave's gun */ 731 | if (game->dave_fire) 732 | { 733 | game->dbullet_dir = game->last_dir; 734 | 735 | /* Bullet should match Dave's direction */ 736 | if (!game->dbullet_dir) 737 | game->dbullet_dir = 1; 738 | 739 | /* Bullet should start in front of Dave */ 740 | if (game->dbullet_dir == 1) 741 | game->dbullet_px = game->dave_px + 18; 742 | 743 | if (game->dbullet_dir == -1) 744 | game->dbullet_px = game->dave_px -8; 745 | 746 | game->dbullet_py = game->dave_py + 8; 747 | game->dave_fire = 0; 748 | } 749 | } 750 | 751 | /* Move monsters along their predefined path */ 752 | void move_monsters(struct game_state *game) 753 | { 754 | uint8_t i,j; 755 | 756 | for (i=0;i<5;i++) 757 | { 758 | struct monster_state *m; 759 | m = &game->monster[i]; 760 | 761 | if (m->type && !m->dead_timer) 762 | { 763 | /* Move monster twice each tick. Hack to match speed of original game */ 764 | for (j=0;j<2;j++) 765 | { 766 | if (!m->next_px && !m->next_py) 767 | { 768 | /* Get the next path waypoint */ 769 | m->next_px = game->level[game->current_level].path[m->path_index]; 770 | m->next_py = game->level[game->current_level].path[m->path_index+1]; 771 | m->path_index+=2; 772 | 773 | /* End of path -- reset monster to start of path */ 774 | if (m->next_px == (signed char)0xEA && m->next_py == (signed char)0xEA) 775 | { 776 | m->next_px = game->level[game->current_level].path[0]; 777 | m->next_py = game->level[game->current_level].path[1]; 778 | m->path_index = 2; 779 | } 780 | } 781 | 782 | /* Move monster left */ 783 | if (m->next_px < 0) 784 | { 785 | m->monster_px -= 1; 786 | m->next_px++; 787 | } 788 | 789 | /* Move monster right */ 790 | if (m->next_px > 0) 791 | { 792 | m->monster_px += 1; 793 | m->next_px--; 794 | } 795 | 796 | /* Move monster up */ 797 | if (m->next_py < 0) 798 | { 799 | m->monster_py -= 1; 800 | m->next_py++; 801 | } 802 | 803 | /* Move monster down */ 804 | if (m->next_py > 0) 805 | { 806 | m->monster_py += 1; 807 | m->next_py--; 808 | } 809 | } 810 | 811 | /* Update monster grid position */ 812 | m->monster_x = m->monster_px / TILE_SIZE; 813 | m->monster_y = m->monster_py / TILE_SIZE; 814 | } 815 | } 816 | } 817 | 818 | /* Monster shooting */ 819 | void fire_monsters(struct game_state *game) 820 | { 821 | int i; 822 | 823 | if (!game->ebullet_px && !game->ebullet_py) 824 | { 825 | for (i=0; i<5; i++) 826 | { 827 | /* Monster's shoot if they're active and visible */ 828 | if (game->monster[i].type && is_visible(game,game->monster[i].monster_px) && !game->monster[i].dead_timer) 829 | { 830 | /* Shoot towards Dave */ 831 | game->ebullet_dir = game->dave_px < game->monster[i].monster_px ? -1 : 1; 832 | 833 | if (!game->ebullet_dir) 834 | game->ebullet_dir = 1; 835 | 836 | /* Start bullet in front of monster */ 837 | if (game->ebullet_dir == 1) 838 | game->ebullet_px = game->monster[i].monster_px + 18; 839 | 840 | if (game->ebullet_dir == -1) 841 | game->ebullet_px = game->monster[i].monster_px - 8; 842 | 843 | game->ebullet_py = game->monster[i].monster_py + 8; 844 | } 845 | } 846 | } 847 | } 848 | 849 | /* Scroll the screen when Dave is near the edge 850 | Game view is 20 grid units wide */ 851 | void scroll_screen(struct game_state *game) 852 | { 853 | /* Scroll right if Dave reaches view position 18 */ 854 | if (game->dave_x - game->view_x >= 18) 855 | game->scroll_x = 15; 856 | 857 | /* Scroll left if Dave reaches view position 2 */ 858 | if (game->dave_x - game->view_x < 2) 859 | game->scroll_x = -15; 860 | 861 | if (game->scroll_x > 0) 862 | { 863 | /* Cap right side at 80 (each level is 100 wide) */ 864 | if (game->view_x == 80) 865 | game->scroll_x = 0; 866 | else 867 | { 868 | game->view_x++; 869 | game->scroll_x--; 870 | } 871 | } 872 | 873 | /* Cap left side at 0*/ 874 | if (game->scroll_x < 0) 875 | { 876 | if (game->view_x == 0) 877 | game->scroll_x = 0; 878 | else 879 | { 880 | game->view_x--; 881 | game->scroll_x++; 882 | } 883 | } 884 | } 885 | 886 | /* Apply gravity to Dave */ 887 | void apply_gravity(struct game_state *game) 888 | { 889 | if (!game->dave_jump && !game->on_ground && !game->dave_jetpack && !game->dave_climb) 890 | { 891 | if (is_clear(game, game->dave_px+4, game->dave_py+17, 1)) 892 | game->dave_py+=2; 893 | else 894 | { 895 | uint8_t not_align; 896 | not_align = game->dave_py % TILE_SIZE; 897 | 898 | /* If Dave is not level aligned, lock him to nearest tile*/ 899 | if (not_align) 900 | { 901 | game->dave_py = not_align < 8 ? 902 | game->dave_py - not_align : 903 | game->dave_py + TILE_SIZE-not_align; 904 | } 905 | } 906 | } 907 | } 908 | 909 | /* Handle level-wide events */ 910 | void update_level(struct game_state *game) 911 | { 912 | uint8_t i; 913 | 914 | /* Increment game dick timer */ 915 | game->tick++; 916 | 917 | /* Decrement jetpack delay */ 918 | if (game->jetpack_delay) 919 | game->jetpack_delay--; 920 | 921 | /* Decrement Dave's jetpack fuel */ 922 | if (game->dave_jetpack) 923 | { 924 | game->jetpack--; 925 | if (!game->jetpack) 926 | game->dave_jetpack = 0; 927 | } 928 | 929 | /* Check if Dave completes level */ 930 | if (game->check_door) 931 | { 932 | if (game->trophy) 933 | { 934 | add_score(game,2000); 935 | 936 | if (game->current_level < 9) 937 | { 938 | game->current_level++; 939 | start_level(game); 940 | } 941 | else 942 | { 943 | printf("You won with %u points!\n", game->score); 944 | game->quit = 1; 945 | } 946 | } 947 | else 948 | game->check_door = 0; 949 | } 950 | 951 | /* Reset level when Dave is dead */ 952 | if (game->dave_dead_timer) 953 | { 954 | game->dave_dead_timer--; 955 | if (!game->dave_dead_timer) 956 | { 957 | if (game->lives) 958 | { 959 | game->lives--; 960 | restart_level(game); 961 | 962 | } 963 | else 964 | game->quit = 1; 965 | } 966 | } 967 | 968 | /* Check monster timers */ 969 | for (i=0;i<5;i++) 970 | { 971 | if (game->monster[i].dead_timer) 972 | { 973 | game->monster[i].dead_timer--; 974 | if (!game->monster[i].dead_timer) 975 | game->monster[i].type = 0; 976 | } 977 | else 978 | { 979 | /* Check Dave/Monster collisions */ 980 | if (game->monster[i].type) 981 | { 982 | if (game->monster[i].monster_x == game->dave_x && game->monster[i].monster_y == game->dave_y) 983 | { 984 | game->monster[i].dead_timer = 30; 985 | game->dave_dead_timer = 30; 986 | } 987 | } 988 | } 989 | } 990 | } 991 | 992 | /* Sets Dave start position in a level */ 993 | void restart_level(struct game_state *game) 994 | { 995 | switch (game->current_level) 996 | { 997 | case 0: game->dave_x = 2; game->dave_y = 8; break; 998 | case 1: game->dave_x = 1; game->dave_y = 8; break; 999 | case 2: game->dave_x = 2; game->dave_y = 5; break; 1000 | case 3: game->dave_x = 1; game->dave_y = 5; break; 1001 | case 4: game->dave_x = 2; game->dave_y = 8; break; 1002 | case 5: game->dave_x = 2; game->dave_y = 8; break; 1003 | case 6: game->dave_x = 1; game->dave_y = 2; break; 1004 | case 7: game->dave_x = 2; game->dave_y = 8; break; 1005 | case 8: game->dave_x = 6; game->dave_y = 1; break; 1006 | case 9: game->dave_x = 2; game->dave_y = 8; break; 1007 | } 1008 | 1009 | game->dave_px = game->dave_x * TILE_SIZE; 1010 | game->dave_py = game->dave_y * TILE_SIZE; 1011 | } 1012 | 1013 | /* Update frame animation based on tick timer and tile's type count */ 1014 | uint8_t update_frame(struct game_state *game, uint8_t tile, uint8_t salt) 1015 | { 1016 | uint8_t mod; 1017 | 1018 | switch (tile) 1019 | { 1020 | case 6: mod = 4; break; 1021 | case 10: mod = 5; break; 1022 | case 25: mod = 4; break; 1023 | case 36: mod = 5; break; 1024 | case 129: mod = 4; break; 1025 | default: mod = 1; break; 1026 | } 1027 | 1028 | return tile + (salt + game->tick/5) % mod; 1029 | } 1030 | 1031 | /* Render the world */ 1032 | void draw_world(struct game_state *game, struct game_assets *assets, SDL_Renderer *renderer) 1033 | { 1034 | SDL_Rect dest; 1035 | uint8_t tile_index; 1036 | uint8_t i, j; 1037 | 1038 | /* Draw each tile in row-major */ 1039 | for (j=0; j < 10; j++) 1040 | { 1041 | dest.y = TILE_SIZE + j * TILE_SIZE; 1042 | dest.w = TILE_SIZE; 1043 | dest.h = TILE_SIZE; 1044 | for (i=0; i < 20; i++) 1045 | { 1046 | dest.x = i * TILE_SIZE; 1047 | tile_index = game->level[game->current_level].tiles[j*100+game->view_x+i]; 1048 | tile_index = update_frame(game,tile_index,i); 1049 | SDL_RenderCopy(renderer, assets->graphics_tiles[tile_index], NULL, &dest); 1050 | } 1051 | } 1052 | } 1053 | 1054 | /* Render dave */ 1055 | void draw_dave(struct game_state *game, struct game_assets *assets, SDL_Renderer *renderer) 1056 | { 1057 | SDL_Rect dest; 1058 | uint8_t tile_index; 1059 | 1060 | dest.x = game->dave_px - game->view_x * TILE_SIZE; 1061 | dest.y = TILE_SIZE + game->dave_py; 1062 | dest.w = 20; 1063 | dest.h = 16; 1064 | 1065 | /* Find the right Dave tile based on his condition */ 1066 | if (!game->last_dir) 1067 | tile_index = 56; 1068 | else 1069 | { 1070 | tile_index = game->last_dir > 0 ? 53 : 57; 1071 | tile_index += (game->dave_tick/5) % 3; 1072 | } 1073 | 1074 | if (game->dave_jetpack) 1075 | tile_index = game->last_dir >= 0 ? 77 : 80; 1076 | else 1077 | { 1078 | if (game->dave_jump || !game->on_ground) 1079 | tile_index = game->last_dir >= 0 ? 67 : 68; 1080 | 1081 | if (game->dave_climb) 1082 | tile_index = 71 + (game->dave_tick/5) % 3; 1083 | } 1084 | 1085 | if (game->dave_dead_timer) 1086 | tile_index = 129 + (game->tick/3) % 4;; 1087 | 1088 | SDL_RenderCopy(renderer, assets->graphics_tiles[tile_index], NULL, &dest); 1089 | } 1090 | 1091 | /* Render Dave's bullets */ 1092 | void draw_dave_bullet(struct game_state *game, struct game_assets *assets, SDL_Renderer *renderer) 1093 | { 1094 | SDL_Rect dest; 1095 | uint8_t tile_index; 1096 | 1097 | if (!game->dbullet_px || !game->dbullet_py) 1098 | return; 1099 | 1100 | dest.x = game->dbullet_px - game->view_x * TILE_SIZE; 1101 | dest.y = TILE_SIZE + game->dbullet_py; 1102 | dest.w = 12; 1103 | dest.h = 3; 1104 | tile_index = game->dbullet_dir > 0 ? 127 : 128; 1105 | 1106 | SDL_RenderCopy(renderer, assets->graphics_tiles[tile_index], NULL, &dest); 1107 | } 1108 | 1109 | /* Render Monster bullets */ 1110 | void draw_monster_bullet(struct game_state *game, struct game_assets *assets, SDL_Renderer *renderer) 1111 | { 1112 | SDL_Rect dest; 1113 | uint8_t tile_index; 1114 | 1115 | if (game->ebullet_px && game->ebullet_px) 1116 | { 1117 | dest.x = game->ebullet_px - game->view_x * TILE_SIZE; 1118 | dest.y = TILE_SIZE + game->ebullet_py; 1119 | dest.w = 12; 1120 | dest.h = 3; 1121 | tile_index = game->ebullet_dir > 0 ? 121 : 124; 1122 | 1123 | SDL_RenderCopy(renderer, assets->graphics_tiles[tile_index], NULL, &dest); 1124 | } 1125 | } 1126 | 1127 | /* Render Monsters */ 1128 | void draw_monsters(struct game_state *game, struct game_assets *assets, SDL_Renderer *renderer) 1129 | { 1130 | SDL_Rect dest; 1131 | uint8_t tile_index; 1132 | struct monster_state *m; 1133 | uint8_t i; 1134 | 1135 | for (i=0;i<5;i++) 1136 | { 1137 | m = &game->monster[i]; 1138 | 1139 | if (m->type) 1140 | { 1141 | dest.x = m->monster_px - game->view_x * TILE_SIZE; 1142 | dest.y = TILE_SIZE + m->monster_py; 1143 | dest.w = 20; 1144 | dest.h = 16; 1145 | 1146 | tile_index = m->dead_timer ? 129 : m->type; 1147 | tile_index += (game->tick/3) % 4; 1148 | 1149 | if (m->type >= 105 && m->type <= 108 && !m->dead_timer) 1150 | { 1151 | dest.w = 18; 1152 | dest.h = 8; 1153 | } 1154 | 1155 | SDL_RenderCopy(renderer, assets->graphics_tiles[tile_index], NULL, &dest); 1156 | } 1157 | } 1158 | } 1159 | 1160 | /* Render all UI elements */ 1161 | void draw_ui(struct game_state *game, struct game_assets *assets, SDL_Renderer *renderer) 1162 | { 1163 | SDL_Rect dest; 1164 | uint8_t i; 1165 | 1166 | /* Screen border */ 1167 | dest.x = 0; 1168 | dest.y = 16; 1169 | dest.w = 960; 1170 | dest.h = 1; 1171 | SDL_SetRenderDrawColor(renderer, 0xEE, 0xEE, 0xEE, 0xFF); 1172 | SDL_RenderFillRect(renderer, &dest); 1173 | dest.y = 176; 1174 | SDL_RenderFillRect(renderer, &dest); 1175 | 1176 | /* Score banner */ 1177 | dest.x = 1; 1178 | dest.y = 2; 1179 | dest.w = 62; 1180 | dest.h = 11; 1181 | SDL_RenderCopy(renderer, assets->graphics_tiles[137], NULL, &dest); 1182 | 1183 | /* Level banner */ 1184 | dest.x = 120; 1185 | SDL_RenderCopy(renderer, assets->graphics_tiles[136], NULL, &dest); 1186 | 1187 | /* Lives banner */ 1188 | dest.x = 200; 1189 | SDL_RenderCopy(renderer, assets->graphics_tiles[135], NULL, &dest); 1190 | 1191 | /* Score 10000s digit */ 1192 | dest.x = 64; 1193 | dest.w = 8; 1194 | dest.h = 11; 1195 | SDL_RenderCopy(renderer, assets->graphics_tiles[148 + (game->score / 10000) % 10], NULL, &dest); 1196 | 1197 | /* Score 1000s digit */ 1198 | dest.x = 72; 1199 | SDL_RenderCopy(renderer, assets->graphics_tiles[148 + (game->score / 1000) % 10], NULL, &dest); 1200 | 1201 | /* Score 100s digit */ 1202 | dest.x = 80; 1203 | SDL_RenderCopy(renderer, assets->graphics_tiles[148 + (game->score / 100) % 10], NULL, &dest); 1204 | 1205 | 1206 | /* Score 10s digit */ 1207 | dest.x = 88; 1208 | SDL_RenderCopy(renderer, assets->graphics_tiles[148 + (game->score / 10) % 10], NULL, &dest); 1209 | 1210 | /* Score LSD is always zero */ 1211 | dest.x = 96; 1212 | SDL_RenderCopy(renderer, assets->graphics_tiles[148], NULL, &dest); 1213 | 1214 | /* Level 10s digit */ 1215 | dest.x = 170; 1216 | SDL_RenderCopy(renderer, assets->graphics_tiles[148 + (game->current_level + 1)/10], NULL, &dest); 1217 | 1218 | /* Level unit digit */ 1219 | dest.x = 178; 1220 | SDL_RenderCopy(renderer, assets->graphics_tiles[148 + (game->current_level + 1) % 10], NULL, &dest); 1221 | 1222 | /* Life count icon */ 1223 | for (i=0; ilives;i++) 1224 | { 1225 | dest.x = (255+16*i); 1226 | dest.w = 16; 1227 | dest.h = 12; 1228 | SDL_RenderCopy(renderer, assets->graphics_tiles[143], NULL, &dest); 1229 | } 1230 | 1231 | /* Trophy pickup banner */ 1232 | if (game->trophy) 1233 | { 1234 | dest.x = 72; 1235 | dest.y = 180; 1236 | dest.w = 176; 1237 | dest.h = 14; 1238 | SDL_RenderCopy(renderer, assets->graphics_tiles[138], NULL, &dest); 1239 | } 1240 | 1241 | /* Gun pickup banner */ 1242 | if (game->gun) 1243 | { 1244 | dest.x = 255; 1245 | dest.y = 180; 1246 | dest.w = 62; 1247 | dest.h = 11; 1248 | SDL_RenderCopy(renderer, assets->graphics_tiles[134], NULL, &dest); 1249 | } 1250 | 1251 | /* Jetpack UI elements */ 1252 | if (game->jetpack) 1253 | { 1254 | /* Jetpack banner */ 1255 | dest.x = 1; 1256 | dest.y = 177; 1257 | dest.w = 62; 1258 | dest.h = 11; 1259 | SDL_RenderCopy(renderer, assets->graphics_tiles[133], NULL, &dest); 1260 | 1261 | /* Jetpack fuel counter */ 1262 | dest.x = 1; 1263 | dest.y = 190; 1264 | dest.w = 62; 1265 | dest.h = 8; 1266 | SDL_RenderCopy(renderer, assets->graphics_tiles[141], NULL, &dest); 1267 | 1268 | /* Jetpack fuel bar */ 1269 | dest.x = 2; 1270 | dest.y = 192; 1271 | dest.w = game->jetpack * 0.23; 1272 | dest.h = 4; 1273 | SDL_SetRenderDrawColor(renderer, 0xEE, 0x00, 0x00, 0xFF); 1274 | SDL_RenderFillRect(renderer, &dest); 1275 | } 1276 | } 1277 | 1278 | /* Checks if designated grid has an obstruction or pickup 1279 | 1 means clear */ 1280 | uint8_t is_clear(struct game_state *game, uint16_t px, uint16_t py, uint8_t is_dave) 1281 | { 1282 | uint8_t grid_x; 1283 | uint8_t grid_y; 1284 | uint8_t type; 1285 | 1286 | grid_x = px / TILE_SIZE; 1287 | grid_y = py / TILE_SIZE; 1288 | 1289 | if (grid_x > 99 || grid_y > 9) 1290 | return 1; 1291 | 1292 | type = game->level[game->current_level].tiles[grid_y*100+grid_x]; 1293 | 1294 | if (type == 1) { return 0; } 1295 | if (type == 3) { return 0; } 1296 | if (type == 5) { return 0; } 1297 | if (type == 15) { return 0; } 1298 | if (type == 16) { return 0; } 1299 | if (type == 17) { return 0; } 1300 | if (type == 18) { return 0; } 1301 | if (type == 19) { return 0; } 1302 | if (type == 21) { return 0; } 1303 | if (type == 22) { return 0; } 1304 | if (type == 23) { return 0; } 1305 | if (type == 24) { return 0; } 1306 | if (type == 29) { return 0; } 1307 | if (type == 30) { return 0; } 1308 | 1309 | /* Dave-only collision checks (pickups) */ 1310 | if (is_dave) 1311 | { 1312 | switch (type) 1313 | { 1314 | case 2: game->check_door = 1; break; 1315 | case 4: 1316 | case 10: 1317 | case 20: 1318 | case 47: 1319 | case 48: 1320 | case 49: 1321 | case 50: 1322 | case 51: 1323 | case 52: 1324 | { 1325 | game->check_pickup_x = grid_x; 1326 | game->check_pickup_y = grid_y; 1327 | } break; 1328 | case 6: 1329 | case 25: 1330 | case 36: 1331 | { 1332 | if (!game->dave_dead_timer) 1333 | game->dave_dead_timer = 30; 1334 | } break; 1335 | default: break; 1336 | } 1337 | } 1338 | 1339 | return 1; 1340 | } 1341 | 1342 | /* Checks if an input pixel position is currently visible */ 1343 | inline uint8_t is_visible(struct game_state *game, uint16_t px) 1344 | { 1345 | uint8_t pos_x; 1346 | pos_x = px / TILE_SIZE; 1347 | return (pos_x - game->view_x < 20 && pos_x - game->view_x >= 0); 1348 | } 1349 | 1350 | /* Adds to player score and checks for extra life every 20,000 points */ 1351 | inline void add_score(struct game_state *game, uint16_t new_score) 1352 | { 1353 | if (game->score / 20000 != ((game->score+new_score) / 20000)) 1354 | game->lives++; 1355 | 1356 | game->score += new_score; 1357 | } 1358 | -------------------------------------------------------------------------------- /lmdave.h: -------------------------------------------------------------------------------- 1 | #ifndef LMDAVE_H 2 | #define LMDAVE_H 3 | 4 | #include 5 | 6 | /* Format of the level information 7 | * -path is used for monster movement 8 | * -tiles contain tileset indices 9 | * -padding unused but included for capatibility 10 | */ 11 | struct dave_level { 12 | int8_t path[256]; 13 | uint8_t tiles[1000]; 14 | uint8_t padding[24]; 15 | }; 16 | 17 | /* Monster state information for a single monster 18 | * -type is the tileset number (0 is unused) 19 | * -path_index references dave_level path data 20 | * -dead_timer is used for death delay 21 | * - *_p[xy] contain world or waypoint data 22 | */ 23 | struct monster_state 24 | { 25 | uint8_t type; 26 | uint8_t path_index; 27 | uint8_t dead_timer; 28 | uint8_t monster_x; 29 | uint8_t monster_y; 30 | uint16_t monster_px; 31 | uint16_t monster_py; 32 | int8_t next_px; 33 | int8_t next_py; 34 | }; 35 | 36 | 37 | /* Game state information in kitchen-sink format 38 | * (Refactor me please!!!) 39 | */ 40 | struct game_state { 41 | uint8_t quit; 42 | uint8_t tick; 43 | uint8_t dave_tick; 44 | uint8_t current_level; 45 | uint8_t lives; 46 | uint32_t score; 47 | uint8_t view_x; 48 | uint8_t view_y; 49 | int8_t dave_x; 50 | int8_t dave_y; 51 | uint8_t dave_dead_timer; 52 | uint16_t dbullet_px; 53 | uint16_t dbullet_py; 54 | int8_t dbullet_dir; 55 | uint16_t ebullet_px; 56 | uint16_t ebullet_py; 57 | int8_t ebullet_dir; 58 | int16_t dave_px; 59 | int16_t dave_py; 60 | uint8_t on_ground; 61 | int8_t scroll_x; 62 | int8_t last_dir; 63 | 64 | uint8_t dave_right; 65 | uint8_t dave_left; 66 | uint8_t dave_jump; 67 | uint8_t dave_fire; 68 | uint8_t dave_down; 69 | uint8_t dave_up; 70 | uint8_t dave_climb; 71 | uint8_t dave_jetpack; 72 | uint8_t jetpack_delay; 73 | uint8_t jump_timer; 74 | uint8_t try_right; 75 | uint8_t try_left; 76 | uint8_t try_jump; 77 | uint8_t try_fire; 78 | uint8_t try_jetpack; 79 | uint8_t try_down; 80 | uint8_t try_up; 81 | uint8_t check_pickup_x; 82 | uint8_t check_pickup_y; 83 | uint8_t check_door; 84 | uint8_t can_climb; 85 | uint8_t collision_point[9]; 86 | uint8_t trophy; 87 | uint8_t gun; 88 | uint8_t jetpack; 89 | 90 | struct monster_state monster[5]; 91 | 92 | struct dave_level level[10]; 93 | }; 94 | 95 | 96 | /* Game asset structure 97 | * Only tileset data for now 98 | * Could include music/sounds, etc 99 | */ 100 | struct game_assets { 101 | SDL_Texture *graphics_tiles[158]; 102 | }; 103 | 104 | 105 | /* Forward declarations */ 106 | void init_game(struct game_state *); 107 | void init_assets(struct game_assets *, SDL_Renderer *); 108 | void check_input(struct game_state *); 109 | void update_game(struct game_state *); 110 | void render(struct game_state *, SDL_Renderer *, struct game_assets *); 111 | void check_collision(struct game_state *); 112 | void clear_input(struct game_state *); 113 | void pickup_item(struct game_state *, uint8_t, uint8_t); 114 | void start_level(struct game_state *); 115 | void update_dbullet(struct game_state *); 116 | void update_ebullet(struct game_state *); 117 | void verify_input(struct game_state *); 118 | void move_dave(struct game_state *); 119 | void move_monsters(struct game_state *); 120 | void fire_monsters(struct game_state *); 121 | void scroll_screen(struct game_state *); 122 | void apply_gravity(struct game_state *); 123 | void update_level(struct game_state *); 124 | void restart_level(struct game_state *); 125 | uint8_t update_frame(struct game_state *, uint8_t, uint8_t); 126 | void draw_world(struct game_state *, struct game_assets *, SDL_Renderer *); 127 | void draw_dave(struct game_state *, struct game_assets *, SDL_Renderer *); 128 | void draw_dave_bullet(struct game_state *, struct game_assets *, SDL_Renderer *); 129 | void draw_monster_bullet(struct game_state *, struct game_assets *, SDL_Renderer *); 130 | void draw_monsters(struct game_state *, struct game_assets *, SDL_Renderer *); 131 | void draw_ui(struct game_state *, struct game_assets *, SDL_Renderer *); 132 | 133 | uint8_t is_clear(struct game_state *, uint16_t, uint16_t, uint8_t); 134 | uint8_t is_visible(struct game_state *, uint16_t); 135 | void add_score(struct game_state *, uint16_t); 136 | #endif 137 | --------------------------------------------------------------------------------