/* *-------------------------------------- * Program Name: Fire And Flames * Author: slimeenergy * License: N/A * Description: CC25 Entry: Fire And Flames *-------------------------------------- */ /* Keep these headers */ #include #include #include #include #include #include #include #include /* Standard headers (recommended) */ #include #include #include #include //https://stackoverflow.com/questions/3437404/min-and-max-in-c #define max(a, b) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ _a > _b ? _a : _b; }) #define min(a, b) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ _a < _b ? _a : _b; }) #include "gfx/gfx.h" #define LEVEL_WIDTH 16 #define LEVEL_LENGTH 12 #define LEVEL_HEIGHT 3 #define LEVEL_COUNT 8 #define SPRITE_SIZE 32 #define LEVEL_X_OFFSET 0 #define LEVEL_Y_OFFSET 0 #define LEVEL_RENDER_WIDTH 256 #define LEVEL_RENDER_HEIGHT 240 #define CAMERA_OFFSET_X (LEVEL_RENDER_WIDTH / 2) + (SPRITE_SIZE / 2) #define CAMERA_OFFSET_Y (LEVEL_RENDER_HEIGHT / 2) + (SPRITE_SIZE / 2) #define TILE_COUNT 10 //intro animation #define SCENE_INTRO 0 //game scene #define SCENE_GAME 1 //level select scene #define SCENE_MENU 2 //win/lose scene #define SCENE_LEVEL_END 3 //game win scene #define SCENE_YOU_WIN 4 //tutorial scene #define SCENE_TUTORIAL 5 //end screen animation counter #define SCENE_ANIMATION_COUNTER 240 #define SPEED 2 #define ANIMATION_WALKING_SPEED 10 #define ANIMATION_REGULAR_SPEED 50 #define TICK_SPEED 40 //The 0th tile in the tileset is planks, but that would actually be tile ID 1. It's all offset because of the nothing tile #define NOTHING_TILE 0 #define FIRE_TILE 5 #define ASH_TILE 6 #define ASH_HOLE_TILE 7 #define STAIRS_DOWN_TILE 8 #define STAIRS_UP_TILE 9 #define FIRE_ANIMATION_SPEED 12 /* //1 in X chance for fire to spread along the current floor (X/Y axis) in a fire tick #define FIRE_SPREAD_CURRENT_CHANCE 10 //1 in X chance for fire to spread along to new floor (Z axis) in a fire tick #define FIRE_SPREAD_NEW_CHANCE 20 //1 in X chance for fire to not be updated anyway (good for performance) #define DORMANT_FIRE_CHANCE 4*/ //1 in X chance for fire to spread along the current floor (X/Y axis) in a fire tick #define FIRE_SPREAD_CURRENT_CHANCE 6 //1 in X chance for fire to spread along to new floor (Z axis) in a fire tick #define FIRE_SPREAD_NEW_CHANCE 9 //1 in X chance for fire to not be updated anyway (good for performance) #define DORMANT_FIRE_CHANCE 2 //1 in X chance for fire to burn out #define BURN_OUT_FIRE_CHANCE 5 #define WEAPON_LIGHTER 0 #define WEAPON_GRENADE 1 #define WEAPON_FLAMETHROWER 2 #define WEAPON_SHAPEDCHARGE 3 #define WEAPON_RPG7 4 //how long the weapon switch animation will last #define WEAPON_SWITCH_ANIMATION 10 #define MM_X 232 #define MM_Y 50 #define MM_W 48 #define MM_H 36 #define W_X 240 #define W_Y 130 #define W_W 64 #define W_H 64 //x/y offset of isometric view of level in level select #define ISO_X 320 / 2 - 20 #define ISO_Y 50 #define ISO_W 16 #define ISO_H 8 #define SAVE_FILE_NAME "TTFAFDAT" #define MAX_HEALTH 6 #define FIRE_DAMAGE 2 #define ASH_DAMAGE 1 #define SUICIDE_MAX_COUNTER 160 //how many frames until the minimap updates //should be a while, function is slight lag. #define MINIMAP_UPDATE_SPEED 100 //number of frames you have where your character is immune after being damaged #define I_FRAMES TICK_SPEED #define EMPTY_LEVEL \ (level_t) \ { \ "Empty", 75, &player, &projectile, 0, 0, 0, \ { \ {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, \ { \ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } \ } \ } \ } /* Structs */ typedef struct { bool moving_right; uint8_t animation_counter; bool animation_up; int24_t x; int24_t y; uint8_t z; //player weapon uint8_t weapon_index; //0=north,1=east,2=south,3=west uint8_t last_moved; uint8_t health; uint8_t health_timer; } player_t; typedef struct { //if active is false, this projectile has already gone off or died. bool active; int24_t x; int24_t y; uint8_t z; int8_t velocity_x; int8_t velocity_y; uint8_t life; //what weapon this projectile was shot from //this will be used to determine what its effects are uint8_t weapon_type; } projectile_t; typedef struct { char *name; //100 = 100% damage needed to beat level //... //0 = 0% damage needed to beat level uint8_t percentage_damage_required; //player pointer player_t *player; //there can only be one projectile at once projectile_t *projectile; int24_t spawnX; int24_t spawnY; uint8_t spawnZ; uint8_t tiles[LEVEL_HEIGHT][LEVEL_LENGTH][LEVEL_WIDTH]; } level_t; typedef struct { //game running or not bool running; //current level level_t current_level; //current scene uint8_t scene; //message to be displayed char *message; //The floor we're viewing on the minimap uint8_t viewing_floor; //Number of flammable tiles that the current level loaded in with. //Use this to see how many have been burnt for the destruction meter uint24_t flammable_tiles; //Number of tiles that have been counted as burnt. uint24_t burnt_tiles; //whether the player has surpassed the damage threshold in the current level bool won; //whether you moved in menu bool moved_in_menu; //whether we should re-render the level on the level_select screen bool level_select_rerender; //counters uint8_t tick_counter; int8_t fire_animation_counter; uint24_t message_counter; uint8_t level_select_counter; //what level we're on in level select menu uint8_t weapon_switch_counter; //animation for weapon switches uint8_t weapon_last_equipped; //also to do with the animation uint8_t scene_animation; //# of scene we are in in an animation with scenes uint8_t scene_animation_counter; //counter for the above } game_t; //save data typedef struct { uint8_t unlocked_level_count; uint8_t best_scores[LEVEL_COUNT]; //if stored_level_count is not equal to LEVEL_COUNT, this save file is old and should be replaced uint8_t stored_level_count; bool done_tutorial; } save_t; /* Put your function prototypes here */ void update(); void render(); void update_game(); void render_game(); void update_projectiles(); void update_fire_tick(); void render_life(); void render_current_layer(); void render_minimap(); void update_menu(); void render_menu(); void update_win_screen(); void render_win_screen(); void update_intro(); void render_intro(); void update_tutorial(); void render_tutorial(); void update_level_end(); void render_level_end(); void render_loading(); void screen_shake(uint8_t length, uint8_t magnitude); void screen_shake_blit(uint8_t length, uint8_t magnitude); int24_t add_camera_offset(int24_t distance, int24_t camera_distance); int24_t get_camera_x(); int24_t get_camera_y(); int24_t get_tile_position(int24_t x); //bounds a tile position to the map size along the X axis int24_t map_bounds_x(int24_t x); //bounds a tile position to the map size along the Y axis int24_t map_bounds_y(int24_t y); void set_tile_on_fire(uint8_t x, uint8_t y, uint8_t z); void move_player(int24_t x, int24_t y); void hurt_player(uint8_t amt); void show_message(char *message); void load_level(level_t *level); void update_minimap_sprite(); //updates minimap as well as tile void set_tile(uint8_t tile, uint8_t x, uint8_t y, uint8_t z); bool object_map_colliding(int24_t x, int24_t y, uint8_t z, int24_t w, int24_t h); bool player_colliding(); bool player_collision(int24_t x, int24_t y, int24_t w, int24_t h); void update_player_colliding_special_tiles(); bool collision(int24_t x, int24_t y, int24_t w, int24_t h, int24_t x2, int24_t y2, int24_t w2, int24_t h2); bool inside(int24_t x, int24_t y, int24_t w, int24_t h, int24_t x2, int24_t y2, int24_t w2, int24_t h2); bool insideOrEqualTo(int24_t x, int24_t y, int24_t w, int24_t h, int24_t x2, int24_t y2, int24_t w2, int24_t h2); bool renderable(int24_t x, int24_t y, int24_t w, int24_t h); uint8_t get_rating_color(uint8_t score); uint8_t get_score(uint8_t percent_dmg, uint8_t player_health); char *get_rating(uint8_t score); bool enter_pressed(); void save_data(); void load_data(); void change_scene(uint8_t target); /* Put all your globals here */ //globals globals globals //Flammable: Wood floor, wood wall, grass //Not flammable: Brick wall, fire const bool flammable[TILE_COUNT] = {false, true, true, false, true, false, false, false, true, true}; //these tiles are either collidable or not const bool collidable[TILE_COUNT] = {false, false, true, true, false, false, false, false, false, false}; //these tiles will have a pseudo isometric roof thing const bool ceilings[TILE_COUNT] = {false, false, true, true, false, false, false, false, false, false}; //the tiles that will have the above will have these colors const uint8_t ceiling_colors[TILE_COUNT] = {0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; //minimap colors const uint8_t minimap_colors[TILE_COUNT] = {0x00, 0x41, 0x81, 0xA0, 0xA3, 0xE0, 0xAA, 0xAA, 0x61, 0x61}; //minimap colors but a bit darker - for use with isometric preview const uint8_t minimap_colors_dark[TILE_COUNT] = {0x00, 0x21, 0x61, 0x80, 0x83, 0xC0, 0xAB, 0xAB, 0x41, 0x41}; //color 0 is the dark color //color 1 is the light color //color 2 is the neutral color const uint8_t isometric_colors[LEVEL_HEIGHT][TILE_COUNT][3] = { //Y level 0 { {0, 0, 0}, //nothing {0x01, 0x21, 0x21}, //floor {0x41, 0x61, 0x62}, //wood wall {0x00, 0x20, 0x40}, //red wall {0x63, 0x84, 0x84}, //grass {0, 0, 0}, //fire {0x29, 0x4A, 0x4A}, //ash {0x29, 0x4A, 0x4A}, //ash hole {0x01, 0x21, 0x21}, //stairs {0x01, 0x21, 0x21}, //stairs }, //Y level 1 { {0, 0, 0}, //nothing {0x22, 0x42, 0x42}, //floor {0x81, 0xA2, 0x82}, //wood wall {0x60, 0xA0, 0x80}, //red wall {0xA5, 0xC5, 0xC5}, //grass {0xE0, 0xE0, 0xE0}, //fire {0x93, 0xB4, 0xB4}, //ash {0x93, 0xB4, 0xB4}, //ash hole {0x22, 0x42, 0x42}, //stairs {0x22, 0x42, 0x42}, //stairs }, //Y level 2 { {0, 0, 0}, //nothing {0x62, 0x63, 0x63}, //floor {0xC3, 0xC4, 0xA3}, //wood wall {0xC0, 0xE1, 0xE0}, //red wall {0xAD, 0xCE, 0xCE}, //grass {0xE0, 0xE0, 0xE0}, //fire {0xB5, 0xDE, 0xDE}, //ash {0xB5, 0xDE, 0xDE}, //ash hole {0x62, 0x63, 0x63}, //stairs {0x62, 0x63, 0x63}, //stairs }}; //if you're moving from one floor to another and the destination is a tile that moves you, abort. const static bool tiles_that_move_you[TILE_COUNT] = {false, false, false, false, false, false, false, true, true, true}; //the length and width of a projectile object from a respective weapon const static uint8_t projectile_sizes[items_num_tiles] = {4, 8, 12, 12, 8}; //how powerful your weapons are at shooting their projectiles const int8_t weapon_velocities[items_num_tiles] = {8, 22, 80, 10, 80}; //how long a weapons' projectile lasts before being killed const uint8_t weapon_lives[items_num_tiles] = {12, 240, 35, 70, 35}; //just planning on leaving the player and projectile objects as globals for now //only one player object in the game player_t player = {true, 0, false, 0, 0, 0, 0, 1, MAX_HEALTH, 0}; //only one projectile in the game projectile_t projectile = {false, 0, 0, 0, 0, 0, 0, 0}; //all levels in the game //Old Level: {"Simple House", 80, &player, &projectile, 2, 3, 0, {{{3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, {4, 4, 4, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2}, {4, 4, 4, 4, 2, 9, 2, 2, 2, 1, 1, 2, 1, 2, 1, 2}, {4, 4, 4, 4, 2, 1, 1, 2, 2, 2, 2, 2, 1, 2, 1, 2}, {4, 4, 4, 4, 2, 2, 2, 2, 2, 1, 1, 2, 1, 2, 1, 2}, {4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2}, {4, 4, 4, 4, 1, 1, 1, 1, 2, 2, 1, 2, 1, 2, 1, 2}, {4, 4, 4, 4, 2, 2, 2, 1, 2, 2, 2, 2, 1, 2, 1, 2}, {4, 4, 4, 4, 2, 9, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2}, {4, 4, 4, 4, 2, 1, 2, 1, 1, 2, 2, 2, 2, 2, 1, 2}, {4, 4, 4, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2}, {3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}}, {{2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2}, {2, 9, 2, 3, 1, 8, 1, 3, 2, 1, 1, 2, 2, 1, 1, 2}, {2, 1, 2, 3, 1, 1, 1, 3, 2, 1, 1, 2, 2, 1, 1, 2}, {2, 1, 2, 3, 1, 1, 1, 3, 2, 1, 1, 2, 2, 1, 1, 2}, {2, 1, 2, 3, 3, 3, 3, 3, 2, 1, 1, 2, 2, 1, 1, 2}, {2, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 1, 1, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 2}, {2, 2, 2, 2, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}}, {{1, 1, 1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {1, 1, 1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {1, 1, 1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {1, 8, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}}, level_t all_levels[LEVEL_COUNT] = { {"Simple House", 80, &player, &projectile, 1, 5, 0, {{{2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, {4, 4, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4}, {4, 4, 4, 4, 2, 9, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4}, {4, 4, 4, 4, 2, 1, 2, 2, 2, 2, 2, 1, 2, 1, 2, 4}, {4, 4, 4, 4, 2, 2, 2, 1, 1, 1, 2, 1, 2, 1, 2, 4}, {4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 4}, {4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 4}, {4, 4, 4, 4, 2, 1, 2, 2, 2, 2, 2, 1, 2, 1, 2, 4}, {4, 4, 4, 4, 2, 1, 2, 9, 1, 1, 1, 1, 2, 1, 2, 4}, {4, 4, 4, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4}, {2, 4, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4}, {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}}, {{0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {2, 2, 2, 3, 9, 1, 8, 3, 2, 2, 2, 2, 2, 2, 2, 0}, {2, 1, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 2, 0}, {2, 1, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0}, {2, 1, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 1, 1, 1, 8, 2, 1, 9, 1, 2, 0}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 0}, {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 3, 1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 3, 8, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 3, 1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 8, 1, 3}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 1, 1, 3}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 1, 1, 3}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 0}}}}, {"Research Station", 85, &player, &projectile, 7, 2, 0, {{{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, {4, 2, 6, 6, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, {4, 2, 6, 6, 2, 4, 4, 4, 4, 4, 4, 2, 2, 2, 4, 4}, {4, 2, 2, 6, 2, 4, 4, 4, 4, 4, 4, 6, 6, 2, 4, 4}, {4, 2, 9, 9, 2, 4, 2, 6, 2, 2, 4, 6, 1, 2, 4, 4}, {4, 2, 1, 1, 1, 4, 6, 1, 9, 6, 4, 2, 6, 6, 4, 4}, {4, 2, 1, 1, 1, 4, 1, 1, 1, 1, 4, 2, 1, 6, 4, 4}, {4, 2, 1, 1, 2, 4, 2, 6, 6, 2, 6, 4, 4, 4, 4, 4}, {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 4, 4, 4, 4, 4}, {4, 4, 4, 4, 4, 2, 9, 1, 1, 6, 1, 9, 2, 4, 4, 4}, {4, 4, 4, 4, 4, 2, 9, 1, 1, 1, 1, 9, 2, 4, 4, 4}, {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0}, {0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0}, {0, 2, 1, 1, 1, 0, 2, 1, 1, 2, 0, 1, 1, 1, 0, 0}, {0, 2, 1, 1, 1, 0, 1, 9, 1, 1, 0, 1, 1, 1, 0, 0}, {0, 2, 1, 1, 1, 0, 1, 8, 1, 1, 0, 1, 1, 1, 0, 0}, {0, 2, 8, 1, 2, 0, 2, 1, 1, 2, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 2, 1, 1, 8, 6, 1, 1, 2, 0, 0, 0}, {0, 0, 0, 0, 0, 2, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 0, 2, 1, 1, 2, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 0, 1, 1, 8, 1, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 0, 2, 1, 1, 2, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}}, {"Catwalk", 80, &player, &projectile, 8, 7, 2, {{{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, {4, 2, 1, 1, 1, 1, 3, 3, 3, 3, 1, 1, 1, 1, 2, 4}, {4, 1, 1, 1, 1, 1, 3, 1, 9, 3, 1, 1, 1, 1, 1, 4}, {4, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 1, 1, 1, 4}, {4, 2, 9, 1, 1, 2, 3, 2, 2, 3, 2, 1, 1, 9, 2, 4}, {4, 1, 1, 1, 1, 1, 3, 2, 2, 3, 1, 1, 1, 1, 1, 4}, {4, 1, 1, 1, 1, 2, 3, 2, 2, 3, 2, 1, 1, 1, 1, 4}, {4, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 1, 1, 1, 4}, {4, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 1, 1, 1, 4}, {4, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 1, 1, 1, 4}, {4, 2, 1, 1, 1, 1, 3, 3, 3, 3, 1, 1, 1, 1, 2, 4}, {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 2, 0, 0, 0, 0, 8, 1, 1, 8, 0, 0, 0, 0, 2, 0}, {0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0}, {0, 2, 1, 1, 1, 2, 1, 2, 2, 1, 2, 1, 1, 1, 2, 0}, {0, 0, 0, 0, 1, 2, 1, 2, 2, 1, 2, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 2, 1, 2, 2, 1, 2, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 9, 1, 1, 1, 1, 9, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0}, {0, 2, 0, 0, 0, 0, 8, 1, 1, 8, 0, 0, 0, 0, 2, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0}, {0, 2, 8, 1, 1, 2, 0, 2, 2, 0, 2, 1, 1, 8, 2, 0}, {0, 0, 0, 0, 1, 1, 0, 2, 2, 0, 1, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 2, 0, 2, 2, 0, 2, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}}, {"Hospital", 80, &player, &projectile, 7, 11, 0, {{{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, {3, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}, {3, 1, 2, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 1, 3}, {3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 3}, {3, 3, 3, 3, 3, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 3}, {6, 6, 4, 4, 4, 4, 4, 1, 4, 4, 4, 4, 4, 6, 6, 6}, {6, 6, 6, 4, 4, 1, 1, 1, 1, 1, 4, 4, 4, 4, 6, 6}, {6, 6, 6, 4, 4, 1, 4, 4, 4, 1, 4, 4, 6, 6, 6, 6}, {6, 6, 4, 6, 4, 1, 4, 4, 4, 1, 4, 6, 4, 6, 6, 6}, {6, 6, 6, 4, 2, 1, 1, 1, 1, 1, 2, 6, 6, 6, 6, 6}, {6, 2, 6, 6, 6, 4, 1, 1, 1, 4, 6, 6, 6, 2, 6, 6}, {6, 6, 6, 6, 4, 4, 1, 1, 1, 4, 4, 6, 6, 6, 6, 6}}, {{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, {3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}, {3, 8, 2, 1, 2, 2, 2, 1, 2, 2, 2, 2, 1, 2, 9, 3}, {3, 1, 2, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 2, 1, 3}, {3, 3, 3, 3, 3, 2, 2, 1, 2, 2, 3, 3, 3, 3, 3, 3}, {0, 0, 0, 0, 3, 1, 1, 1, 1, 1, 3, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 3, 1, 1, 1, 1, 1, 3, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 3, 1, 1, 1, 1, 1, 3, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 3, 1, 1, 1, 1, 1, 3, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0}, {0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{1, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 1, 1, 3, 3, 3}, {3, 1, 3, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 3, 8, 3}, {3, 7, 3, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 3, 1, 3}, {3, 1, 3, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 3}, {1, 1, 1, 1, 3, 3, 3, 1, 3, 3, 3, 1, 1, 1, 1, 1}, {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}}, {"Log Cabin", 75, &player, &projectile, 8, 6, 0, {{{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2}, {4, 2, 4, 4, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4}, {4, 4, 4, 2, 4, 4, 2, 9, 1, 1, 1, 2, 4, 4, 4, 4}, {4, 2, 4, 4, 4, 4, 2, 1, 1, 1, 1, 2, 4, 4, 2, 4}, {4, 4, 4, 4, 2, 4, 2, 2, 1, 1, 2, 2, 4, 4, 4, 4}, {4, 4, 4, 4, 4, 4, 4, 2, 1, 1, 2, 4, 4, 4, 4, 4}, {4, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 4, 4, 4}, {4, 4, 4, 4, 4, 4, 2, 4, 4, 2, 4, 4, 4, 4, 4, 4}, {4, 4, 4, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, {4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 4, 4, 4, 4, 2, 4}, {4, 2, 4, 4, 4, 4, 4, 2, 4, 4, 4, 2, 4, 4, 4, 4}, {4, 4, 4, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4}, {0, 2, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 4}, {0, 4, 0, 2, 0, 0, 2, 1, 1, 1, 9, 2, 0, 0, 0, 0}, {4, 4, 4, 0, 0, 0, 2, 1, 1, 8, 1, 2, 0, 0, 2, 0}, {0, 4, 4, 0, 2, 0, 2, 2, 1, 1, 2, 2, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0}, {0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 2, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 2, 0}, {0, 2, 0, 0, 0, 0, 0, 2, 0, 4, 0, 2, 0, 0, 0, 0}, {0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {4, 4, 4, 4, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0}, {4, 4, 4, 4, 4, 0, 2, 8, 1, 1, 1, 2, 0, 4, 4, 0}, {0, 4, 4, 4, 4, 0, 2, 1, 1, 1, 1, 2, 0, 4, 4, 4}, {0, 0, 4, 4, 4, 4, 2, 1, 1, 1, 1, 2, 0, 0, 4, 0}, {4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0}, {4, 4, 4, 0, 0, 4, 4, 0, 0, 4, 4, 4, 4, 4, 0, 0}, {0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 4, 4, 0, 0}, {0, 0, 4, 4, 4, 4, 4, 0, 4, 4, 0, 0, 0, 4, 4, 0}, {0, 4, 0, 4, 4, 4, 0, 4, 0, 0, 0, 4, 4, 4, 4, 4}, {4, 4, 4, 4, 0, 0, 4, 4, 4, 4, 4, 4, 4, 0, 4, 0}, {0, 4, 4, 4, 4, 0, 4, 4, 4, 0, 0, 4, 0, 0, 0, 0}}}}, {"Wooden Bunker", 90, &player, &projectile, 7, 6, 2, {{{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, {2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 9, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2}, {2, 9, 2, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2}, {2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2}, {2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2}, {2, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2}, {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}}, {{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, {2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2}, {2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2}, {2, 2, 1, 8, 1, 2, 1, 9, 1, 1, 1, 1, 2, 1, 2, 2}, {2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2}, {2, 2, 2, 2, 1, 2, 2, 2, 1, 1, 2, 2, 2, 1, 2, 2}, {2, 1, 2, 2, 1, 2, 2, 2, 1, 1, 2, 2, 2, 1, 2, 2}, {2, 8, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2}, {2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2}, {2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2}, {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}}, {{6, 6, 5, 5, 5, 4, 4, 4, 2, 4, 5, 6, 6, 6, 6, 6}, {6, 6, 5, 4, 4, 4, 3, 3, 3, 4, 4, 5, 6, 6, 6, 6}, {6, 5, 5, 4, 4, 4, 3, 8, 3, 4, 4, 4, 5, 5, 6, 6}, {5, 2, 4, 2, 4, 4, 3, 1, 3, 4, 4, 4, 2, 5, 6, 6}, {5, 5, 4, 4, 4, 4, 4, 4, 4, 2, 4, 4, 4, 5, 5, 6}, {6, 5, 4, 4, 4, 4, 2, 4, 4, 4, 4, 4, 5, 5, 6, 6}, {5, 5, 5, 4, 2, 4, 4, 4, 4, 4, 5, 5, 4, 5, 6, 6}, {5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 5}, {6, 6, 6, 5, 5, 5, 5, 4, 2, 4, 4, 5, 6, 6, 6, 6}, {6, 5, 6, 6, 6, 6, 6, 6, 5, 5, 5, 6, 5, 6, 6, 6}, {6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6}, {6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6}}}}, {"Holes", 65, &player, &projectile, 7, 6, 2, {{{2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2}, {2, 1, 1, 9, 1, 1, 9, 1, 1, 9, 1, 1, 9, 1, 1, 2}, {2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 2}, {2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2}, {2, 1, 1, 9, 1, 1, 9, 1, 1, 9, 1, 1, 9, 1, 1, 2}, {2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 2}, {2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2}, {2, 1, 1, 9, 1, 1, 9, 1, 1, 9, 1, 1, 9, 1, 1, 2}, {2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 2}, {2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2}, {2, 1, 1, 9, 1, 1, 9, 1, 1, 9, 1, 1, 9, 1, 1, 2}, {2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 2}}, {{3, 3, 2, 3, 0, 0, 2, 3, 3, 3, 0, 0, 3, 3, 3, 2}, {3, 0, 0, 9, 0, 0, 9, 0, 0, 9, 0, 0, 9, 0, 0, 3}, {2, 0, 0, 3, 2, 3, 3, 0, 0, 3, 3, 3, 2, 0, 0, 3}, {3, 3, 2, 3, 0, 0, 2, 3, 3, 3, 0, 0, 3, 3, 3, 3}, {3, 0, 0, 9, 0, 0, 9, 0, 0, 9, 0, 0, 9, 0, 0, 3}, {2, 0, 0, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 0, 0, 2}, {3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 0, 0, 2, 3, 3, 3}, {3, 0, 0, 9, 0, 0, 9, 0, 0, 9, 0, 0, 9, 0, 0, 3}, {2, 0, 0, 3, 3, 2, 3, 0, 0, 3, 3, 3, 3, 0, 0, 3}, {3, 3, 3, 3, 0, 0, 3, 2, 3, 3, 0, 0, 2, 3, 3, 3}, {3, 0, 0, 9, 0, 0, 9, 0, 0, 9, 0, 0, 9, 0, 0, 2}, {2, 0, 0, 2, 3, 3, 3, 0, 0, 3, 2, 3, 3, 0, 0, 3}}, {{6, 6, 6, 6, 0, 0, 4, 6, 6, 6, 0, 0, 4, 4, 6, 6}, {6, 0, 0, 4, 0, 0, 4, 0, 0, 4, 0, 0, 4, 0, 0, 4}, {6, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4}, {4, 4, 4, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 4, 4}, {4, 0, 0, 4, 0, 0, 4, 0, 0, 4, 0, 0, 4, 0, 0, 4}, {4, 0, 0, 4, 4, 4, 4, 0, 0, 6, 4, 4, 4, 0, 0, 4}, {6, 4, 4, 4, 0, 0, 4, 4, 4, 6, 0, 0, 4, 4, 4, 4}, {4, 0, 0, 4, 0, 0, 4, 0, 0, 4, 0, 0, 4, 0, 0, 4}, {4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4}, {4, 4, 4, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 4, 4}, {6, 0, 0, 4, 0, 0, 4, 0, 0, 4, 0, 0, 4, 0, 0, 6}, {6, 0, 0, 6, 6, 4, 4, 0, 0, 4, 4, 6, 6, 0, 0, 6}}}}, {"Warehouse", 95, &player, &projectile, 0, 5, 0, {{{6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6}, {6, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6}, {6, 3, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 3, 6}, {1, 3, 1, 9, 1, 1, 1, 2, 1, 1, 1, 1, 9, 1, 3, 1}, {6, 1, 1, 1, 2, 2, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 2, 2, 1, 2, 1, 2, 2, 1, 2, 2, 1, 6}, {6, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 1, 6}, {1, 1, 2, 2, 1, 2, 2, 1, 2, 2, 2, 1, 1, 1, 1, 1}, {6, 3, 1, 9, 1, 2, 2, 1, 1, 1, 1, 1, 9, 1, 3, 1}, {6, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 6}, {6, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6}, {6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0}, {0, 3, 0, 0, 0, 0, 0, 2, 6, 0, 0, 0, 0, 0, 3, 0}, {0, 3, 0, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 0, 3, 0}, {0, 0, 0, 1, 6, 6, 1, 1, 1, 6, 2, 0, 1, 0, 0, 0}, {0, 0, 0, 1, 6, 6, 0, 1, 0, 6, 6, 0, 2, 2, 0, 0}, {0, 0, 2, 1, 0, 0, 0, 1, 2, 2, 0, 0, 2, 2, 0, 0}, {0, 0, 6, 1, 0, 2, 2, 1, 6, 2, 6, 0, 1, 0, 0, 0}, {0, 3, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 0, 3, 0}, {0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0}, {0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0}, {0, 3, 0, 0, 3, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 0}, {0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0}, {0, 3, 0, 0, 3, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 0}, {0, 3, 0, 0, 3, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 0}, {0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0}, {0, 3, 0, 0, 3, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 0}, {0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}}}; /// /*/ / / Spoilers Warning Below / / / / / / / / / / Spoilers Warning Below /*/ ///spoilers const char *ENDING_TEXTS[7] = { "You've done well.", "Everything is scorched.", "Your work is not done yet.", "Some things are still left standing...", "...you will have some new tools.", "Come later and finish this.", "The End"}; ///ended spoilers /*/ / / Spoilers over / / / / / / / / / / Spoilers over /*/ /// const char *weapon_names[items_num_tiles] = {"Lighter", "Napalm", "Flamethrower", "ShapedCharge", "RPG-7"}; //misc globals stored in game object game_t game = { true, //game running //blank level as placeholder below //we will load data into this EMPTY_LEVEL, SCENE_INTRO, //scene "", //message, 0, //viewing floor 0, 0, //flammable / burnt tiles false, //whether the win condition has been met false, //whether you moved in the menu true, //re-render on level select screen (SCENE_MENU) 0, 0, 0, 0, 0, 0, 0, 0 //counters }; save_t current_save = { 0, //unlocked level count {0, 0, 0}, //level highscores LEVEL_COUNT, //number of elements in the above array (used to detect incompatible saves) false //whether the tutorial has been completed yet }; //minimap sprite gfx_TempSprite(minimap, MM_W, MM_H); gfx_TempSprite(player_tile_0_flip, 32, 32); gfx_TempSprite(player_tile_1_flip, 32, 32); gfx_TempSprite(weapon_animating_from, 64, 64); gfx_TempSprite(weapon_animating_to, 64, 64); gfx_TempSprite(items_downscaled_tile_2_flip, 16, 16); int main(void) { gfx_Begin(); gfx_SetDrawBuffer(); gfx_SetTransparentColor(0xF8); gfx_FlipSpriteY(items_downscaled_tile_2, items_downscaled_tile_2_flip); gfx_FlipSpriteY(player_tile_0, player_tile_0_flip); gfx_FlipSpriteY(player_tile_1, player_tile_1_flip); gfx_SetTextTransparentColor(0); gfx_SetTextFGColor(0xFF); gfx_SetTextBGColor(0x00); srand(rtc_Time()); game.message_counter = 500; load_data(); //legacy line that sent you straight into a level //load_level(&all_levels[0]); //preparing the intro animation game.scene_animation_counter = SCENE_ANIMATION_COUNTER; while (game.running) { kb_Scan(); update(); render(); if (kb_Data[6] & kb_Clear) { game.running = false; } //for the isometric level select, we're not swapping the draw buffer if (game.scene != SCENE_MENU) { gfx_SwapDraw(); } } gfx_End(); save_data(); return 0; } /* Put other functions here */ void update() { if (game.scene == SCENE_GAME) { update_game(); } else if (game.scene == SCENE_LEVEL_END) { update_level_end(); } else if (game.scene == SCENE_MENU) { update_menu(); } else if (game.scene == SCENE_YOU_WIN) { update_win_screen(); } else if (game.scene == SCENE_INTRO) { update_intro(); } else if (game.scene == SCENE_TUTORIAL) { update_tutorial(); } } void render() { //exclude the menu's buffer from being drawn over if (game.scene != SCENE_MENU) { gfx_FillScreen(0x00); } if (game.scene == SCENE_GAME) { render_game(); } else if (game.scene == SCENE_LEVEL_END) { render_level_end(); } else if (game.scene == SCENE_MENU) { render_menu(); } else if (game.scene == SCENE_YOU_WIN) { render_win_screen(); } else if (game.scene == SCENE_INTRO) { render_intro(); } else if (game.scene == SCENE_TUTORIAL) { render_tutorial(); } } void load_level(level_t *level) { uint8_t k, i, j; //copy over all level data memcpy(&game.current_level, level, sizeof(*level)); //copy over tile data memcpy(&game.current_level.tiles, &level->tiles, sizeof(level->tiles)); game.current_level.player->x = SPRITE_SIZE * level->spawnX; game.current_level.player->y = SPRITE_SIZE * level->spawnY; game.flammable_tiles = 0; game.burnt_tiles = 0; game.current_level.player->z = level->spawnZ; game.current_level.player->health = MAX_HEALTH; game.current_level.player->health_timer = 0; game.current_level.player->weapon_index = 0; game.won = false; game.current_level.projectile->active = false; game.current_level.projectile->life = 0; for (k = 0; k < LEVEL_HEIGHT; k++) { for (i = 0; i < LEVEL_LENGTH; i++) { for (j = 0; j < LEVEL_WIDTH; j++) { if (flammable[game.current_level.tiles[k][i][j]] || game.current_level.tiles[k][i][j] == ASH_TILE || game.current_level.tiles[k][i][j] == ASH_HOLE_TILE || game.current_level.tiles[k][i][j] == FIRE_TILE) { game.flammable_tiles++; } } } } game.viewing_floor = game.current_level.player->z; //scene animation counter is used as a counter for the suicide bar in the game menu game.scene_animation_counter = 0; update_minimap_sprite(); change_scene(SCENE_GAME); } void update_game() { //player's velocity after key presses int24_t v_x = 0, v_y = 0; //whether the player ran/moved or not bool ran = false; static bool second_debounced = false, plus_minus_debounced = false; //continue to level end screen if (enter_pressed() && (game.won || game.current_level.player->health <= 0)) { game.message_counter = 500; change_scene(SCENE_LEVEL_END); //reuse message_counter for the animation on the level_end scene } //things that will run even when you're dead: if (++game.fire_animation_counter >= FIRE_ANIMATION_SPEED) { game.fire_animation_counter = 0; } //move up/down in minimap if (kb_Data[6] & kb_Add) { if (plus_minus_debounced == false) { if (game.viewing_floor < LEVEL_HEIGHT - 1) { game.viewing_floor++; update_minimap_sprite(); } } plus_minus_debounced = true; } else if (kb_Data[6] & kb_Sub) { if (plus_minus_debounced == false) { if (game.viewing_floor > 0) { game.viewing_floor--; update_minimap_sprite(); } } plus_minus_debounced = true; } else { plus_minus_debounced = false; } //if player is dead if (game.current_level.player->health <= 0) { //don't continue rest of update function game.current_level.player->health_timer = 0; return; } //change weapons if (kb_Data[1] & kb_2nd) { //only let player switch weapons if the animation isn't going if (second_debounced == false && game.weapon_switch_counter <= 0) { //prep the old weapon animation sprites gfx_ScaleSprite(items_tiles[game.current_level.player->weapon_index], weapon_animating_from); //change weapon game.current_level.player->weapon_index = ++game.current_level.player->weapon_index % 5; //prep the new weapon animation sprite gfx_ScaleSprite(items_tiles[game.current_level.player->weapon_index], weapon_animating_to); //set the animation timer game.weapon_switch_counter = WEAPON_SWITCH_ANIMATION; } second_debounced = true; } else { second_debounced = false; } //fire projectiles if (kb_Data[2] & kb_Alpha) { //projectile if (game.current_level.projectile->life == 0) { //velocity will be set to these set variables //magnitude is how fast the weapon will shoot its projectile out int8_t set_velocity_x, set_velocity_y, magnitude; magnitude = weapon_velocities[game.current_level.player->weapon_index]; game.current_level.projectile->y = game.current_level.player->y + 18 + (game.current_level.player->last_moved == 2 ? 15 : 0); game.current_level.projectile->x = game.current_level.player->x + (game.current_level.player->moving_right ? 24 : 12); game.current_level.projectile->weapon_type = game.current_level.player->weapon_index; game.current_level.projectile->life = weapon_lives[game.current_level.player->weapon_index]; switch (game.current_level.player->last_moved) { //north case 0: set_velocity_y = -magnitude + 1; set_velocity_x = randInt(-magnitude / 8, magnitude / 8); break; //east case 1: set_velocity_x = magnitude; set_velocity_y = randInt(-magnitude / 8, magnitude / 8) + 1; break; //south case 2: set_velocity_y = magnitude + 1; set_velocity_x = randInt(-magnitude / 8, magnitude / 8); break; //west case 3: set_velocity_x = -magnitude; set_velocity_y = randInt(-magnitude / 8, magnitude / 8) + 1; break; } game.current_level.projectile->velocity_x = set_velocity_x; game.current_level.projectile->velocity_y = set_velocity_y; game.current_level.projectile->z = game.current_level.player->z; game.current_level.projectile->active = true; switch (game.current_level.projectile->weapon_type) { case WEAPON_LIGHTER: screen_shake(1, 2); break; case WEAPON_GRENADE: screen_shake(1, 5); break; case WEAPON_FLAMETHROWER: screen_shake(10, 2); break; case WEAPON_SHAPEDCHARGE: screen_shake(1, 2); break; case WEAPON_RPG7: screen_shake(15, 2); break; } } } //update projectiles update_projectiles(); if (kb_Data[7] & kb_Up) { v_y += -SPEED; } if (kb_Data[7] & kb_Down) { v_y += SPEED; } if (kb_Data[7] & kb_Right) { v_x += SPEED; } if (kb_Data[7] & kb_Left) { v_x += -SPEED; } //move the player if (v_x != 0 || v_y != 0) { if (v_x < 0) { game.current_level.player->moving_right = false; game.current_level.player->last_moved = 3; } else if (v_x > 0) { game.current_level.player->moving_right = true; game.current_level.player->last_moved = 1; } if (v_y < 0) { game.current_level.player->last_moved = 0; } else if (v_y > 0) { game.current_level.player->last_moved = 2; } move_player(v_x, v_y); ran = true; } //Rest of the counters if (++game.current_level.player->animation_counter > (ran ? ANIMATION_WALKING_SPEED : ANIMATION_REGULAR_SPEED)) { game.current_level.player->animation_up = !game.current_level.player->animation_up; game.current_level.player->animation_counter = 0; } if (++game.tick_counter >= TICK_SPEED) { game.tick_counter = 0; update_fire_tick(); } if (game.weapon_switch_counter > 0) { game.weapon_switch_counter--; } if (game.current_level.player->health_timer > 0) { game.current_level.player->health_timer--; } if (kb_Data[1] & kb_Del) { if (game.scene_animation_counter < SUICIDE_MAX_COUNTER) { game.scene_animation_counter++; } if (game.scene_animation_counter >= SUICIDE_MAX_COUNTER) { screen_shake_blit(10, 5); hurt_player(game.current_level.player->health); show_message("Committed suicide!"); } } else { game.scene_animation_counter = 0; } } void render_game() { render_current_layer(); gfx_SetColor(0x20); gfx_FillRectangle_NoClip(0, 0, 32, 240); gfx_FillRectangle_NoClip(32, 0, 288, 32); gfx_FillRectangle_NoClip(32, 208, 288, 32); gfx_FillRectangle_NoClip(223, 32, 97, 176); gfx_SetColor(0xC0); gfx_Rectangle_NoClip(32, 32, 192, 176); //burn color gfx_SetColor(0xE2); gfx_FillRectangle_NoClip(5, SPRITE_SIZE, SPRITE_SIZE - 10, 240 - SPRITE_SIZE * 2); //unburnt color gfx_SetColor(0x41); gfx_FillRectangle_NoClip(5, SPRITE_SIZE, SPRITE_SIZE - 10, ((240 - SPRITE_SIZE * 2) * (game.flammable_tiles - game.burnt_tiles)) / game.flammable_tiles); //outline gfx_SetColor(0xC0); gfx_Rectangle_NoClip(5, SPRITE_SIZE, SPRITE_SIZE - 10, 240 - SPRITE_SIZE * 2); //damage required if (game.burnt_tiles * 100 / game.flammable_tiles >= game.current_level.percentage_damage_required) { gfx_SetColor(0xFF); gfx_SetTextFGColor(0x07); game.won = true; } else { gfx_SetColor(0xE0); gfx_SetTextFGColor(0xE5); } gfx_HorizLine_NoClip(3, SPRITE_SIZE + ((240 - SPRITE_SIZE * 2) - (game.current_level.percentage_damage_required * (240 - SPRITE_SIZE * 2) / 100)) - 1, SPRITE_SIZE - 6); gfx_PrintStringXY("%", 12, SPRITE_SIZE + ((240 - SPRITE_SIZE * 2) - (game.current_level.percentage_damage_required * (240 - SPRITE_SIZE * 2) / 100)) - 5); //text overlay gfx_SetTextFGColor(0xFF); gfx_SetTextScale(1, 1); gfx_PrintStringXY("DMG", 5, 240 / 2 - 8 / 2); if (game.message_counter > 0) { gfx_SetTextScale(1, 1); gfx_SetTextFGColor(0xFF); gfx_PrintStringXY(game.message, 32 + (SPRITE_SIZE * 6 / 2 - gfx_GetStringWidth(game.message) / 2), SPRITE_SIZE - 10); game.message_counter--; } render_life(); render_minimap(); //weapon rendering if (game.weapon_switch_counter > 0) { //animation progress is WEAPON_SWITCH_ANIMATION-game.weapon_switch_counter / WEAPON_SWITCH_ANIMATION gfx_SetClipRegion(W_X, W_Y, W_X + W_W, W_Y + W_H); //we're using a tempsprite to store these scaled up sprites we're animating between because there's no //clipped routine for scaled transparent sprites, and it'd probably lag a ton if there was gfx_TransparentSprite(weapon_animating_from, W_X + (((WEAPON_SWITCH_ANIMATION - game.weapon_switch_counter) * 64) / WEAPON_SWITCH_ANIMATION), W_Y); gfx_TransparentSprite(weapon_animating_to, W_X - 64 + (((WEAPON_SWITCH_ANIMATION - game.weapon_switch_counter) * 64) / WEAPON_SWITCH_ANIMATION), W_Y); gfx_SetClipRegion(0, 0, 320, 240); } else { gfx_ScaledTransparentSprite_NoClip(items_tiles[game.current_level.player->weapon_index], W_X, W_Y, 4, 4); } //weapon outline gfx_SetColor(0xC0); gfx_Rectangle_NoClip(W_X, W_Y, W_W, W_H); //weapon name gfx_PrintStringXY(weapon_names[game.current_level.player->weapon_index], W_X + (W_W / 2 - gfx_GetStringWidth(weapon_names[game.current_level.player->weapon_index]) / 2), W_Y - 10); gfx_PrintStringXY("[2ND] CHANGE", W_X + (W_W / 2 - gfx_GetStringWidth("[2ND] CHANGE") / 2), W_Y + W_H + 2); gfx_PrintStringXY("[ALPHA] FIRE", W_X + (W_W / 2 - gfx_GetStringWidth("[ALPHA] FIRE") / 2), W_Y + W_H + 12); //won/lost text if (game.won || game.current_level.player->health <= 0) { gfx_SetTextBGColor(0x40); gfx_SetTextFGColor(0xFF); if (game.current_level.player->health <= 0) { gfx_PrintStringXY("You were burnt alive!", SPRITE_SIZE + 20, SPRITE_SIZE + 16); } else { gfx_PrintStringXY("You torched this place!", SPRITE_SIZE + 16, SPRITE_SIZE + 16); } gfx_PrintStringXY("ENT to continue...", SPRITE_SIZE + 30, SPRITE_SIZE + 16 + 10); gfx_SetTextBGColor(0x00); } //restart gfx_SetTextFGColor(0xFF); gfx_SetTextBGColor(0x00); gfx_PrintStringXY("[DEL] Suicide", 32 + (SPRITE_SIZE * 6 / 2 - gfx_GetStringWidth("[DEL] Suicide") / 2), 5); if (game.scene_animation_counter > 0 && game.scene_animation_counter < SUICIDE_MAX_COUNTER) { uint8_t col = 0; char *display; gfx_SetColor(0x20); gfx_FillRectangle_NoClip(SPRITE_SIZE + SPRITE_SIZE * 6 / 2 - 40, 240 / 2 - 10, 80, 20); switch (game.scene_animation_counter * 4 / SUICIDE_MAX_COUNTER) { case 0: display = "Writing will..."; col = 0x61; break; case 1: display = "Saying goodbyes..."; col = 0x82; break; case 2: display = "Praying prayers..."; col = 0xA3; break; case 3: display = "Forgetting regrets..."; col = 0xC4; break; case 4: display = "Blazing..."; col = 0xE5; break; } gfx_SetColor(col); gfx_FillRectangle_NoClip(SPRITE_SIZE + SPRITE_SIZE * 6 / 2 - 40, 240 / 2 - 10, game.scene_animation_counter * 80 / SUICIDE_MAX_COUNTER, 20); gfx_SetColor(0xC0); gfx_Rectangle_NoClip(SPRITE_SIZE + SPRITE_SIZE * 6 / 2 - 40, 240 / 2 - 10, 80, 20); gfx_SetTextBGColor(0x40); gfx_PrintStringXY(display, SPRITE_SIZE + SPRITE_SIZE * 6 / 2 - gfx_GetStringWidth(display) / 2, 240 / 2 - 20); gfx_SetTextBGColor(0x00); if (game.scene_animation_counter % 3 == 0) { screen_shake_blit(2, floor(game.scene_animation_counter * 5 / SUICIDE_MAX_COUNTER) + 1); } } } //updates the physics/life/burning of projectiles void update_projectiles() { uint8_t size = projectile_sizes[game.current_level.projectile->weapon_type]; if (game.current_level.projectile->life > 0) { game.current_level.projectile->life--; } if (game.current_level.projectile->active == false) { return; } //falling //if projectile is moving slowly and is not on the ground floor if (abs(game.current_level.projectile->velocity_x) <= 2 && abs(game.current_level.projectile->velocity_y <= 2) && game.current_level.projectile->z > 0) { //if the projectile is on a nothing tile if (game.current_level.tiles[game.current_level.projectile->z][map_bounds_y(game.current_level.projectile->y / SPRITE_SIZE)][map_bounds_x(game.current_level.projectile->x / SPRITE_SIZE)] == NOTHING_TILE) { //fall down a floor game.current_level.projectile->z--; } } //inside of wall already if (game.current_level.projectile->weapon_type == WEAPON_FLAMETHROWER) { if (object_map_colliding(game.current_level.projectile->x - (size / 2), game.current_level.projectile->y - (size / 2), game.current_level.projectile->z, size, size)) { switch (game.current_level.projectile->weapon_type) { case WEAPON_FLAMETHROWER: game.current_level.projectile->active = false; return; } } } //velocity game.current_level.projectile->x += game.current_level.projectile->velocity_x; game.current_level.projectile->y += game.current_level.projectile->velocity_y; if (object_map_colliding(game.current_level.projectile->x - (size / 2), game.current_level.projectile->y - (size / 2), game.current_level.projectile->z, size, size)) { game.current_level.projectile->x -= game.current_level.projectile->velocity_x; game.current_level.projectile->y -= game.current_level.projectile->velocity_y; game.current_level.projectile->velocity_x = 0; game.current_level.projectile->velocity_y = 0; //do something on impact if this is that kind of projectile switch (game.current_level.projectile->weapon_type) { case WEAPON_LIGHTER: if (randInt(1, 2) == 1) { set_tile_on_fire(map_bounds_x(get_tile_position(game.current_level.projectile->x)), map_bounds_y(get_tile_position(game.current_level.projectile->y)), game.current_level.projectile->z); screen_shake(2, 2); game.current_level.projectile->active = false; } return; case WEAPON_FLAMETHROWER: game.current_level.projectile->active = false; return; case WEAPON_RPG7: { int24_t ty, tx; uint8_t i, j; ty = get_tile_position(game.current_level.projectile->y); tx = get_tile_position(game.current_level.projectile->x); for (i = map_bounds_y(ty - 1); i <= map_bounds_y(ty + 1); i++) { for (j = map_bounds_x(tx - 1); j <= map_bounds_x(tx + 1); j++) { if (randInt(1, 5) == 1 && flammable[game.current_level.tiles[game.current_level.projectile->z][i][j]]) { //annihilate tile //(has to be flammable otherwise you could get over 100% dmg) set_tile(ASH_TILE, j, i, game.current_level.projectile->z); screen_shake(2, 6); } else if (randInt(1, 2) == 1) { //set tile on fire set_tile_on_fire(j, i, game.current_level.projectile->z); screen_shake(2, 6); } } } game.current_level.projectile->active = false; } break; } } else { game.current_level.projectile->velocity_x -= game.current_level.projectile->velocity_x / 5; game.current_level.projectile->velocity_y -= game.current_level.projectile->velocity_y / 5; if (abs(game.current_level.projectile->velocity_x) < 5) { game.current_level.projectile->velocity_x = 0; } if (abs(game.current_level.projectile->velocity_y) < 5) { game.current_level.projectile->velocity_y = 0; } } //frame-by-frame update for certain projectiles switch (game.current_level.projectile->weapon_type) { case WEAPON_FLAMETHROWER: { int24_t ty, tx; uint8_t i, j; ty = get_tile_position(game.current_level.projectile->y); tx = get_tile_position(game.current_level.projectile->x); for (i = map_bounds_y(ty - 1); i <= map_bounds_y(ty + 1); i++) { for (j = map_bounds_x(tx - 1); j <= map_bounds_x(tx + 1); j++) { if (randInt(1, 4) == 1) { set_tile_on_fire(j, i, game.current_level.projectile->z); } } } //set_tile_on_fire(tx,ty,game.current_level.projectile->z); } break; } if (game.current_level.projectile->life == 0) { //do something on death if this is that kind of projectile //(like a grenade) switch (game.current_level.projectile->weapon_type) { //spark from lighter case WEAPON_LIGHTER: if (randInt(1, 2) == 1) { set_tile_on_fire(map_bounds_x(get_tile_position(game.current_level.projectile->x)), map_bounds_y(get_tile_position(game.current_level.projectile->y)), game.current_level.projectile->z); screen_shake(2, 2); } break; //napalm grenade case WEAPON_GRENADE: { //note: because we're subtracting from these map positions, //they need to be signed as, otherwise, //a negative position will overflow. int24_t i, j, ty, tx, c; c = 0; ty = get_tile_position(game.current_level.projectile->y); tx = get_tile_position(game.current_level.projectile->x); for (i = map_bounds_y(ty - 3); i < map_bounds_y(ty + 4); i++) { for (j = map_bounds_x(tx - 3); j < map_bounds_x(tx + 4); j++) { if (randInt(1, max(1, abs((j - tx) * (i - ty)))) == 1) { set_tile_on_fire(j, i, game.current_level.projectile->z); c++; } } } screen_shake(c, c); } break; //shaped charge's fuse goes off case WEAPON_SHAPEDCHARGE: { int24_t k, ty, tx; ty = map_bounds_y(get_tile_position(game.current_level.projectile->y)); tx = map_bounds_x(get_tile_position(game.current_level.projectile->x)); for (k = max(0, ((int24_t)game.current_level.projectile->z)); k >= 0; k--) { set_tile_on_fire(tx, ty, k); } screen_shake(2, 10); } break; } game.current_level.projectile->active = false; } } int24_t get_tile_position(int24_t x) { return x / SPRITE_SIZE; } //bounds a tile position to the map size along the X axis int24_t map_bounds_x(int24_t x) { return max(0, min(LEVEL_WIDTH - 1, x)); } //bounds a tile position to the map size along the Y axis int24_t map_bounds_y(int24_t y) { return max(0, min(LEVEL_LENGTH - 1, y)); } void set_tile_on_fire(uint8_t x, uint8_t y, uint8_t z) { if (flammable[game.current_level.tiles[z][y][x]]) { set_tile(FIRE_TILE, x, y, z); //game.current_level.tiles[z][y][x] = FIRE_TILE; } } //spreads fire void update_fire_tick() { int24_t k, i, j, y, x, z; //create a buffer uint8_t tiles_buffer[LEVEL_HEIGHT][LEVEL_LENGTH][LEVEL_WIDTH]; //clone level data to buffer //so we can read it to see what //the array looked like before //any changes for (k = 0; k < LEVEL_HEIGHT; k++) { for (i = 0; i < LEVEL_LENGTH; i++) { for (j = 0; j < LEVEL_WIDTH; j++) { tiles_buffer[k][i][j] = game.current_level.tiles[k][i][j]; } } } game.burnt_tiles = 0; //iterate through all tiles for (k = 0; k < LEVEL_HEIGHT; k++) { for (i = 0; i < LEVEL_LENGTH; i++) { for (j = 0; j < LEVEL_WIDTH; j++) { uint8_t dormant, deleted; if (game.current_level.tiles[k][i][j] == FIRE_TILE || game.current_level.tiles[k][i][j] == ASH_TILE || game.current_level.tiles[k][i][j] == ASH_HOLE_TILE) { game.burnt_tiles++; } //only perform fire spread calculations on fire tiles if (tiles_buffer[k][i][j] != FIRE_TILE) { continue; } dormant = randInt(1, DORMANT_FIRE_CHANCE); //fire is dormant if (dormant == 1) { continue; } deleted = randInt(1, BURN_OUT_FIRE_CHANCE); if (deleted == 1) { //game.current_level.tiles[k][i][j] = ASH_TILE; set_tile(ASH_TILE, j, i, k); continue; } //iterate through all neighboring tiles (including corners and the center tile, which will be chopped out) for (z = max(0, k - 1); z <= min(LEVEL_HEIGHT - 1, k + 1); z++) { for (y = max(0, i - 1); y <= min(LEVEL_LENGTH - 1, i + 1); y++) { for (x = max(0, j - 1); x <= min(LEVEL_WIDTH - 1, j + 1); x++) { uint8_t tile, random_chance; tile = tiles_buffer[z][y][x]; //random chance of fire spreading based on whether it's along the Z axis or not random_chance = randInt(1, (z == k) ? FIRE_SPREAD_CURRENT_CHANCE : FIRE_SPREAD_NEW_CHANCE); //center tile if (x == j && y == i && z == k) { continue; } //we only want to perform this spread fire calculation on adjacent tiles, so //locations on two axis need to be the same position as the base fire tile. if (!((x == j && y == i) || (x == j && z == k) || (y == i && z == k))) { continue; } if (!flammable[tile]) { continue; } if (random_chance != 1) { continue; } set_tile(FIRE_TILE, x, y, z); //game.current_level.tiles[z][y][x] = FIRE_TILE; game.burnt_tiles++; if (game.current_level.player->z == z && renderable(add_camera_offset(x * SPRITE_SIZE, get_camera_x()), add_camera_offset(y * SPRITE_SIZE, get_camera_y()), SPRITE_SIZE, SPRITE_SIZE)) { screen_shake(2, 1); } } } } } } } //in case fire changes, update specials update_player_colliding_special_tiles(); } void update_level_end() { bool enter = enter_pressed(); if (game.message_counter > 0) { if (game.message_counter >= 200) { game.message_counter /= 1.01; } else { game.message_counter--; } } if (enter && game.message_counter > 1) { game.message_counter = 0; enter = false; } if (enter && game.message_counter <= 1) { bool goingToMenu = true; uint8_t score = get_score(game.burnt_tiles * 100 / game.flammable_tiles, game.current_level.player->health); if (score >= current_save.best_scores[game.level_select_counter]) { current_save.best_scores[game.level_select_counter] = score; } game.level_select_rerender = true; if (game.won == true) { if (game.level_select_counter == current_save.unlocked_level_count) { if (current_save.unlocked_level_count + 1 >= LEVEL_COUNT) { //you beat all levels goingToMenu = false; game.scene_animation_counter = 0; game.scene_animation = 0; change_scene(SCENE_YOU_WIN); } current_save.unlocked_level_count = min(LEVEL_COUNT - 1, current_save.unlocked_level_count + 1); game.level_select_counter = min(game.level_select_counter + 1, LEVEL_COUNT - 1); } } //if we haven't decided on going to SCENE_YOU_WIN if (goingToMenu) { change_scene(SCENE_MENU); render_loading(); } } } void render_level_end() { char *percent_dmg, *player_health, *score; uint8_t score_amt; score_amt = get_score(game.burnt_tiles * 100 / game.flammable_tiles, game.current_level.player->health); percent_dmg = "100%/100% damage"; player_health = "0 health remaining"; score = "Score: 000%"; sprintf(percent_dmg, "%d%%/%d%% damage", game.burnt_tiles * 100 / game.flammable_tiles, game.current_level.percentage_damage_required); if (game.current_level.player->health > 0) { sprintf(player_health, "%d health remaining", game.current_level.player->health); } else { player_health = "Player died! (halves score)"; } sprintf(score, "Score: %d%%", score_amt * 100 / (100 + MAX_HEALTH * 2)); gfx_FillScreen(0x20); gfx_SetColor(0x61); gfx_FillRectangle_NoClip(320 / 2 - 240 / 2, 240 / 2 - 160 / 2, 240, 160); gfx_SetColor(0xC0); gfx_Rectangle_NoClip(320 / 2 - 240 / 2, 240 / 2 - 160 / 2, 240, 160); gfx_SetTextScale(2, 2); { char *text = game.won ? "Level Scorched!" : "Game Over"; gfx_SetTextFGColor(game.won ? 0x64 : 0x20); gfx_PrintStringXY(text, 320 / 2 - gfx_GetStringWidth(text) / 2, (game.message_counter >= 400) ? 112 : (game.message_counter >= 200 ? 50 + ((game.message_counter - 200) * 0.31) : (50))); } gfx_SetTextScale(1, 1); gfx_SetTextFGColor(0xFF); if (game.message_counter < 200) { if (game.message_counter <= 170) { gfx_PrintStringXY("Level Summary", 320 / 2 - 240 / 2 + 5, 240 / 2 - 160 / 2 + 34); } if (game.message_counter <= 140) { gfx_PrintStringXY(percent_dmg, 320 / 2 - 240 / 2 + 5, 240 / 2 - 160 / 2 + 54); } if (game.message_counter <= 120) { gfx_PrintStringXY(player_health, 320 / 2 - 240 / 2 + 5, 240 / 2 - 160 / 2 + 64); } if (game.message_counter <= 100) { gfx_PrintStringXY(score, 320 / 2 - 240 / 2 + 5, 240 / 2 - 160 / 2 + 74); //new high! if (score_amt > current_save.best_scores[game.level_select_counter]) { gfx_PrintStringXY("(New hiscore!)", 320 / 2 - 240 / 2 + 5 + gfx_GetStringWidth(score) + 4, 240 / 2 - 160 / 2 + 74); } } if (game.message_counter <= 70) { gfx_PrintStringXY("Final rating:", 320 / 2 - 240 / 2 + 5, 240 / 2 - 160 / 2 + 94); } if (game.message_counter <= 0) { gfx_PrintStringXY("[ENT] to continue...", 320 / 2 - 240 / 2 + 5, 240 / 2 + 160 / 2 - 16); } } if (game.message_counter <= 1) { gfx_SetTextScale(2, 2); gfx_SetTextFGColor(get_rating_color(score_amt)); gfx_PrintStringXY(get_rating(score_amt), 320 / 2 - gfx_GetStringWidth(get_rating(score_amt)), 240 / 2 - 160 / 2 + 114); gfx_SetTextScale(1, 1); } //little pieces of text popping up if (game.message_counter == 170 || game.message_counter == 140 || game.message_counter == 120 || game.message_counter == 100 || game.message_counter == 70) { screen_shake(2, 1); } //Rating/"ENT to continue..." popping up if (game.message_counter == 1) { screen_shake(4, 4); } } void render_loading() { gfx_SetTextFGColor(0xFF); gfx_SetTextScale(1, 1); gfx_PrintStringXY("Loading...", 5, 5); gfx_BlitBuffer(); } void update_menu() { bool move_debounced = false; if (kb_Data[7] & kb_Left) { if (!move_debounced) { if (game.level_select_counter > 0) { game.level_select_counter--; } else { game.level_select_counter = LEVEL_COUNT - 1; } game.level_select_rerender = true; game.moved_in_menu = true; render_loading(); } move_debounced = true; } else if (kb_Data[7] & kb_Right) { if (!move_debounced) { if (game.level_select_counter < LEVEL_COUNT - 1) { game.level_select_counter++; } else { game.level_select_counter = 0; } game.level_select_rerender = true; game.moved_in_menu = true; render_loading(); } move_debounced = true; } else { move_debounced = false; } if (kb_Data[1] & kb_Mode) { game.scene_animation = 0; game.scene_animation_counter = 0; change_scene(SCENE_TUTORIAL); game.current_level = EMPTY_LEVEL; game.current_level.player->health = MAX_HEALTH; game.current_level.player->weapon_index = 0; game.current_level.player->last_moved = 1; game.current_level.player->moving_right = true; } if (enter_pressed() && game.level_select_counter <= current_save.unlocked_level_count) { load_level(&all_levels[game.level_select_counter]); } } void render_menu() { uint8_t k, i, j, z, k2; if (!game.level_select_rerender) { return; } gfx_FillScreen(0xAB); //z is used for rendering coords, while k is for accessing which layer we're on z = LEVEL_HEIGHT - 1; for (k = 0; k < LEVEL_HEIGHT; k++) { for (i = 0; i < LEVEL_LENGTH; i++) { for (j = 0; j < LEVEL_WIDTH; j++) { //screen coordinates int24_t screen_x = j * ISO_W / 2 - i * ISO_W / 2, screen_y = (j * ISO_H / 2) + (i * ISO_H / 2) + (z * ISO_H); //tile we're working with uint8_t tile = all_levels[game.level_select_counter].tiles[k][i][j]; //coords for an ISO floor tile int24_t pos[8] = { ISO_X + screen_x + ISO_W / 2, ISO_Y + screen_y, ISO_X + screen_x + ISO_W, ISO_Y + screen_y + ISO_H / 2, ISO_X + screen_x + ISO_W / 2, ISO_Y + screen_y + ISO_H, ISO_X + screen_x, ISO_Y + screen_y + ISO_H / 2}; //coords for the roof of a wall tile (unnecessary now that I've commented out all outilnes) /*int24_t pos_ceiling[8] = { ISO_X + screen_x + ISO_W/2, ISO_Y + screen_y - ISO_H, ISO_X + screen_x + ISO_W, ISO_Y + screen_y + ISO_H / 2 - ISO_H, ISO_X + screen_x + ISO_W/2, ISO_Y + screen_y + ISO_H - ISO_H, ISO_X + screen_x, ISO_Y + screen_y + ISO_H / 2 - ISO_H };*/ int24_t pos_wall[12] = { ISO_X + screen_x, ISO_Y + screen_y + ISO_H / 2, //0,1: bottom left body ISO_X + screen_x + ISO_W / 2, ISO_Y + screen_y + ISO_H, //2,3: bottom body ISO_X + screen_x + ISO_W, ISO_Y + screen_y + ISO_H / 2, //4,5: bottom right body ISO_X + screen_x + ISO_W, ISO_Y + screen_y - ISO_H / 2, //6,7: top right body ISO_X + screen_x + ISO_W / 2, ISO_Y + screen_y, //8,9: center body ISO_X + screen_x, ISO_Y + screen_y - ISO_H / 2 //10,11: top left body }; if (tile == NOTHING_TILE) { continue; } if (!ceilings[tile]) { //whether this should be shaded bool ceiling = false; for (k2 = min(LEVEL_HEIGHT, k + 1); k2 < LEVEL_HEIGHT; k2++) { if (all_levels[game.level_select_counter].tiles[k2][i][j] != NOTHING_TILE) { ceiling = true; } } //floor fill if (ceiling) { gfx_SetColor(isometric_colors[k][tile][0]); //dark } else { gfx_SetColor(isometric_colors[k][tile][1]); //light/neutral, floors don't matter } gfx_FillTriangle_NoClip(pos[0], pos[1], pos[2], pos[3], pos[6], pos[7]); gfx_FillTriangle_NoClip(pos[4], pos[5], pos[2], pos[3], pos[6], pos[7]); //floor outline //gfx_SetColor(0x00); //gfx_Polygon_NoClip(pos,4); continue; } //wall //body of wall //left half gfx_SetColor(isometric_colors[k][tile][0]); //dark gfx_FillTriangle_NoClip(pos_wall[0], pos_wall[1], pos_wall[10], pos_wall[11], pos_wall[8], pos_wall[9]); gfx_FillTriangle_NoClip(pos_wall[0], pos_wall[1], pos_wall[2], pos_wall[3], pos_wall[8], pos_wall[9]); //right half gfx_SetColor(isometric_colors[k][tile][2]); //medium gfx_FillTriangle_NoClip(pos_wall[8], pos_wall[9], pos_wall[6], pos_wall[7], pos_wall[4], pos_wall[5]); gfx_FillTriangle_NoClip(pos_wall[8], pos_wall[9], pos_wall[4], pos_wall[5], pos_wall[2], pos_wall[3]); //top half of wall (roof) gfx_SetColor(isometric_colors[k][tile][1]); //light gfx_FillTriangle_NoClip(pos[0], pos[1] - ISO_H, pos[2], pos[3] - ISO_H, pos[6], pos[7] - ISO_H); gfx_FillTriangle_NoClip(pos[4], pos[5] - ISO_H, pos[2], pos[3] - ISO_H, pos[6], pos[7] - ISO_H); /*gfx_SetColor(0x00); //wall outline gfx_Polygon_NoClip(pos_wall,6); gfx_VertLine(pos_wall[8],pos_wall[9],ISO_H); //wall top outline gfx_Polygon_NoClip(pos_ceiling,4);*/ } } z--; } gfx_SetColor(0x20); gfx_FillRectangle_NoClip(0, 220, 320, 20); gfx_SetColor(0xC0); gfx_HorizLine_NoClip(0, 220, 320); gfx_SetTextFGColor(0xFF); gfx_SetTextScale(2, 2); gfx_PrintStringXY(all_levels[game.level_select_counter].name, 320 / 2 - gfx_GetStringWidth(all_levels[game.level_select_counter].name) / 2, 240 - 38); gfx_ScaledTransparentSprite_NoClip(locks_tiles[game.level_select_counter <= current_save.unlocked_level_count ? 1 : 0], 320 / 2 - gfx_GetStringWidth(all_levels[game.level_select_counter].name) / 2 - 20, 240 - 38, 2, 2); gfx_SetTextScale(1, 1); if (game.level_select_counter > current_save.unlocked_level_count) { gfx_PrintStringXY("(Locked)", 320 / 2 - gfx_GetStringWidth("(Locked)") / 2, 240 - 38 - 14); } //selected level & best score { char *text = "[<] Selected Level 000/000 [>]"; char *best_text = "Best Score: 000%"; sprintf(text, "[<] Selected Level %d/%d [>]", game.level_select_counter + 1, LEVEL_COUNT); sprintf(best_text, "Best Score: %d%%", current_save.best_scores[game.level_select_counter] * 100 / (100 + MAX_HEALTH * 2)); gfx_PrintStringXY(text, 320 / 2 - gfx_GetStringWidth(text) / 2, 240 - 12); if (game.level_select_counter <= current_save.unlocked_level_count) { gfx_PrintStringXY("[ENT] Play", 320 / 2 - gfx_GetStringWidth("[ENT] Play") / 2, 5 + 10); gfx_PrintStringXY(best_text, 320 / 2 - gfx_GetStringWidth(best_text) / 2, 240 - 38 - 14); } } gfx_PrintStringXY("[MODE] Tutorial", 320 / 2 - gfx_GetStringWidth("[MODE] Tutorial") / 2, 5); if (game.moved_in_menu) { gfx_BlitBuffer(); gfx_ShiftDown(1); gfx_BlitBuffer(); gfx_ShiftDown(2); gfx_BlitBuffer(); gfx_ShiftUp(2); gfx_BlitBuffer(); gfx_ShiftUp(1); gfx_BlitBuffer(); game.moved_in_menu = false; } else { gfx_BlitBuffer(); } game.level_select_rerender = false; } void update_win_screen() { //make the player seem alive if (++player.animation_counter > ANIMATION_REGULAR_SPEED * 2) { player.animation_up = !player.animation_up; player.animation_counter = 0; } if (game.scene_animation_counter > 0) { game.scene_animation_counter /= 1.02; } if (enter_pressed()) { if (game.scene_animation < 6) { game.scene_animation++; } else { //no transition, it looks better like this. //there would be a transition if we used change_scene() //the eyes stay game.scene = SCENE_INTRO; game.scene_animation = 0; } game.scene_animation_counter = SCENE_ANIMATION_COUNTER; } } void render_win_screen() { uint24_t width; gfx_SetTextFGColor(0xFF); if (game.scene_animation == 6) { gfx_SetTextScale(2, 2); } width = gfx_GetStringWidth(ENDING_TEXTS[game.scene_animation]); if (game.scene_animation <= 2 || game.scene_animation >= 5) { gfx_FillScreen(0x00); gfx_TransparentSprite_NoClip(player_tiles[player.animation_up ? 0 : 1], 320 / 2 - SPRITE_SIZE / 2, 240 / 2 + SPRITE_SIZE / 2); gfx_PrintStringXY(ENDING_TEXTS[game.scene_animation], 320 / 2 - (width / 2), 240 / 2 - (game.scene_animation == 6 ? 8 : 4)); } if (game.scene_animation == 3 || game.scene_animation == 4) { //custom backgrounds gfx_SetTextBGColor(0x20); gfx_ScaledSprite_NoClip((game.scene_animation == 3 ? bridge : weapons), 0, 0, 4, 4); gfx_PrintStringXY(ENDING_TEXTS[game.scene_animation], 320 / 2 - width / 2, 240 / 2 - 4 + (game.scene_animation == 3 ? 50 : 90) + (game.scene_animation_counter * 10 / SCENE_ANIMATION_COUNTER)); } gfx_SetTextScale(1, 1); gfx_PrintStringXY("Press [ENT] ...", 320 - gfx_GetStringWidth("Press [ENT] ...") - 5, 240 - 10); gfx_SetTextBGColor(0x00); } void update_intro() { //update counters for animation if (game.scene_animation_counter > 70) { game.scene_animation_counter--; if (game.scene_animation_counter == 71) { player.animation_counter = 0; player.animation_up = false; } } else { //make the player seem alive game.scene_animation_counter /= 1.01; if (++player.animation_counter > ANIMATION_REGULAR_SPEED * 2) { player.animation_up = !player.animation_up; player.animation_counter = 0; } } if (enter_pressed() && game.scene_animation_counter <= 0) { game.level_select_rerender = true; game.level_select_counter = 0; if (current_save.done_tutorial) { change_scene(SCENE_MENU); render_loading(); } else { change_scene(SCENE_TUTORIAL); } } } void render_intro() { gfx_SetTextFGColor(0xFF); //animation if (game.scene_animation_counter >= 150) { gfx_FillScreen(0x00); //render player's eyes gfx_SetColor(0xE0); gfx_FillRectangle_NoClip(320 / 2 - SPRITE_SIZE / 2 + 16, 240 / 2 + SPRITE_SIZE / 2 + 12, 2, 4); gfx_FillRectangle_NoClip(320 / 2 - SPRITE_SIZE / 2 + 22, 240 / 2 + SPRITE_SIZE / 2 + 12, 2, 4); } else { uint8_t offset = max(0, min(50, 120 - game.scene_animation_counter)) / 2, offset2 = max(0, min(50, 120 - game.scene_animation_counter)); gfx_FillScreen(0x20); //render player & text gfx_TransparentSprite_NoClip(player_tiles[player.animation_up ? 0 : 1], 320 / 2 - SPRITE_SIZE / 2, 240 / 2 + SPRITE_SIZE / 2 - offset); gfx_PrintStringXY("You want to watch the world burn.", 320 / 2 - (gfx_GetStringWidth("You want to watch the world burn.") / 2), 240 / 2 - (game.scene_animation == 6 ? 8 : 4) - offset2); //render fire tiles gfx_Sprite_NoClip(tiles_tiles[FIRE_TILE - ((game.scene_animation_counter <= 50) ? 0 : 1)], 320 / 2 - SPRITE_SIZE / 2 + SPRITE_SIZE + 4, 240 / 2 + SPRITE_SIZE / 2 + 4 - offset); if (game.scene_animation_counter == 149) { screen_shake(4, 1); } if (game.scene_animation_counter <= 100) { gfx_Sprite_NoClip(tiles_tiles[FIRE_TILE - 1], 320 / 2 - SPRITE_SIZE / 2 + 4, 240 / 2 + SPRITE_SIZE / 2 + SPRITE_SIZE + 4 - offset); if (game.scene_animation_counter == 100) { screen_shake(8, 2); } } if (game.scene_animation_counter <= 50) { gfx_Sprite_NoClip(tiles_tiles[FIRE_TILE - 1], 320 / 2 - SPRITE_SIZE / 2 + SPRITE_SIZE + 4, 240 / 2 + SPRITE_SIZE / 2 + SPRITE_SIZE + 4 - offset); gfx_Sprite_NoClip(tiles_tiles[FIRE_TILE - 1], 320 / 2 - SPRITE_SIZE / 2 - SPRITE_SIZE + 4, 240 / 2 + SPRITE_SIZE / 2 + SPRITE_SIZE + 4 - offset); if (game.scene_animation_counter == 50) { screen_shake(8, 3); } } if (game.scene_animation_counter <= 1) { gfx_Sprite_NoClip(tiles_tiles[FIRE_TILE - 1], 320 / 2 - SPRITE_SIZE / 2 + SPRITE_SIZE * 2 + 4, 240 / 2 + SPRITE_SIZE / 2 + SPRITE_SIZE * 2 + 4 - offset); gfx_Sprite_NoClip(tiles_tiles[FIRE_TILE - 1], 320 / 2 - SPRITE_SIZE / 2 + SPRITE_SIZE * 2 + 4, 240 / 2 + SPRITE_SIZE / 2 + 4 - offset); gfx_Sprite_NoClip(tiles_tiles[FIRE_TILE - 1], 320 / 2 - SPRITE_SIZE / 2 - SPRITE_SIZE * 2 + 4, 240 / 2 + SPRITE_SIZE / 2 + 4 - offset); gfx_Sprite_NoClip(tiles_tiles[FIRE_TILE - 1], 320 / 2 - SPRITE_SIZE / 2 + 4, 240 / 2 + SPRITE_SIZE / 2 + SPRITE_SIZE * 2 + 4 - offset); if (game.scene_animation_counter == 1) { screen_shake(8, 5); } } } if (game.scene_animation_counter <= 0) { gfx_PrintStringXY("Press [ENT] ...", 320 - gfx_GetStringWidth("Press [ENT] ...") - 5, 240 - 10); } } void update_tutorial() { if (++player.animation_counter > ANIMATION_REGULAR_SPEED * 2) { player.animation_up = !player.animation_up; player.animation_counter = 0; } if (enter_pressed()) { game.scene_animation++; player.animation_counter = 0; } if (game.scene_animation == 15) { game.scene_animation = 0; change_scene(SCENE_MENU); //game.scene = SCENE_MENU; game.level_select_rerender = true; current_save.done_tutorial = true; render_loading(); } } void render_tutorial() { gfx_FillScreen(0x20); gfx_SetTextFGColor(0xFF); gfx_SetTextScale(1, 1); //weapon overview if (game.scene_animation >= 9 && game.scene_animation <= 13) { gfx_SetTextScale(2, 2); gfx_PrintStringXY("WEAPON OVERVIEW", 320 / 2 - gfx_GetStringWidth("WEAPON OVERVIEW") / 2, 40); gfx_SetTextScale(1, 1); gfx_ScaledTransparentSprite_NoClip(items_tiles[game.scene_animation - 9], 320 / 2 - SPRITE_SIZE, 240 / 2 - SPRITE_SIZE, 4, 4); gfx_SetColor(0xC0); gfx_Rectangle_NoClip(320 / 2 - SPRITE_SIZE - 2, 240 / 2 - SPRITE_SIZE - 2, SPRITE_SIZE * 2 + 4, SPRITE_SIZE * 2 + 4); gfx_PrintStringXY(weapon_names[game.scene_animation - 9], 320 / 2 - gfx_GetStringWidth(weapon_names[game.scene_animation - 9]) / 2, 75); if (game.scene_animation == 9) { gfx_PrintStringXY("The lighter is used for controlled burns.", 320 / 2 - gfx_GetStringWidth("The lighter is used for controlled burns.") / 2, 160); } else if (game.scene_animation == 10) { gfx_PrintStringXY("The napalm grenade has a long fuse, but", 320 / 2 - gfx_GetStringWidth("The napalm grenade has a long fuse, but") / 2, 160); gfx_PrintStringXY("it will cause a large amount of damage.", 320 / 2 - gfx_GetStringWidth("it will cause a large amount of damage.") / 2, 160 + 10); } else if (game.scene_animation == 11) { gfx_PrintStringXY("The flamethrower is a fast, effective", 320 / 2 - gfx_GetStringWidth("The flamethrower is a fast, effective") / 2, 160); gfx_PrintStringXY("weapon - great for corridors.", 320 / 2 - gfx_GetStringWidth("weapon - great for corridors.") / 2, 160 + 10); } else if (game.scene_animation == 12) { gfx_PrintStringXY("The shaped charge blasts fire to", 320 / 2 - gfx_GetStringWidth("The shaped charge blasts fire below to") / 2, 160); gfx_PrintStringXY("all floors at and below where it's placed.", 320 / 2 - gfx_GetStringWidth("all floors at and below where it's placed.") / 2, 160 + 10); } else if (game.scene_animation == 13) { gfx_PrintStringXY("The RPG-7 is an anti-tank rocket-propelled", 320 / 2 - gfx_GetStringWidth("The RPG-7 is an anti-tank rocket-propelled") / 2, 160); gfx_PrintStringXY("grenade launcher. It's effective for", 320 / 2 - gfx_GetStringWidth("grenade launcher. It's effective for") / 2, 160 + 10); gfx_PrintStringXY("exploding things at a distance.", 320 / 2 - gfx_GetStringWidth("exploding things at a distance.") / 2, 160 + 20); } return; } //last message if (game.scene_animation == 14) { gfx_ScaledSprite_NoClip(intro, 0, 0, 4, 4); //old player sprite //gfx_TransparentSprite_NoClip((game.current_level.player->animation_up ? player_tile_0 : player_tile_1), 320 / 2 - SPRITE_SIZE / 2, 240 / 2 - 8 - SPRITE_SIZE - 4); gfx_SetTextBGColor(0x40); gfx_SetTextScale(2, 2); gfx_PrintStringXY("Good luck", 320 / 2 - gfx_GetStringWidth("Good luck") / 2, 240 / 2 - 8); gfx_SetTextScale(1, 1); gfx_PrintStringXY("and enjoy the inferno.", 320 / 2 - gfx_GetStringWidth("and enjoy the inferno.") / 2, 240 / 2 - 8 + 16 + 4); gfx_SetTextBGColor(0x00); return; } if (game.scene_animation >= 5) { //borders gfx_SetColor(0x20); gfx_FillRectangle_NoClip(0, 0, 32, 240); gfx_FillRectangle_NoClip(32, 0, 288, 32); gfx_FillRectangle_NoClip(32, 208, 288, 32); gfx_FillRectangle_NoClip(223, 32, 97, 176); gfx_SetColor(0xC0); gfx_Rectangle_NoClip(32, 32, 192, 176); //tiles and player render_current_layer(); if (game.scene_animation == 5) { gfx_PrintStringXY("This is you.", SPRITE_SIZE * 2 + 24, SPRITE_SIZE * 3 - 20); gfx_PrintStringXY("Use Arrows to move,", SPRITE_SIZE * 2 - 4, SPRITE_SIZE * 3 - 10); gfx_PrintStringXY("Use Alpha to fire.", SPRITE_SIZE * 2 + 2, SPRITE_SIZE * 3); } } if (game.scene_animation >= 6) { render_minimap(); if (game.scene_animation == 6) { gfx_PrintStringXY("This is your minimap ->", 80, 60); gfx_PrintStringXY("[+] / [-] to scroll.", 80, 70); } if (game.scene_animation == 7) { gfx_PrintStringXY("This is your floor ->", 90, 40); } if (game.scene_animation == 8) { gfx_PrintStringXY("This is the minimap's floor ->", 35, 88); } } //life if (game.scene_animation >= 3) { render_life(); if (game.scene_animation == 3) { gfx_PrintStringXY("This is your life. Don't let it run out.", 70, 240 - 40); } } //damage if (game.scene_animation >= 1) { //unburnt color gfx_SetColor(0x41); gfx_FillRectangle_NoClip(5, SPRITE_SIZE, SPRITE_SIZE - 10, (240 - SPRITE_SIZE * 2)); //outline gfx_SetColor(0xC0); gfx_Rectangle_NoClip(5, SPRITE_SIZE, SPRITE_SIZE - 10, 240 - SPRITE_SIZE * 2); //damage required gfx_SetColor(0xE0); gfx_SetTextFGColor(0xE5); gfx_HorizLine_NoClip(3, SPRITE_SIZE + ((240 - SPRITE_SIZE * 2) - (game.current_level.percentage_damage_required * (240 - SPRITE_SIZE * 2) / 100)) - 1, SPRITE_SIZE - 6); gfx_PrintStringXY("%", 12, SPRITE_SIZE + ((240 - SPRITE_SIZE * 2) - (game.current_level.percentage_damage_required * (240 - SPRITE_SIZE * 2) / 100)) - 5); //text overlay gfx_SetTextFGColor(0xFF); gfx_SetTextScale(1, 1); gfx_PrintStringXY("DMG", 5, 240 / 2 - 8 / 2); if (game.scene_animation == 1) { gfx_PrintStringXY("<- This bar represents the level's damage.", 30, 240 / 2 - 4); gfx_PrintStringXY(" It will fill up as the level gets", 30, 240 / 2 - 4 + 10); gfx_PrintStringXY(" charred and burned.", 30, 240 / 2 - 4 + 10 + 10); } else if (game.scene_animation == 2) { gfx_PrintStringXY("<- This is your damage goal for a level.", 30, 72); } } //weapon if (game.scene_animation >= 4) { //sprite gfx_ScaledTransparentSprite_NoClip(items_tiles[game.current_level.player->weapon_index], W_X, W_Y, 4, 4); //weapon outline gfx_SetColor(0xC0); gfx_Rectangle_NoClip(W_X, W_Y, W_W, W_H); //weapon name gfx_PrintStringXY(weapon_names[game.current_level.player->weapon_index], W_X + (W_W / 2 - gfx_GetStringWidth(weapon_names[game.current_level.player->weapon_index]) / 2), W_Y - 8); if (game.scene_animation == 4) { gfx_PrintStringXY("This is your equipped weapon ->", 25, 160); gfx_PrintStringXY("Use 2nd to change it.", 25, 160 + 10); } } if (game.scene_animation == 0) { gfx_PrintStringXY("Use ENT to progress through tutorial...", 320 / 2 - gfx_GetStringWidth("Use ENT to progress through tutorial...") / 2, 240 / 2 - 4); } } void render_life() { uint8_t i, off; off = (6 * SPRITE_SIZE) / 2 - (MAX_HEALTH * 18 + 10) / 2; gfx_SetColor(0x00); gfx_FillRectangle_NoClip(SPRITE_SIZE + off, 240 - 25, 18 * MAX_HEALTH + 10, 19); for (i = 0; i < MAX_HEALTH; i++) { gfx_SetColor(game.current_level.player->health >= (i + 1) ? 0xC0 : 0x40); gfx_FillRectangle_NoClip(SPRITE_SIZE + i * 18 + off + 5, 240 - 20, 16, 9); } gfx_SetColor(0xC0); gfx_Rectangle_NoClip(SPRITE_SIZE + off, 240 - 25, 18 * MAX_HEALTH + 10, 19); } void render_minimap() { char *floor = "Floor 000"; char *viewing = "Viewing 000"; sprintf(floor, "Floor %d", game.current_level.player->z + 1); sprintf(viewing, "Viewing %d", game.viewing_floor + 1); //background gfx_SetColor(0x41); gfx_FillRectangle_NoClip(MM_X, MM_Y, MM_W, MM_H); /*for(i = 0; i < LEVEL_LENGTH; i++) { for(j = 0; j < LEVEL_WIDTH; j++) { gfx_SetColor(minimap_colors[game.current_level.tiles[game.viewing_floor][i][j]]); gfx_FillRectangle_NoClip(MM_X + j*w,MM_Y + (i*h),w,h); } }*/ gfx_Sprite_NoClip(minimap, MM_X, MM_Y); gfx_SetColor(0xC0); gfx_Rectangle_NoClip(MM_X, MM_Y, MM_W, MM_H); if (game.fire_animation_counter <= FIRE_ANIMATION_SPEED / 2) { gfx_SetColor(0xFF); if (game.current_level.player->z != game.viewing_floor) { gfx_SetColor(0xD5); } gfx_FillRectangle_NoClip(MM_X + (map_bounds_x(get_tile_position(game.current_level.player->x + SPRITE_SIZE / 2)) * (MM_W / LEVEL_WIDTH)), MM_Y + (map_bounds_y(get_tile_position(game.current_level.player->y + SPRITE_SIZE)) * (MM_H / LEVEL_LENGTH)), (MM_W / LEVEL_WIDTH), (MM_H / LEVEL_LENGTH)); } gfx_SetTextFGColor(0xFF); gfx_PrintStringXY(floor, MM_X, MM_Y - 10); gfx_PrintStringXY(viewing, MM_X, MM_Y + MM_H + 2); gfx_PrintStringXY("[+]", MM_X + MM_W + 2, MM_Y + (MM_H / 3) - 4); gfx_PrintStringXY("[-]", MM_X + MM_W + 2, MM_Y + (MM_H * 2 / 3) - 4); } uint8_t get_score(uint8_t percent_dmg, uint8_t player_health) { if (player_health == 0) { return percent_dmg / 2; } return percent_dmg + player_health * 2; } uint8_t get_rating_color(uint8_t score) { if (score <= 3) { return 0x20; //F- } else if (score <= 6) { return 0x20; } else if (score <= 12) { return 0x40; } else if (score <= 24) { return 0x80; } else if (score <= 32) { return 0xC0; } else if (score <= 40) { return 0xA0; } else if (score <= 48) { return 0xE2; } else if (score <= 56) { return 0xE4; } else if (score <= 64) { return 0xC7; } else if (score <= 72) { return 0xA7; } else if (score <= 85) { return 0x67; } else if (score <= 100) { return 0x47; } else if (score < 100 + MAX_HEALTH * 2) { return 0x07; } else { return 0xFF; //A+ } } char *get_rating(uint8_t score) { if (score <= 3) { return "F-"; } else if (score <= 6) { return "F"; } else if (score <= 12) { return "D-"; } else if (score <= 24) { return "D"; } else if (score <= 32) { return "D+"; } else if (score <= 40) { return "C-"; } else if (score <= 48) { return "C"; } else if (score <= 56) { return "C+"; } else if (score <= 64) { return "B-"; } else if (score <= 72) { return "B"; } else if (score <= 85) { return "B+"; } else if (score <= 100) { return "A-"; } else if (score < 100 + MAX_HEALTH * 2) { return "A"; } else { return "A+"; } } void render_current_layer() { int24_t i, j, imin, jmin, imax, jmax; int24_t cam_x, cam_y, player_tile_x, player_tile_y; cam_x = get_camera_x(); cam_y = get_camera_y(); player_tile_x = game.current_level.player->x / SPRITE_SIZE; player_tile_y = game.current_level.player->y / SPRITE_SIZE; imin = max(0, player_tile_y - (LEVEL_RENDER_HEIGHT / SPRITE_SIZE) - 1); imax = min(LEVEL_LENGTH - 1, player_tile_y + (LEVEL_RENDER_HEIGHT / SPRITE_SIZE) + 1); jmin = max(0, player_tile_x - (LEVEL_RENDER_WIDTH / SPRITE_SIZE) - 1); jmax = min(LEVEL_WIDTH - 1, player_tile_x + (LEVEL_RENDER_WIDTH / SPRITE_SIZE) + 1); //tiles for (i = imin; i <= imax; i++) { for (j = jmin; j <= jmax; j++) { //rendering offset for tile with camera factored in //ix and iy won't have the fire offsets (good for fire background) int24_t x, y, ix, iy; //the tile we'll render uint8_t tile = game.current_level.tiles[game.current_level.player->z][i][j]; if (game.current_level.tiles[game.current_level.player->z][i][j] == 0) { continue; } x = add_camera_offset(j * (SPRITE_SIZE), cam_x); y = add_camera_offset(i * (SPRITE_SIZE), cam_y); ix = x; iy = y; if (tile == FIRE_TILE) { if (game.fire_animation_counter < FIRE_ANIMATION_SPEED / 2) { y += (-game.fire_animation_counter) / 2; } else { y += (-FIRE_ANIMATION_SPEED + game.fire_animation_counter) / 2; } } if ((tile != FIRE_TILE && !renderable(x, y, SPRITE_SIZE, SPRITE_SIZE)) || (tile == FIRE_TILE && !renderable(x, y, SPRITE_SIZE, SPRITE_SIZE + (iy - y)))) { continue; } if (game.current_level.tiles[game.current_level.player->z][i][j] == FIRE_TILE) { gfx_SetColor(0xC0); gfx_FillRectangle_NoClip(ix, iy, SPRITE_SIZE, SPRITE_SIZE); } if (tile == ASH_TILE) { if (game.current_level.player->z > 0) { if (!collidable[game.current_level.tiles[game.current_level.player->z - 1][i][j]] && !tiles_that_move_you[game.current_level.tiles[game.current_level.player->z - 1][i][j]]) { tile = ASH_HOLE_TILE; set_tile(tile, j, i, game.current_level.player->z); } } } gfx_Sprite_NoClip(tiles_tiles[tile - 1], x, y); } } //projectile if (game.current_level.projectile->active && game.current_level.projectile->z == game.current_level.player->z) { int24_t x, y; uint8_t size; size = projectile_sizes[game.current_level.projectile->weapon_type]; x = add_camera_offset(game.current_level.projectile->x - (size / 2), cam_x); y = add_camera_offset(game.current_level.projectile->y - (size / 2), cam_y); if (renderable(x, y, size, size)) { gfx_SetColor(0xC0); gfx_FillRectangle_NoClip(x, y, size, size); } } //this % 8 <= 3 stuff is about flashing the player during invincibility frames if ((game.current_level.player->health_timer == 0 || (game.current_level.player->health_timer % 8 <= 3)) && game.current_level.player->health > 0) { //player gfx_TransparentSprite_NoClip(game.current_level.player->moving_right ? (game.current_level.player->animation_up ? player_tile_0 : player_tile_1) : (game.current_level.player->animation_up ? player_tile_0_flip : player_tile_1_flip), add_camera_offset(game.current_level.player->x, cam_x), add_camera_offset(game.current_level.player->y, cam_y)); // //all other sprites look alright flipped over to the other side, but //the flame thrower will use an actual flipped version //so it looks like the player is holding it // //player's item { // x/y offsets for held item based on direction int8_t xoff, yoff; xoff = ((game.current_level.player->last_moved == 1) ? 4 : ((game.current_level.player->last_moved == 3) ? -4 : 0)) + (game.current_level.player->moving_right ? 14 : -0); yoff = (game.current_level.player->last_moved == 0 ? 12 : (game.current_level.player->last_moved == 2 ? 18 : 14)); gfx_TransparentSprite_NoClip((game.current_level.player->weapon_index == 2 && !game.current_level.player->moving_right) ? (items_downscaled_tile_2_flip) : items_downscaled_tiles[game.current_level.player->weapon_index], add_camera_offset(game.current_level.player->x + xoff, cam_x), add_camera_offset(game.current_level.player->y + yoff, cam_y)); } } //render fire over player if they're dead if (game.current_level.player->health <= 0) { gfx_SetColor(0xC0); gfx_FillRectangle_NoClip(add_camera_offset(game.current_level.player->x, cam_x), add_camera_offset(game.current_level.player->y, cam_y), SPRITE_SIZE, SPRITE_SIZE); gfx_Sprite_NoClip(tiles_tiles[FIRE_TILE - 1], add_camera_offset(game.current_level.player->x, cam_x), add_camera_offset(game.current_level.player->y + (game.fire_animation_counter < FIRE_ANIMATION_SPEED / 2 ? (-game.fire_animation_counter) / 2 : (-FIRE_ANIMATION_SPEED + game.fire_animation_counter) / 2), cam_y)); } //ceilings/tile tops for (i = imin; i <= imax; i++) { for (j = jmin; j <= jmax; j++) { //rendering offset for tile with camera factored in int24_t x, y; if (!ceilings[game.current_level.tiles[game.current_level.player->z][i][j]]) { continue; } x = add_camera_offset(j * (SPRITE_SIZE), cam_x); y = add_camera_offset(i * (SPRITE_SIZE), cam_y); if (!renderable(x, y - SPRITE_SIZE, SPRITE_SIZE, SPRITE_SIZE)) { continue; } gfx_SetColor(ceiling_colors[game.current_level.tiles[game.current_level.player->z][i][j]]); gfx_FillRectangle_NoClip(add_camera_offset(j * SPRITE_SIZE, cam_x), add_camera_offset(i * SPRITE_SIZE - SPRITE_SIZE, cam_y), SPRITE_SIZE, SPRITE_SIZE); } } //player's reload if (game.current_level.projectile->life > 0 && game.current_level.player->health > 0) { gfx_SetColor(0x00); gfx_FillRectangle_NoClip(add_camera_offset(game.current_level.player->x, cam_x), add_camera_offset(game.current_level.player->y - 10, cam_y), 32, 6); gfx_SetColor(0xFF); gfx_FillRectangle_NoClip(add_camera_offset(game.current_level.player->x + 2, cam_x), add_camera_offset(game.current_level.player->y - 10 + 2, cam_y), ((weapon_lives[game.current_level.projectile->weapon_type] - game.current_level.projectile->life) * 28) / weapon_lives[game.current_level.projectile->weapon_type], 2); } } bool collision(int24_t x, int24_t y, int24_t w, int24_t h, int24_t x2, int24_t y2, int24_t w2, int24_t h2) { return (x + w > x2 && y + h > y2 && y2 + h2 > y && x2 + w2 > x); } bool player_colliding() { //min variables are the player's top left tile position minus one. max variables would be your top left //tile position plus one, but instead, we're just adding two to the min variables and capping the level widths/heights /*int24_t tilexmin,tileymin,i,j; //Not sure, but I set i and j to int24s instead of uint8_ts because I need to multiply them by sprite_size for map positions tilexmin = min(LEVEL_WIDTH-3,max(0,(game.current_level.player->x/SPRITE_SIZE)-1)); tileymin = min(LEVEL_LENGTH-3,max(0,(game.current_level.player->y/SPRITE_SIZE)-1)); for(i = tileymin; i <= tileymin+2; i++) { for(j = tilexmin; j <= tilexmin+2; j++) { if(!collidable[game.current_level.tiles[game.current_level.player->z][i][j]]) { continue; } if(player_collision(j*SPRITE_SIZE,i*SPRITE_SIZE,SPRITE_SIZE,SPRITE_SIZE)) { return true; } } } return false; */ return object_map_colliding(game.current_level.player->x + (SPRITE_SIZE / 4), game.current_level.player->y + SPRITE_SIZE - 1, game.current_level.player->z, (SPRITE_SIZE * 2) / 4, 1); } //same function as player_colliding but works with any object. maybe replace player_colliding with this in the future bool object_map_colliding(int24_t x, int24_t y, uint8_t z, int24_t w, int24_t h) { int24_t tilexmin, tileymin, i, j; tilexmin = min(LEVEL_WIDTH - 3, max(0, (x / SPRITE_SIZE) - 1)); tileymin = min(LEVEL_LENGTH - 3, max(0, (y / SPRITE_SIZE) - 1)); if (!insideOrEqualTo(0, 0, LEVEL_WIDTH * SPRITE_SIZE, LEVEL_LENGTH * SPRITE_SIZE, x, y, w, h)) { return true; } for (i = tileymin; i <= tileymin + 2; i++) { for (j = tilexmin; j <= tilexmin + 2; j++) { if (!collidable[game.current_level.tiles[z][i][j]] && !(game.current_level.tiles[z][i][j] == NOTHING_TILE && z == 0)) { continue; } if (collision(x, y, w, h, j * SPRITE_SIZE, i * SPRITE_SIZE, SPRITE_SIZE, SPRITE_SIZE)) { return true; } } } return false; } bool player_collision(int24_t x, int24_t y, int24_t w, int24_t h) { return collision(game.current_level.player->x + (SPRITE_SIZE / 4), game.current_level.player->y + SPRITE_SIZE - 1, (SPRITE_SIZE * 2) / 4, 1, x, y, w, h); } //works like the above function (player_colliding), but, instead, we're checking for collisions with: //4 fire - we will do something to the player's health when they touch it //7,8 stairs - if they go up, the player will go up a floor. If they go down, the player will go down a floor //6 holes of ash - if a player collides with this, they'll fall down a floor void update_player_colliding_special_tiles() { //min variables are the player's top left tile position minus one. max variables would be your top left //tile position plus one, but instead, we're just adding two to the min variables and capping the level widths/heights int24_t tilexmin, tileymin, i, j; const static bool tiles_to_look_for[TILE_COUNT] = {true, false, false, false, false, true, true, true, true, true}; tilexmin = min(LEVEL_WIDTH - 3, max(0, (game.current_level.player->x / SPRITE_SIZE) - 1)); tileymin = min(LEVEL_LENGTH - 3, max(0, (game.current_level.player->y / SPRITE_SIZE) - 1)); for (i = tileymin; i <= tileymin + 2; i++) { for (j = tilexmin; j <= tilexmin + 2; j++) { uint8_t tile = game.current_level.tiles[game.current_level.player->z][i][j]; if (tiles_to_look_for[tile] == false) { continue; } if (!player_collision(j * SPRITE_SIZE, i * SPRITE_SIZE, SPRITE_SIZE, SPRITE_SIZE)) { continue; } if (tile == STAIRS_UP_TILE || tile == STAIRS_DOWN_TILE || tile == ASH_HOLE_TILE || (tile == NOTHING_TILE && game.current_level.player->z > 0)) { uint8_t destZ = game.current_level.player->z + ((tile == STAIRS_DOWN_TILE || tile == ASH_HOLE_TILE || tile == NOTHING_TILE) ? -1 : 1); uint8_t destTile = game.current_level.tiles[destZ][i][j]; //if you're moving from one floor to another and the destination is a tile that moves you, abort. if (tiles_that_move_you[destTile]) { //checking if we get into an infinite loop with this destination tile. //if we're moving up and it moves us up that just means it's an elevator. //if it moves us down and we're moving up, it's an infinite loop. That means //we're continuing. if (game.current_level.player->z + ((destTile == STAIRS_DOWN_TILE || destTile == ASH_HOLE_TILE || destTile == NOTHING_TILE) ? -1 : 1) != destZ) { continue; } } //move the minimap to your floor game.viewing_floor = destZ; update_minimap_sprite(); game.current_level.player->z = destZ; game.current_level.player->x = j * SPRITE_SIZE; game.current_level.player->y = i * SPRITE_SIZE; if (tile == ASH_HOLE_TILE) { show_message("Fell down burnt floor!"); } else if (tile == STAIRS_UP_TILE) { show_message("Climbed up stairs"); } else if (tile == STAIRS_DOWN_TILE) { show_message("Climbed down stairs"); } else if (tile == NOTHING_TILE) { show_message("Fell down hole!"); } } else if (tile == FIRE_TILE || tile == ASH_TILE) { //there's a reason that ash tiles hurt you - they are unburnable and can last forever while a level around //you burns. if (game.current_level.player->health_timer == 0) { if (tile == FIRE_TILE) { show_message("Burnt by fire!"); hurt_player(FIRE_DAMAGE); } else { show_message("Burnt by hot ash!"); hurt_player(ASH_DAMAGE); } } } } } } void show_message(char *message) { game.message = message; game.message_counter = 60; } void move_player(int24_t x, int24_t y) { if (x != 0) { game.current_level.player->x += x; if (player_colliding()) { game.current_level.player->x -= x; } } if (y != 0) { game.current_level.player->y += y; if (player_colliding()) { game.current_level.player->y -= y; } } if (x != 0 || y != 0) { update_player_colliding_special_tiles(); } } //checks if second box is inside of first box bool inside(int24_t x, int24_t y, int24_t w, int24_t h, int24_t x2, int24_t y2, int24_t w2, int24_t h2) { return (x2 > x && x2 + w2 < x + w && y2 > y && y2 + h2 < y + h); } bool insideOrEqualTo(int24_t x, int24_t y, int24_t w, int24_t h, int24_t x2, int24_t y2, int24_t w2, int24_t h2) { return (x2 >= x && x2 + w2 <= x + w && y2 >= y && y2 + h2 <= y + h); } bool renderable(int24_t x, int24_t y, int24_t w, int24_t h) { return inside(LEVEL_X_OFFSET, LEVEL_Y_OFFSET, LEVEL_RENDER_WIDTH + LEVEL_X_OFFSET, LEVEL_RENDER_HEIGHT + LEVEL_Y_OFFSET, x, y, w, h); } int24_t add_camera_offset(int24_t distance, int24_t camera_distance) { return distance - camera_distance; } int24_t get_camera_x() { return LEVEL_X_OFFSET + game.current_level.player->x - CAMERA_OFFSET_X; } int24_t get_camera_y() { return LEVEL_Y_OFFSET + game.current_level.player->y - CAMERA_OFFSET_Y; } void screen_shake(uint8_t length, uint8_t magnitude) { uint8_t i, m; m = magnitude; for (i = 0; i < length; i++) { uint8_t l = randInt(1, 4); switch (l) { case 1: gfx_ShiftUp(m); break; case 2: gfx_ShiftRight(m); break; case 3: gfx_ShiftDown(m); break; case 4: gfx_ShiftLeft(m); break; } gfx_SwapDraw(); m -= magnitude / length; } } void screen_shake_blit(uint8_t length, uint8_t magnitude) { uint8_t i, m; m = magnitude; for (i = 0; i < length; i++) { uint8_t l = randInt(1, 4); switch (l) { case 1: gfx_ShiftUp(m); break; case 2: gfx_ShiftRight(m); break; case 3: gfx_ShiftDown(m); break; case 4: gfx_ShiftLeft(m); break; } gfx_BlitBuffer(); m -= magnitude / length; } } void hurt_player(uint8_t amt) { if (game.current_level.player->health_timer > 0) { return; } game.current_level.player->health_timer = I_FRAMES; game.current_level.player->health = max(game.current_level.player->health, amt) - amt; screen_shake(2, 6); if (game.current_level.player->health <= 0) { //kill player return; } } void update_minimap_sprite() { int24_t i, j; for (i = 0; i < minimap->height; i++) { for (j = 0; j < minimap->width; j++) { minimap->data[i * minimap->width + j] = minimap_colors[game.current_level.tiles[game.viewing_floor][(i * LEVEL_LENGTH) / minimap->height][(j * LEVEL_WIDTH) / minimap->width]]; } } } void set_tile(uint8_t tile, uint8_t x, uint8_t y, uint8_t z) { //minimap tile width and height int24_t i, j, w, h; uint8_t color; game.current_level.tiles[z][y][x] = tile; //if tile wont be rendered in minimap if (z != game.viewing_floor) { return; } //update minimap color = minimap_colors[tile]; w = minimap->width / LEVEL_WIDTH; h = minimap->height / LEVEL_LENGTH; for (i = h * y; i < (h * y) + h; i++) { for (j = w * x; j < (w * x) + w; j++) { minimap->data[i * minimap->width + j] = color; } } } bool enter_pressed() { static bool debounced = false; if (kb_Data[6] & kb_Enter) { if (!debounced) { debounced = true; return true; } debounced = true; } else { debounced = false; } return false; } void save_data() { ti_var_t file; ti_CloseAll(); file = ti_Open(SAVE_FILE_NAME, "w"); if (file) { ti_Rewind(file); current_save.stored_level_count = LEVEL_COUNT; ti_Write(¤t_save, sizeof(save_t), 1, file); } ti_Close(file); } void load_data() { ti_var_t file; save_t loading; ti_CloseAll(); file = ti_Open(SAVE_FILE_NAME, "a+"); if (file) { ti_Rewind(file); ti_Read(&loading, sizeof(save_t), 1, file); //if our stored data is compatible if (loading.stored_level_count == LEVEL_COUNT) { current_save = loading; } } ti_Close(file); } //not too sure what i'm doing in this function //but it works void change_scene(uint8_t target) { uint24_t i; game.scene = target; //if it's the menu, rendering will take too long so we can't do a fancy transition. if (target == SCENE_MENU) { game.level_select_rerender = true; game.scene = target; return; } //do a fancy transition out //steps of 4 pixels gfx_SetColor(0x00); gfx_BlitScreen(); for (i = 0; i <= 320 - 4; i += 4) { gfx_FillRectangle(i, 0, 4, 240); gfx_BlitBuffer(); } //do a fancy transition in //steps of 10 pixels for (i = 0; i <= 320; i += 10) { render(); gfx_SetColor(0x00); gfx_FillRectangle(i, 0, 320 - i, 240); gfx_BlitBuffer(); } }