diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e08935..1ccbd0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,4 +20,4 @@ add_executable(dummy src/level.h src/main.cpp src/util/util.cpp - src/util/util.h src/objects/tank.cpp src/objects/tank.h src/objects/shell.cpp src/objects/shell.h src/objects/mine.cpp src/objects/mine.h src/globals.cpp src/globals.h src/util/profiler.c src/util/profiler.h src/graphics/gui.cpp src/graphics/gui.h src/graphics/partial_redraw.cpp src/graphics/partial_redraw.h src/graphics/dynamic_sprites.cpp src/graphics/dynamic_sprites.h src/ai/ai_state.h src/objects/physicsbody.cpp src/objects/physicsbody.h src/input.cpp src/input.h src/fwd.h src/util/trig.cpp src/util/trig.h src/util/cpp_internals.cpp src/physics/physics.h src/game.cpp src/game.h src/objects/mine_detector.cpp src/objects/mine_detector.h src/util/debug_malloc.h src/graphics/tiles.cpp src/graphics/tiles.h src/graphics/repalettize.cpp src/graphics/repalettize.h src/graphics/tank_sprite.cpp src/graphics/tank_sprite.h) + src/util/util.h src/objects/tank.cpp src/objects/tank.h src/objects/shell.cpp src/objects/shell.h src/objects/mine.cpp src/objects/mine.h src/util/profiler.c src/util/profiler.h src/graphics/partial_redraw.cpp src/graphics/partial_redraw.h src/graphics/dynamic_sprites.cpp src/graphics/dynamic_sprites.h src/ai/ai_state.h src/objects/physicsbody.cpp src/objects/physicsbody.h src/input.cpp src/input.h src/fwd.h src/util/trig.cpp src/util/trig.h src/util/cpp_internals.cpp src/physics/physics.h src/game.cpp src/game.h src/objects/mine_detector.cpp src/objects/mine_detector.h src/util/debug_malloc.h src/graphics/tiles.cpp src/graphics/tiles.h src/graphics/repalettize.cpp src/graphics/repalettize.h src/graphics/tank_sprite.cpp src/graphics/tank_sprite.h src/gui/error.cpp src/gui/error.h src/gui/pause.cpp src/gui/pause.h src/gui/kill_counts.cpp src/gui/kill_counts.h src/gui/transition.cpp src/gui/transition.h src/gui/banner.cpp src/gui/banner.h src/gui/aim_indicator.cpp src/gui/aim_indicator.h) diff --git a/readme.md b/readme.md index 50405ba..e0bbb74 100644 --- a/readme.md +++ b/readme.md @@ -10,3 +10,5 @@ Developed using the [CE C Toolchain](https://github.com/CE-Programming/toolchain The [C libraries](https://github.com/CE-Programming/libraries/releases/latest) are required to run this program. As this project is still in alpha, GitHub releases may be significantly behind until I get the final release out. + +Feel free to ask questions or roast my terrible code quality in the project's [Cemetech thread](https://www.cemetech.net/forum/viewtopic.php?t=14718). diff --git a/src/ai/ai.cpp b/src/ai/ai.cpp index f5c45f7..3ed6330 100644 --- a/src/ai/ai.cpp +++ b/src/ai/ai.cpp @@ -1,7 +1,7 @@ #include "ai.h" #include "../util/profiler.h" -#include "../globals.h" +#include "../game.h" void ai_process_move(Tank *tank) { profiler_add(ai_move); @@ -85,7 +85,7 @@ void move_random(Tank *tank) { } } tank->tread_rot += randInt(0, DEGREES_TO_ANGLE(6)) - DEGREES_TO_ANGLE(3); - tank->set_velocity(Tank::velocities[tank->type]); + tank->set_velocity(tank->velocity()); profiler_end(ai_move_random); } @@ -119,7 +119,7 @@ void aim_random(Tank *tank) { //todo: add some visualizations as I have absolutely no idea wtf is going on here //it worked well in my head, okay? void aim_reflect(Tank *tank) { - ai_fire_reflect_state_t *ai = &tank->ai_fire.reflect; + struct ai_fire_reflect_state *ai = &tank->ai_fire.reflect; if(!tank->can_shoot()) return; //Loop through all X values, then all Y values if(tank->ai_fire.reflect.scan_dir == AXIS_X) { @@ -128,7 +128,7 @@ void aim_reflect(Tank *tank) { ai->scan_pos = 1; ai->scan_dir = AXIS_Y; point_at_player(tank, game.player); - if(pointing_at_target(tank, game.player, Tank::max_bounces[tank->type], false)) { + if(pointing_at_target(tank, game.player, tank->max_bounces(), false)) { tank->fire_shell(); } return; //I know this kinda skips a tick but whatever @@ -140,7 +140,7 @@ void aim_reflect(Tank *tank) { if(left != (game.player->center_x() < rX)) return; //if the specified X line was a mirror, where would the target appear to be? //lineseg between it and the center of tank - line_seg_t line; + struct line_seg line; line.x1 = tank->center_x(); line.y1 = tank->center_y(); line.x2 = 2 * rX - game.player->position_x - game.player->width / 2; @@ -149,7 +149,7 @@ void aim_reflect(Tank *tank) { int yInt = y_intercept(&line, rX); uint8_t xT = x - !left; uint8_t yT = COORD_TO_Y_TILE(yInt); - tile_t tile = tiles[yT][xT]; + tile_t tile = game.tiles[yT][xT]; #ifdef DBG_DRAW gfx_SetColor(COL_RED); drawLine(&line); @@ -165,7 +165,7 @@ void aim_reflect(Tank *tank) { } //if so, check if pointing_at_target tank->barrel_rot = fast_atan2(line.y2 - line.y1, line.x2 - line.x1); - if(pointing_at_target(tank, game.player, Tank::max_bounces[tank->type], false)) { + if(pointing_at_target(tank, game.player, tank->max_bounces(), false)) { //if so, fire tank->fire_shell(); } @@ -175,7 +175,7 @@ void aim_reflect(Tank *tank) { ai->scan_pos = 1; ai->scan_dir = AXIS_X; point_at_player(tank, game.player); - if(pointing_at_target(tank, game.player, Tank::max_bounces[tank->type], false)) { + if(pointing_at_target(tank, game.player, tank->max_bounces(), false)) { tank->fire_shell(); } return; //I know this kinda skips a tick but whatever @@ -187,7 +187,7 @@ void aim_reflect(Tank *tank) { if(up != (game.player->center_y() < rY)) return; //if the specified X line was a mirror, where would the target appear to be? //lineseg between it and the center of tank - line_seg_t line; + struct line_seg line; line.x1 = tank->center_x(); line.y1 = tank->center_y(); line.x2 = game.player->center_x(); @@ -196,7 +196,7 @@ void aim_reflect(Tank *tank) { int xInt = x_intercept(&line, rY); uint8_t xT = COORD_TO_X_TILE(xInt); uint8_t yT = y - !up; - tile_t tile = tiles[yT][xT]; + tile_t tile = game.tiles[yT][xT]; #ifdef DBG_DRAW gfx_SetColor(COL_RED); drawLine(&line); @@ -208,7 +208,7 @@ void aim_reflect(Tank *tank) { if(!TILE_HEIGHT(tile) || TILE_TYPE(tile) == DESTROYED) return; //if so, check if pointing_at_target tank->barrel_rot = fast_atan2(line.y2 - line.y1, line.x2 - line.x1); - if(pointing_at_target(tank, game.player, Tank::max_bounces[tank->type], false)) { + if(pointing_at_target(tank, game.player, tank->max_bounces(), false)) { //if so, fire tank->fire_shell(); } @@ -237,7 +237,7 @@ tile_t get_tile_at_offset(Tank *tank, angle_t angle_offset, int distance) { int8_t tile_y = COORD_TO_X_TILE(y); if(tile_x < 0 || tile_x >= LEVEL_SIZE_X) return 1; if(tile_y < 0 || tile_y >= LEVEL_SIZE_Y) return 1; - return tiles[COORD_TO_Y_TILE(y)][COORD_TO_X_TILE(x)]; + return game.tiles[COORD_TO_Y_TILE(y)][COORD_TO_X_TILE(x)]; } // todo: check tank's future position @@ -247,7 +247,7 @@ bool pointing_at_target(Tank *tank, PhysicsBody *target, uint8_t max_bounces, __ angle_t angle = tank->barrel_rot; for(uint8_t bounces = 0; bounces <= max_bounces; bounces++) { bool reflectAxis; - line_seg_t line; + struct line_seg line; profiler_add(raycast); reflectAxis = raycast(posX, posY, angle, &line); profiler_end(raycast); diff --git a/src/ai/ai_state.h b/src/ai/ai_state.h index 354a7ad..40e75b9 100644 --- a/src/ai/ai_state.h +++ b/src/ai/ai_state.h @@ -5,41 +5,41 @@ #include #include "../util/util.h" -typedef struct { +struct ai_fire_random_state { bool clockwise; -} ai_fire_random_state_t; +}; -typedef struct {} ai_fire_current_state_t; +struct ai_fire_current_state {}; -typedef struct { +struct ai_fire_reflect_state { uint8_t scan_dir; //0 = X, 1 = Y uint8_t scan_pos; -} ai_fire_reflect_state_t; +}; -typedef struct {} ai_fire_future_state_t; +struct ai_fire_future_state {}; -typedef union { - ai_fire_random_state_t random; - ai_fire_current_state_t current; - ai_fire_reflect_state_t reflect; - ai_fire_future_state_t future; -} ai_fire_state_t; +union ai_fire_state { + struct ai_fire_random_state random; + struct ai_fire_current_state current; + struct ai_fire_reflect_state reflect; + struct ai_fire_future_state future; +}; -typedef struct { +struct ai_move_random_state { uint8_t cur_dir; -} ai_move_random_state_t; +}; -typedef struct {} ai_move_toward_state_t; +struct ai_move_toward_state {}; -typedef struct { +struct ai_move_away_state { uint target_x; uint target_y; -} ai_move_away_state_t; +}; -typedef union ai_move { - ai_move_random_state_t random; - ai_move_toward_state_t toward; - ai_move_away_state_t away; -} ai_move_state_t; +union ai_move_state { + struct ai_move_random_state random; + struct ai_move_toward_state toward; + struct ai_move_away_state away; +}; #endif //TANKS_AI_STATE_H diff --git a/src/game.cpp b/src/game.cpp index dbbae3e..b9abbdb 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -3,27 +3,26 @@ #include #include #include "objects/tank.h" -#include "globals.h" #include "graphics/graphics.h" -#include "graphics/gui.h" #include "input.h" #include "util/profiler.h" #include "graphics/tank_sprite.h" +#include "gui/error.h" +#include "gui/transition.h" -bool start_mission(const serialized_tank_t *ser_tanks) { +struct game game; + +bool start_mission(const struct serialized_tank *ser_tanks) { dbg_printf("starting mission\n"); - for(auto & obj : PhysicsBody::objects) { - obj->active = false; - } - PhysicsBody::remove_inactive(); + PhysicsBody::remove_all(); game.num_tanks = 0; for(uint8_t i = 0; i < game.level.num_tanks; i++) { if(!game.alive_tanks[i]) continue; //dbg_printf("tank created: %p\n", tank); - Tank *tank = new (std::nothrow) Tank(&ser_tanks[i], i); + Tank *tank = new(std::nothrow) Tank(&ser_tanks[i], i); if(!tank) { ERROR("Failed to allocate tank"); } @@ -31,8 +30,8 @@ bool start_mission(const serialized_tank_t *ser_tanks) { for(uint8_t x = 1; x < LEVEL_SIZE_X - 1; x++) { for(uint8_t y = 1; y < LEVEL_SIZE_Y - 1; y++) { - if(TILE_TYPE(tiles[y][x]) == DESTROYED) - tiles[y][x] = TILE_HEIGHT(tiles[y][x]) | DESTRUCTIBLE; + if(TILE_TYPE(game.tiles[y][x]) == DESTROYED) + game.tiles[y][x] = TILE_HEIGHT(game.tiles[y][x]) | DESTRUCTIBLE; } } @@ -47,7 +46,7 @@ bool start_mission(const serialized_tank_t *ser_tanks) { return true; } -uint8_t play_mission(const serialized_tank_t *ser_tanks) { +uint8_t play_mission(const struct serialized_tank *ser_tanks) { start_mission(ser_tanks); while(true) { profiler_start(total); @@ -64,7 +63,7 @@ uint8_t play_mission(const serialized_tank_t *ser_tanks) { profiler_start(pb_collision); process_collisions(); profiler_end(pb_collision); - for(auto & object : PhysicsBody::objects) { + for(auto &object : PhysicsBody::objects) { object->tick(); } PhysicsBody::remove_inactive(); @@ -98,10 +97,10 @@ uint8_t play_mission(const serialized_tank_t *ser_tanks) { } } -uint8_t play_level(const void *comp_tiles, const serialized_tank_t *ser_tanks) { +uint8_t play_level(const void *comp_tiles, const struct serialized_tank *ser_tanks) { decompress_tiles(comp_tiles); - for(uint8_t i = 0; i < game.level.num_tanks; i++){ + for(uint8_t i = 0; i < game.level.num_tanks; i++) { game.alive_tanks[i] = true; } diff --git a/src/game.h b/src/game.h index 43fdd47..c80b517 100644 --- a/src/game.h +++ b/src/game.h @@ -3,8 +3,28 @@ #include "objects/tank.h" -bool start_mission(const serialized_tank_t *ser_tanks); //Start a mission and reset various tank things. -uint8_t play_level(const void *comp_tiles, const serialized_tank_t *ser_tanks); -uint8_t play_mission(const serialized_tank_t *ser_tanks); +// Game status +enum { + QUIT = 1, NEXT_LEVEL, RETRY, LOSE, ERROR +}; + +struct game { + struct level level; //level currently being played + uint8_t mission; //The mission number, always displayed 1 higher than stored. Also used as an index for levels. + tile_t tiles[LEVEL_SIZE_Y][LEVEL_SIZE_X]; //Currently active tilemap data + uint8_t lives; //Number of remaining tanks. This includes the tank that is currently in use, so a value of 1 means that the game will end the next time the tank is hit. + uint8_t total_kills; //Number of enemy tanks destroyed. + uint8_t kills[NUM_TANK_TYPES]; + uint8_t num_tanks; + bool alive_tanks[MAX_NUM_TANKS]; + Tank *player; + uint24_t tick; +}; + +extern struct game game; + +bool start_mission(const struct serialized_tank *ser_tanks); //Start a mission and reset various tank things. +uint8_t play_level(const void *comp_tiles, const struct serialized_tank *ser_tanks); +uint8_t play_mission(const struct serialized_tank *ser_tanks); #endif //TANKS_GAME_H diff --git a/src/globals.cpp b/src/globals.cpp deleted file mode 100644 index 2255d33..0000000 --- a/src/globals.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "globals.h" - -game_t game; //Game global, so I can reuse those names elsewhere if needed - -tile_t tiles[LEVEL_SIZE_Y][LEVEL_SIZE_X]; //Currently active tilemap data \ No newline at end of file diff --git a/src/globals.h b/src/globals.h deleted file mode 100644 index 0a5ac85..0000000 --- a/src/globals.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef TANKS_GLOBALS_H -#define TANKS_GLOBALS_H - -#include -#include "objects/tank.h" -#include "level.h" - -// Game status -enum { - QUIT = 1, NEXT_LEVEL, RETRY, LOSE, ERROR -}; - -typedef struct { - level_t level; //level currently being played - uint8_t mission; //The mission number, always displayed 1 higher than stored. Also used as an index for levels. - uint8_t lives; //Number of remaining tanks. This includes the tank that is currently in use, so a value of 1 means that the game will end the next time the tank is hit. - uint8_t total_kills; //Number of enemy tanks destroyed. - uint8_t kills[NUM_TANK_TYPES]; - uint8_t num_tanks; - bool alive_tanks[MAX_NUM_TANKS]; - Tank *player; - uint24_t tick; -} game_t; - -extern tile_t tiles[LEVEL_SIZE_Y][LEVEL_SIZE_X]; -extern game_t game; - -#endif //TANKS_GLOBALS_H diff --git a/src/graphics/graphics.cpp b/src/graphics/graphics.cpp index 8338bc3..2792eda 100644 --- a/src/graphics/graphics.cpp +++ b/src/graphics/graphics.cpp @@ -1,14 +1,13 @@ #include "graphics.h" -#include - -#include "../globals.h" #include "../util/profiler.h" #include "partial_redraw.h" #include "dynamic_sprites.h" -#include "gui.h" #include "../data/gfx/enemy_pal.h" #include "tiles.h" +#include "../game.h" +#include "../gui/banner.h" +#include "../gui/aim_indicator.h" bool needs_redraw; @@ -72,7 +71,7 @@ void render() { gfx_SetClipRegion(SCREEN_X_CONST(0), SCREEN_Y_CONST(-TILE_SIZE), SCREEN_X_CONST(LEVEL_SIZE_X * TILE_SIZE), SCREEN_Y_CONST((LEVEL_SIZE_Y - 2) * TILE_SIZE)); for(uint8_t layer = 0; layer < 3; layer++) { - for(auto *it: PhysicsBody::objects) { + for(auto it: PhysicsBody::objects) { it->render(layer); } } @@ -87,7 +86,3 @@ void render() { profiler_end(graphics); } - -void draw_line(line_seg_t *ls) { - gfx_Line(SCREEN_X(ls->x1), SCREEN_Y(ls->y1), SCREEN_X(ls->x2), SCREEN_Y(ls->y2)); -} diff --git a/src/graphics/graphics.h b/src/graphics/graphics.h index 48f6291..a4f2045 100644 --- a/src/graphics/graphics.h +++ b/src/graphics/graphics.h @@ -86,13 +86,9 @@ void init_graphics(); void render(); //Render tilemap, tanks, and UI during the game loop -void draw_line(line_seg_t *ls); - void get_sprite_footprint(gfx_region_t *out, const PhysicsBody *phys, gfx_sprite_t **sprites, const uint8_t *offsets_x, const uint8_t *offsets_y, uint8_t anim); -void get_enemy_palette_map(uint8_t *out, tank_type_t type); - extern bool needs_redraw; // set if the entire map should be redrawn #endif diff --git a/src/graphics/gui.cpp b/src/graphics/gui.cpp deleted file mode 100644 index 4c0eb82..0000000 --- a/src/graphics/gui.cpp +++ /dev/null @@ -1,581 +0,0 @@ -#include "gui.h" - -#include -#include -#include -#include -#include - -#include "graphics.h" -#include "../globals.h" -#include "partial_redraw.h" -#include "../util/profiler.h" -#include "repalettize.h" - -void display_scores() { - -} - -// todo: improve -void display_kill_counts() { - const uint bg_width = 120; - const uint base_x = (LCD_WIDTH - bg_width) / 2; - - const uint8_t bands_base_y = 17; - const uint8_t band_height = 3; - const uint8_t num_bands = 4; - const uint8_t bands_total_height = num_bands * 2 * band_height - band_height; - - const uint8_t text_base_y = bands_base_y + bands_total_height + 20; - const uint8_t line_spacing = 18; - const uint text_center_point = LCD_WIDTH / 2 + 19; - const uint8_t char_width = 8; - - const uint sprite_center = LCD_WIDTH / 2 - 19; - const uint sprite_base_y = text_base_y - 2; - - const uint8_t bottom_band_y = LCD_HEIGHT - 36; - - const uint final_box_width = 38; - const uint8_t final_box_height = 19; - const uint final_box_x = text_center_point - final_box_width / 2; - const uint8_t final_box_y = bottom_band_y + 2 * band_height; - const uint8_t box_text_y = final_box_y + (final_box_height - 6 * 2) / 2; - - const char results[] = "Results"; - - gfx_SetColor(COL_BG); - gfx_FillRectangle(base_x, 0, bg_width, LCD_HEIGHT); - - gfx_SetColor(COL_OLIVE_BANDS); - for(uint8_t i = 0; i < num_bands; i++) - gfx_FillRectangle(base_x, bands_base_y + i * 2 * band_height, bg_width, band_height); - - gfx_FillRectangle(base_x, bottom_band_y, bg_width, band_height); - - gfx_SetColor(COL_WHITE); - gfx_FillRectangle(final_box_x, final_box_y, final_box_width, final_box_height); - gfx_FillCircle(final_box_x, final_box_y + final_box_height / 2, final_box_height / 2); - gfx_FillCircle(final_box_x + final_box_width, final_box_y + final_box_height / 2, final_box_height / 2); - - gfx_SetTextFGColor(COL_BLACK); - gfx_SetTextScale(2, 2); - gfx_PrintStringXY(results, (LCD_WIDTH - gfx_GetStringWidth(results)) / 2, bands_base_y + band_height); - - // todo: P1 text - - gfx_UninitedSprite(en_tank_shadow, en_tank_width, en_tank_height); - get_sprite_shadow(en_tank_shadow, en_tank, COL_RIB_SHADOW); - - gfx_SetTextScale(1, 1); - for(uint8_t i = 0; i < NUM_TANK_TYPES - 1; i++) { - uint8_t num_kills = game.kills[i + 1]; - if(!num_kills) continue; - - palette_map_t palette_map; - get_enemy_palette_map(palette_map, i + 1); - gfx_UninitedSprite(en_tank_palettized, en_tank_width, en_tank_height); - repalettize_sprite(en_tank_palettized, en_tank, palette_map); - - gfx_TransparentSprite_NoClip(en_tank_shadow, sprite_center - en_tank_width / 2 + 2, sprite_base_y + line_spacing * i + 2); - gfx_TransparentSprite_NoClip(en_tank_palettized, sprite_center - en_tank_width / 2, sprite_base_y + line_spacing * i); - - if(num_kills >= 10) { - gfx_SetTextXY(text_center_point - char_width, text_base_y + line_spacing * i); - gfx_PrintUInt(num_kills, 2); - } else { - gfx_SetTextXY(text_center_point - char_width / 2, text_base_y + line_spacing * i); - gfx_PrintUInt(num_kills, 1); - } - gfx_BlitBuffer(); - delay(500); - } - - gfx_SetTextScale(2, 2); - if(game.total_kills > 100) { - gfx_SetTextXY(text_center_point - char_width * 3 / 2, box_text_y); - gfx_PrintUInt(game.total_kills, 3); - } else if(game.total_kills > 10) { - gfx_SetTextXY(text_center_point - char_width, box_text_y); - gfx_PrintUInt(game.total_kills, 2); - } else { - gfx_SetTextXY(text_center_point - char_width / 2, box_text_y); - gfx_PrintUInt(game.total_kills, 1); - } - - gfx_BlitBuffer(); - - while(!kb_IsDown(kb_KeyEnter) && !kb_IsDown(kb_KeyClear)); -} - -void draw_tank_background(uint8_t min_y, uint8_t max_y, uint8_t lives, uint8_t life_color, const gfx_sprite_t *tank_shadow) { - static uint8_t frame = 0, offset = 0; - gfx_SetColor(COL_BG); - gfx_FillRectangle_NoClip(0, 0, LCD_WIDTH, min_y); - gfx_FillRectangle_NoClip(0, max_y, LCD_WIDTH, LCD_HEIGHT - max_y); - - const uint TANK_TILE_SIZE = 34; - const uint8_t FRAMES_PER_PX = 3; - - frame++; - if(frame == FRAMES_PER_PX) { - frame = 0; - offset++; - if(offset == TANK_TILE_SIZE) { - offset = 0; - } - } - - // Draw the background tank sprite - for(int x = offset - TANK_TILE_SIZE; x < LCD_WIDTH; x += TANK_TILE_SIZE) { - gfx_SetClipRegion(0, 0, LCD_WIDTH, min_y); - for(int y = -offset; y < min_y; y += TANK_TILE_SIZE) { - gfx_TransparentSprite(bg_tank, x, y); - } - gfx_SetClipRegion(0, max_y, LCD_WIDTH, LCD_HEIGHT); - for(int y = -offset; y < LCD_WIDTH; y += TANK_TILE_SIZE) { - gfx_TransparentSprite(bg_tank, x, y); - } - } - gfx_SetClipRegion(0, 0, LCD_WIDTH, LCD_HEIGHT); - - gfx_SetTextXY((LCD_WIDTH - 8 * MISSION_NUMBER_TEXT) / 2 + 2, 186 + 2); - gfx_SetTextFGColor(COL_RIB_SHADOW); - gfx_PrintString("x "); - gfx_PrintUInt(lives, 1); - - gfx_SetTextXY((LCD_WIDTH - 8 * MISSION_NUMBER_TEXT) / 2, 186); - gfx_SetTextFGColor(life_color); - gfx_PrintString("x "); - gfx_PrintUInt(lives, 1); - - gfx_TransparentSprite_NoClip(tank_shadow, LCD_WIDTH / 2 - fg_tank_width - 30, 189); - gfx_TransparentSprite_NoClip(fg_tank, LCD_WIDTH / 2 - fg_tank_width - 32, 187); -} - -//Screen is 700 (240) pixels tall -//Background is (228,230,173) -//140 (48) pixels between the top and banner -//294 (?) pixel tall red banner -//Red banner has checkerboard with 38x52 (12x15) pixel rhombi -//Colors of rhombi are (174,49,48) and (164,45,46) -//16 (5) pixel shadow underneath (183,185,139) -//10 (3) pixel gap on top, 12 (4) on bottom -//12 (4) pixel tall gold (193,162,43) band on top and bottom -//40 (14) (12) pixels between band and text -//60 (21) (24) pixel tall mission number text w/ shadow -//50 (17) (16) pixels between -//44 (15) (16) pixel tall Enemies Remaining text w/ shadow -//50 (17) (16) pixels between text and bottom band -//Text shadow (134,36,37) has 8px (3px) offset -//# of lives text (70,127,111) - centered between bottom or ribbon and bottom of screen -void mission_start_screen(uint8_t mission, uint8_t lives, uint8_t num_tanks) { - timer_Disable(1); - timer_Set(1, 33 * MISSION_START_TIME); - timer_SetReload(1, 33 * MISSION_START_TIME); - timer_AckInterrupt(1, TIMER_RELOADED); - timer_Enable(1, TIMER_32K, TIMER_0INT, TIMER_DOWN); - - gfx_FillScreen(COL_BG); - - gfx_SetColor(COL_RHOM_1); - - const int BANNER_TOP = 48; - const int BANNER_BOTTOM = 148; - const int SHADOW_BOTTOM = BANNER_BOTTOM + 4; - - gfx_FillRectangle_NoClip(0, BANNER_TOP, LCD_WIDTH, BANNER_BOTTOM - BANNER_TOP); - gfx_SetColor(COL_RHOM_2); - for(int x = 0; x <= LCD_WIDTH / 12; x++) { - for(int y = 0; y < 6; y++) { - gfx_FillTriangle(x * 12 + 6, 55 + y * 15, x * 12, y * 15 + 55 + 7, x * 12 + 12, y * 15 + 55 + 7); - gfx_FillTriangle(x * 12 + 6, 55 + y * 15 + 15, x * 12, y * 15 + 55 + 7, x * 12 + 12, y * 15 + 55 + 7); - } - } - - gfx_SetColor(COL_RIB_SHADOW); - gfx_FillRectangle_NoClip(0, BANNER_BOTTOM, LCD_WIDTH, SHADOW_BOTTOM - BANNER_BOTTOM); - - gfx_SetColor(COL_GOLD); - gfx_FillRectangle_NoClip(0, 51, LCD_WIDTH, 4); - gfx_FillRectangle_NoClip(0, 140, LCD_WIDTH, 4); - - //Print mission number - gfx_SetTextScale(MISSION_NUMBER_TEXT, MISSION_NUMBER_TEXT); - gfx_SetTextFGColor(COL_TXT_SHADOW); - gfx_SetTextXY((LCD_WIDTH - 60 * MISSION_NUMBER_TEXT) / 2 + 3, 70 + 3); - gfx_PrintString("Mission "); - gfx_PrintUInt(mission + 1, 1 + (mission >= 9) + (mission >= 99)); - - gfx_SetTextFGColor(COL_BG); - gfx_SetTextXY((LCD_WIDTH - 60 * MISSION_NUMBER_TEXT) / 2, 70); - gfx_PrintString("Mission "); - gfx_PrintUInt(mission + 1, 1 + (mission >= 9) + (mission >= 99)); - - - gfx_SetTextScale(ENEMY_TANK_TEXT, ENEMY_TANK_TEXT); - gfx_SetTextFGColor(COL_TXT_SHADOW); - gfx_SetTextXY((LCD_WIDTH - 97 * ENEMY_TANK_TEXT) / 2 + 3, 110 + 3); - gfx_PrintString("Enemy Tanks: "); - gfx_PrintUInt(num_tanks, 1); - gfx_SetTextXY((LCD_WIDTH - 8 * MISSION_NUMBER_TEXT) / 2, 150); - - gfx_SetTextFGColor(COL_BG); - gfx_SetTextXY((LCD_WIDTH - 97 * ENEMY_TANK_TEXT) / 2, 110); - gfx_PrintString("Enemy Tanks: "); - gfx_PrintUInt(num_tanks, 1); - - gfx_BlitBuffer(); - - gfx_UninitedSprite(fg_tank_shadow, fg_tank_width, fg_tank_height); - get_sprite_shadow(fg_tank_shadow, fg_tank, COL_RIB_SHADOW); - - while(true) { - if(timer_ChkInterrupt(1, TIMER_RELOADED)) { - timer_AckInterrupt(1, TIMER_RELOADED); - break; - } - if(kb_Data[1] & kb_2nd || kb_Data[1] & kb_Del || kb_Data[6] & kb_Clear) { - while(kb_Data[1] || kb_Data[6]); - break; - } - - draw_tank_background(BANNER_TOP, SHADOW_BOTTOM, lives, COL_LIVES_TXT, fg_tank_shadow); - - gfx_SwapDraw(); - } - - gfx_SetTextFGColor(COL_BLACK); - gfx_SetTextScale(1, 1); -} - -void extra_life_screen(uint8_t old_lives) { - const uint8_t BANNER_START_Y = 86; - const uint8_t BANNER_HEIGHT = 35; - const uint BANNER_WIDTH = 306; - const uint8_t NOTCH_WIDTH = 8; - const uint8_t SHADOW_HEIGHT = 5; - const uint8_t STAR_X_OFFSET = 10; - - const uint BANNER_START_X = LCD_WIDTH / 2 - BANNER_WIDTH / 2; - const uint BANNER_END_X = BANNER_START_X + BANNER_WIDTH; - const uint8_t BANNER_END_Y = BANNER_START_Y + BANNER_HEIGHT; - - const uint WAIT_TIME = 33 * MISSION_START_TIME; - - timer_Disable(1); - timer_Set(1, WAIT_TIME); - timer_SetReload(1, WAIT_TIME); - timer_AckInterrupt(1, TIMER_RELOADED); - timer_Enable(1, TIMER_32K, TIMER_0INT, TIMER_DOWN); - - gfx_FillScreen(COL_BG); - - gfx_SetColor(COL_LIGHT_GREEN); - gfx_FillRectangle_NoClip(BANNER_START_X, BANNER_START_Y, BANNER_WIDTH, BANNER_HEIGHT); - - gfx_SetColor(COL_DARK_GREEN); - gfx_FillRectangle_NoClip(BANNER_START_X, BANNER_START_Y, BANNER_WIDTH, 2); - gfx_FillRectangle_NoClip(BANNER_START_X, BANNER_END_Y - 2, BANNER_WIDTH, 2); - gfx_HorizLine_NoClip(BANNER_START_X, BANNER_START_Y + 3, BANNER_WIDTH); - gfx_HorizLine_NoClip(BANNER_START_X, BANNER_END_Y - 4, BANNER_WIDTH); - - gfx_SetColor(COL_BG); - gfx_FillTriangle_NoClip(BANNER_START_X, BANNER_START_Y, - BANNER_START_X, BANNER_END_Y, - BANNER_START_X + NOTCH_WIDTH, BANNER_START_Y + BANNER_HEIGHT / 2); - gfx_FillTriangle_NoClip(BANNER_END_X, BANNER_START_Y, - BANNER_END_X, BANNER_END_Y, - BANNER_END_X - NOTCH_WIDTH, BANNER_START_Y + BANNER_HEIGHT / 2); - - gfx_SetTextScale(2, 2); - uint text_x = LCD_WIDTH / 2 - gfx_GetStringWidth("Bonus Tank!") / 2; - uint text_y = BANNER_START_Y + BANNER_HEIGHT / 2 - 8; - - gfx_SetTextFGColor(COL_DARK_GREEN); - gfx_PrintStringXY("Bonus Tank!", text_x + 2, text_y + 2); - - gfx_SetTextFGColor(COL_GOLD); - gfx_PrintStringXY("Bonus Tank!", text_x, text_y); - - gfx_TransparentSprite_NoClip(star, BANNER_START_X + STAR_X_OFFSET, BANNER_START_Y + BANNER_HEIGHT / 2 - star_height / 2); - gfx_TransparentSprite_NoClip(star, BANNER_END_X - STAR_X_OFFSET - star_width, BANNER_START_Y + BANNER_HEIGHT / 2 - star_height / 2); - - gfx_BlitBuffer(); - - gfx_UninitedSprite(fg_tank_shadow, fg_tank_width, fg_tank_height); - get_sprite_shadow(fg_tank_shadow, fg_tank, COL_RIB_SHADOW); - - while(true) { - if(timer_ChkInterrupt(1, TIMER_RELOADED)) { - timer_AckInterrupt(1, TIMER_RELOADED); - break; - } - if(kb_Data[1] & kb_2nd || kb_Data[1] & kb_Del || kb_Data[6] & kb_Clear) { - while(kb_Data[1] || kb_Data[6]); - break; - } - - if(timer_GetLow(1) > WAIT_TIME * 2 / 3) { - draw_tank_background(BANNER_START_Y, BANNER_START_Y + BANNER_HEIGHT + SHADOW_HEIGHT, - old_lives, COL_LIVES_TXT, fg_tank_shadow); - } else if(timer_GetLow(1) > WAIT_TIME / 3) { - draw_tank_background(BANNER_START_Y, BANNER_START_Y + BANNER_HEIGHT + SHADOW_HEIGHT, - old_lives + 1, COL_GOLD, fg_tank_shadow); - } else { - draw_tank_background(BANNER_START_Y, BANNER_START_Y + BANNER_HEIGHT + SHADOW_HEIGHT, - old_lives + 1, COL_LIVES_TXT, fg_tank_shadow); - } - - gfx_SwapDraw(); - } - - gfx_SetTextFGColor(COL_BLACK); - gfx_SetTextScale(1, 1); -} - -#define KILL_COUNTER_END_X (SCREEN_DELTA_X_CONST(2.75 * TILE_SIZE)) -#define KILL_COUNTER_INNER_END_X (SCREEN_DELTA_X_CONST(2.4 * TILE_SIZE)) -#define KILL_COUNTER_RADIUS ((uint)SCREEN_DELTA_Y_CONST(TILE_SIZE) - 1) -#define KILL_COUNTER_INNER_RADIUS ((uint)SCREEN_DELTA_Y_CONST(0.75 * TILE_SIZE)) -#define KILL_COUNTER_HEIGHT (2 * KILL_COUNTER_RADIUS + 1) -#define KILL_COUNTER_INNER_HEIGHT (2 * KILL_COUNTER_INNER_RADIUS + 1) -#define KILL_COUNTER_Y (SCREEN_Y_CONST((LEVEL_SIZE_Y - 2) * TILE_SIZE)) -#define KILL_COUNTER_INNER_Y (KILL_COUNTER_Y + KILL_COUNTER_RADIUS - KILL_COUNTER_INNER_RADIUS) - -void update_game_kill_counter_current_buffer(uint8_t kills) { - uint8_t digits = 1 + (kills > 9) + (kills > 99); - uint8_t width = gfx_GetCharWidth('1') * digits; - uint8_t x = KILL_COUNTER_INNER_END_X - KILL_COUNTER_INNER_RADIUS - width; - gfx_SetColor(COL_WHITE); - gfx_FillRectangle_NoClip(x, KILL_COUNTER_INNER_Y, width, KILL_COUNTER_INNER_HEIGHT); - gfx_SetTextFGColor(COL_LIVES_TXT); - gfx_SetTextXY(x, KILL_COUNTER_INNER_Y + 5); - gfx_PrintUInt(kills, digits); -} - -// todo: ?????? -void update_game_kill_counter(uint8_t kills, bool force) { - static uint8_t last = -1; - if(!force && last == kills) return; - last = kills; - gfx_SetDrawScreen(); - update_game_kill_counter_current_buffer(kills); - gfx_SetDrawBuffer(); - update_game_kill_counter_current_buffer(kills); -} - -void display_game_kill_counter() { - gfx_SetColor(COL_LIVES_TXT); // todo: add a bluer color - gfx_FillCircle_NoClip(KILL_COUNTER_END_X - KILL_COUNTER_RADIUS, KILL_COUNTER_Y + KILL_COUNTER_RADIUS, - KILL_COUNTER_RADIUS); - gfx_FillRectangle_NoClip(0, KILL_COUNTER_Y, KILL_COUNTER_END_X - KILL_COUNTER_RADIUS, KILL_COUNTER_HEIGHT); - - gfx_SetColor(COL_WHITE); - gfx_FillCircle_NoClip(KILL_COUNTER_INNER_END_X - KILL_COUNTER_INNER_RADIUS - 1, - KILL_COUNTER_INNER_Y + KILL_COUNTER_INNER_RADIUS, KILL_COUNTER_INNER_RADIUS); - gfx_FillRectangle_NoClip(0, KILL_COUNTER_INNER_Y, KILL_COUNTER_INNER_END_X - KILL_COUNTER_INNER_RADIUS - 1, - KILL_COUNTER_INNER_HEIGHT); -} - -// todo: apparently this shows the number of alive tanks, not the number of lives -void display_game_banner(uint8_t mission, uint8_t lives) { - // todo: check if the compiler optimizes these properly - const uint8_t banner_width = SCREEN_DELTA_X_CONST(10.5 * TILE_SIZE); - const uint8_t banner_height = 14; - const uint base_x = (LCD_WIDTH - banner_width) / 2; - const uint8_t base_y = SCREEN_Y_CONST((LEVEL_SIZE_Y - 2) * TILE_SIZE) + 3; - const uint8_t text_x = base_x + 18; - const uint text2_x = base_x + 122; - const uint8_t text_y = base_y + 3; - const uint8_t rhomb_width = 7; - - gfx_SetColor(COL_RHOM_1); - gfx_HorizLine(base_x + 1, base_y, banner_width - 2); - gfx_HorizLine(base_x + 1, base_y + banner_height - 1, banner_width - 2); - gfx_FillRectangle_NoClip(base_x, base_y + 1, banner_width, banner_height - 2); - - gfx_SetColor(COL_RHOM_2); - for(uint x = base_x; x < base_x + banner_width; x += rhomb_width) { - gfx_FillTriangle_NoClip(x, base_y + banner_height / 2 - 1, x + rhomb_width - 1, base_y + banner_height / 2 - 1, - x + rhomb_width / 2, base_y); - gfx_FillTriangle_NoClip(x, base_y + banner_height / 2 - 1, x + rhomb_width - 1, base_y + banner_height / 2 - 1, - x + rhomb_width / 2, base_y + banner_height - 1); - } - - gfx_SetTextXY(text_x + 1, text_y + 1); - gfx_SetTextFGColor(COL_TXT_SHADOW); - gfx_PrintString("Mission "); - gfx_PrintUInt(mission, 1); - gfx_SetTextXY(text2_x + 1, text_y + 1); - gfx_PrintString("x "); - gfx_PrintUInt(lives, 1); - - gfx_SetTextXY(text_x, text_y); - gfx_SetTextFGColor(COL_BG); - gfx_PrintString("Mission "); - gfx_PrintUInt(mission, 1); - gfx_SetTextXY(text2_x, text_y); - gfx_PrintString("x "); - gfx_PrintUInt(lives, 1); -} - -void rounded_rectangle(uint24_t x, uint8_t y, uint24_t width, uint8_t height, uint8_t radius) { - gfx_FillCircle_NoClip(x + radius + 1, y + radius + 1, radius); - gfx_FillCircle_NoClip(x + radius + 1, y + height - radius - 1, radius); - gfx_FillCircle_NoClip(x + width - radius - 1, y + radius + 1, radius); - gfx_FillCircle_NoClip(x + width - radius - 1, y + height - radius - 1, radius); - - gfx_FillRectangle_NoClip(x + radius + 1, y, x + width - x - 2 * radius - 2, radius + 1); - gfx_FillRectangle_NoClip(x, y + radius + 1, x + width - x, y + height - y - 2 * radius - 2); - gfx_FillRectangle_NoClip(x + radius + 1, y + height - radius - 1, x + width - x - 2 * radius - 2, radius + 1); -} - -uint8_t pause_menu() { - const uint8_t OUTER_BORDER_WIDTH = 2; - const uint8_t INNER_BORDER_WIDTH = 2; - const uint8_t BORDER_CURVE_RADIUS = 7; - const uint24_t BUTTON_WIDTH = 244; - //const uint24_t BUTTON_WIDTH_SELECTED = 269; - const uint8_t BUTTON_HEIGHT = 58; - //const uint8_t BUTTON_HEIGHT_SELECTED = 63; - const uint8_t BUTTON_GAP = 7; - - const uint8_t TOP_BUTTON_CENTER_Y = LCD_HEIGHT / 2 - BUTTON_GAP - BUTTON_HEIGHT; - - needs_redraw = true; - - int8_t selection = 0; - - gfx_SetTextFGColor(COL_BLACK); - gfx_SetTextScale(2, 2); - - while(true) { - if(kb_IsDown(kb_KeyClear)) { - gfx_SetTextScale(1, 1); - return 0; - } - if(kb_IsDown(kb_Key2nd) || kb_IsDown(kb_KeyClear)) { - gfx_SetTextScale(1, 1); - return selection; - } - if(kb_IsDown(kb_KeyEnter)) return selection; - - if(kb_IsDown(kb_KeyUp)) selection--; - if(kb_IsDown(kb_KeyDown)) selection++; - if(selection < 0) selection = 2; - if(selection >= 3) selection = 0; - - while(kb_IsDown(kb_KeyUp) || kb_IsDown(kb_KeyDown)); - - for(int8_t button = 0; button < 3; button++) { - const char *strings[3] = {"Continue", "Start Over", "Quit"}; - uint8_t center_y = TOP_BUTTON_CENTER_Y + button * (BUTTON_HEIGHT + BUTTON_GAP); - uint24_t x = LCD_WIDTH / 2 - BUTTON_WIDTH / 2; - uint8_t y = center_y - BUTTON_HEIGHT / 2; - - gfx_SetColor(COL_WHITE); - rounded_rectangle(x, y, BUTTON_WIDTH, BUTTON_HEIGHT, BORDER_CURVE_RADIUS); - - gfx_SetColor(COL_LIVES_TXT); - rounded_rectangle(x + OUTER_BORDER_WIDTH, y + OUTER_BORDER_WIDTH, - BUTTON_WIDTH - 2 * OUTER_BORDER_WIDTH, BUTTON_HEIGHT - 2 * OUTER_BORDER_WIDTH, - BORDER_CURVE_RADIUS - OUTER_BORDER_WIDTH); - - gfx_SetColor(button == selection ? COL_RIB_SHADOW : COL_WHITE); - rounded_rectangle(x + OUTER_BORDER_WIDTH + INNER_BORDER_WIDTH, - y + OUTER_BORDER_WIDTH + INNER_BORDER_WIDTH, - BUTTON_WIDTH - 2 * (OUTER_BORDER_WIDTH + INNER_BORDER_WIDTH), - BUTTON_HEIGHT - 2 * (OUTER_BORDER_WIDTH + INNER_BORDER_WIDTH), - BORDER_CURVE_RADIUS - OUTER_BORDER_WIDTH - INNER_BORDER_WIDTH); - - gfx_SetTextXY(LCD_WIDTH / 2 - gfx_GetStringWidth(strings[button]) / 2, y + BUTTON_HEIGHT / 2 - 8); - gfx_PrintString(strings[button]); - } - gfx_BlitBuffer(); - }; -} - -void draw_aim_dots() { - profiler_start(aim_indicator); - const uint8_t NUM_DOTS = 6; - line_seg_t line; - angle_t angle = game.player->barrel_rot; - - profiler_add(raycast); - raycast(game.player->center_x(), game.player->center_y(), angle, &line); - profiler_end(raycast); - - int dx = (line.x2 - line.x1) / (NUM_DOTS - 1); - int dy = (line.y2 - line.y1) / (NUM_DOTS - 1); - - int x = line.x1; - int y = line.y1; - - for(uint8_t dot = 0; dot < NUM_DOTS; dot++) { - pdraw_TransparentSprite_NoClip(aim_dot, SCREEN_X(x) - aim_dot_width / 2, SCREEN_Y(y) - aim_dot_height / 2 - 10); - x += dx; - y += dy; - } - profiler_end(aim_indicator); -} - -#ifndef COMMIT -#define COMMIT "non-git" -#endif - -[[noreturn]] void error_screen(const char *error, const char *file, uint24_t line) { - uint24_t *sp; - asm("\tld\thl,0\n" - "\tadd\thl,sp" - : "=l" (sp)); -#ifndef NDEBUG - dbg_sprintf(dbgerr, "Error: \"%s\" at %s:%u\n", error, file, line); - *(char*)-1 = 2; // Open CEmu debugger, if the other call fails - dbg_Debugger(); -#endif - gfx_palette[0] = gfx_RGBTo1555(0, 120, 215); - gfx_palette[255] = gfx_RGBTo1555(255, 255, 255); - gfx_SetDrawScreen(); - gfx_ZeroScreen(); - gfx_SetTextFGColor(255); - gfx_SetTextBGColor(0); - gfx_SetTextTransparentColor(0); - - gfx_SetTextScale(4, 4); - const char *messages[] = {":(", "bruh", "wat", ">_<"}; - gfx_PrintStringXY(messages[randInt(0,3)], 16, 16); - - gfx_SetTextScale(1, 1); - gfx_PrintStringXY("Tanks ran into a problem.", 16, 60); - gfx_PrintStringXY("Press clear to return to TI-OS.", 16, 72); - - gfx_PrintStringXY("If you're using the latest", 16, 96); - gfx_PrintStringXY("version of Tanks,", 16, 108); - gfx_PrintStringXY("please submit a bug report:", 16, 120); - gfx_PrintStringXY("https://git.io/JtJpA", 16, 132); - - gfx_PrintStringXY("Include this info:", 16, 156); - gfx_PrintStringXY(error, 16, 168); - - gfx_PrintStringXY(file, 16, 180); - gfx_PrintChar(':'); - gfx_PrintUInt(line, 1); - gfx_PrintString(" @ " COMMIT); - - gfx_PrintStringXY("Stack trace:", LCD_WIDTH - 16 - gfx_GetStringWidth("Stack trace:"), 16); - uint8_t width = gfx_GetCharWidth('0') * 8; - for(uint8_t y = 28; y < LCD_HEIGHT - 10; y += 10, sp++) { - gfx_SetTextXY(LCD_WIDTH - 16 - width, y); - gfx_PrintUInt(*sp, 8); - } - - asm("ei"); // For screenshots - while(kb_IsDown(kb_KeyClear)); - while(!kb_IsDown(kb_KeyClear)); - asm("di"); - - gfx_End(); - ti_CloseAll(); - exit(1); -} diff --git a/src/graphics/gui.h b/src/graphics/gui.h deleted file mode 100644 index ffcd4f2..0000000 --- a/src/graphics/gui.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef TANKS_GUI_H -#define TANKS_GUI_H - -#include - -#include - -//amount of time in milliseconds the mission start screen displays -#define MISSION_START_TIME 3000 -//Font size -#define MISSION_NUMBER_TEXT 3 -#define ENEMY_TANK_TEXT 2 - -void display_scores(); //Display high scores - -void display_kill_counts(); - -void mission_start_screen(uint8_t mission, uint8_t lives, uint8_t num_tanks); - -void extra_life_screen(uint8_t old_lives); - -void update_game_kill_counter(uint8_t kills, bool force); - -void display_game_kill_counter(); - -void display_game_banner(uint8_t mission, uint8_t lives); - -uint8_t pause_menu(); - -void draw_aim_dots(); - -[[noreturn]] void error_screen(const char *error, const char *file, uint24_t line); - -#define ERROR(msg) error_screen(msg, __FILE_NAME__, __LINE__) - -#endif //TANKS_GUI_H diff --git a/src/graphics/partial_redraw.cpp b/src/graphics/partial_redraw.cpp index 5639223..e649550 100644 --- a/src/graphics/partial_redraw.cpp +++ b/src/graphics/partial_redraw.cpp @@ -6,7 +6,7 @@ #include "../util/profiler.h" bool pdraw_current_buffer = false; -tinystl::vector pdraw_sprites[2]; +tinystl::vector pdraw_sprites[2]; bool pdraw_RectRegion(uint x, uint8_t y, uint8_t width, uint8_t height) { profiler_add(store_bg); @@ -48,7 +48,7 @@ void pdraw_TransparentSprite_NoClip(gfx_sprite_t *sprite, uint x, uint8_t y) { void pdraw_RemoveSprites() { pdraw_current_buffer = !pdraw_current_buffer; while(!pdraw_sprites[pdraw_current_buffer].empty()) { - pdraw_sprite_t & psprite = pdraw_sprites[pdraw_current_buffer].back(); + struct pdraw_sprite & psprite = pdraw_sprites[pdraw_current_buffer].back(); gfx_Sprite_NoClip(psprite.sprite, psprite.x, psprite.y); free(psprite.sprite); pdraw_sprites[pdraw_current_buffer].pop_back(); @@ -57,7 +57,7 @@ void pdraw_RemoveSprites() { void pdraw_FreeAll() { for(auto & pdraw_sprite : pdraw_sprites) { - for(pdraw_sprite_t & psprite : pdraw_sprite) { + for(struct pdraw_sprite & psprite : pdraw_sprite) { free(psprite.sprite); } pdraw_sprite.clear(); diff --git a/src/graphics/partial_redraw.h b/src/graphics/partial_redraw.h index 0e59066..fb9988c 100644 --- a/src/graphics/partial_redraw.h +++ b/src/graphics/partial_redraw.h @@ -5,11 +5,11 @@ #include "../util/util.h" -typedef struct { +struct pdraw_sprite { uint x; uint8_t y; gfx_sprite_t *sprite; -} pdraw_sprite_t; +}; // Store a rectangular region for redraw on the next relevant frame bool pdraw_RectRegion(uint x, uint8_t y, uint8_t width, uint8_t height); diff --git a/src/graphics/repalettize.h b/src/graphics/repalettize.h index 0edfc90..ffcf1dd 100644 --- a/src/graphics/repalettize.h +++ b/src/graphics/repalettize.h @@ -1,6 +1,7 @@ #ifndef TANKS_REPALETTIZE_H #define TANKS_REPALETTIZE_H +#include "../objects/tank.h" #include #define NUM_NON_DYNAMIC_COLORS 4 @@ -12,4 +13,6 @@ void repalettize_sprite(gfx_sprite_t *out, const gfx_sprite_t *in, const uint8_t void get_sprite_shadow(gfx_sprite_t *out, gfx_sprite_t *in, uint8_t shadow_color); +void get_enemy_palette_map(uint8_t *out, tank_type_t type); + #endif //TANKS_REPALETTIZE_H diff --git a/src/graphics/tank_sprite.cpp b/src/graphics/tank_sprite.cpp index f25290b..074616f 100644 --- a/src/graphics/tank_sprite.cpp +++ b/src/graphics/tank_sprite.cpp @@ -1,10 +1,10 @@ #include #include "tank_sprite.h" #include "dynamic_sprites.h" -#include "gui.h" #include "graphics.h" #include "repalettize.h" -#include "../globals.h" +#include "../game.h" +#include "../gui/error.h" #define MAX_SPRITES 30 diff --git a/src/graphics/tiles.cpp b/src/graphics/tiles.cpp index 767ce3c..a7f4d3a 100644 --- a/src/graphics/tiles.cpp +++ b/src/graphics/tiles.cpp @@ -1,7 +1,7 @@ #include "tiles.h" #include "graphics.h" -#include "../globals.h" #include "../util/profiler.h" +#include "../game.h" uint8_t tilemap[TILEMAP_HEIGHT][TILEMAP_WIDTH]; // For each tilemap tile, the level Y of the block that it's representing @@ -22,7 +22,7 @@ void generate_bg_tilemap() { for(int8_t y = 0; y < LEVEL_SIZE_Y; y++) { uint8_t x; for(x = 0; x < TILEMAP_WIDTH; x++) { - tile_t tile = tiles[y][x]; + tile_t tile = game.tiles[y][x]; uint8_t height = TILE_HEIGHT(tile); uint8_t type = TILE_TYPE(tile); bool tall = bottom_is_tall[height]; diff --git a/src/gui/aim_indicator.cpp b/src/gui/aim_indicator.cpp new file mode 100644 index 0000000..9e50bf8 --- /dev/null +++ b/src/gui/aim_indicator.cpp @@ -0,0 +1,32 @@ +#include +#include "aim_indicator.h" +#include "../util/trig.h" +#include "../game.h" +#include "../util/profiler.h" +#include "../data/gfx/aim_dot.h" +#include "../graphics/graphics.h" +#include "../graphics/partial_redraw.h" + +void draw_aim_dots() { + profiler_start(aim_indicator); + const uint8_t NUM_DOTS = 6; + struct line_seg line; + angle_t angle = game.player->barrel_rot; + + profiler_add(raycast); + raycast(game.player->center_x(), game.player->center_y(), angle, &line); + profiler_end(raycast); + + int dx = (line.x2 - line.x1) / (NUM_DOTS - 1); + int dy = (line.y2 - line.y1) / (NUM_DOTS - 1); + + int x = line.x1; + int y = line.y1; + + for(uint8_t dot = 0; dot < NUM_DOTS; dot++) { + pdraw_TransparentSprite_NoClip(aim_dot, SCREEN_X(x) - aim_dot_width / 2, SCREEN_Y(y) - aim_dot_height / 2 - 10); + x += dx; + y += dy; + } + profiler_end(aim_indicator); +} diff --git a/src/gui/aim_indicator.h b/src/gui/aim_indicator.h new file mode 100644 index 0000000..51aebf2 --- /dev/null +++ b/src/gui/aim_indicator.h @@ -0,0 +1,6 @@ +#ifndef TANKS_AIM_INDICATOR_H +#define TANKS_AIM_INDICATOR_H + +void draw_aim_dots(); + +#endif //TANKS_AIM_INDICATOR_H diff --git a/src/gui/banner.cpp b/src/gui/banner.cpp new file mode 100644 index 0000000..7b9f0d2 --- /dev/null +++ b/src/gui/banner.cpp @@ -0,0 +1,92 @@ +#include "../game.h" +#include +#include +#include +#include "banner.h" +#include "../graphics/graphics.h" + +#define KILL_COUNTER_END_X (SCREEN_DELTA_X_CONST(2.75 * TILE_SIZE)) +#define KILL_COUNTER_INNER_END_X (SCREEN_DELTA_X_CONST(2.4 * TILE_SIZE)) +#define KILL_COUNTER_RADIUS ((uint)SCREEN_DELTA_Y_CONST(TILE_SIZE) - 1) +#define KILL_COUNTER_INNER_RADIUS ((uint)SCREEN_DELTA_Y_CONST(0.75 * TILE_SIZE)) +#define KILL_COUNTER_HEIGHT (2 * KILL_COUNTER_RADIUS + 1) +#define KILL_COUNTER_INNER_HEIGHT (2 * KILL_COUNTER_INNER_RADIUS + 1) +#define KILL_COUNTER_Y (SCREEN_Y_CONST((LEVEL_SIZE_Y - 2) * TILE_SIZE)) +#define KILL_COUNTER_INNER_Y (KILL_COUNTER_Y + KILL_COUNTER_RADIUS - KILL_COUNTER_INNER_RADIUS) + +void update_game_kill_counter_current_buffer(uint8_t kills) { + uint8_t digits = 1 + (kills > 9) + (kills > 99); + uint8_t width = gfx_GetCharWidth('1') * digits; + uint8_t x = KILL_COUNTER_INNER_END_X - KILL_COUNTER_INNER_RADIUS - width; + gfx_SetColor(COL_WHITE); + gfx_FillRectangle_NoClip(x, KILL_COUNTER_INNER_Y, width, KILL_COUNTER_INNER_HEIGHT); + gfx_SetTextFGColor(COL_LIVES_TXT); + gfx_SetTextXY(x, KILL_COUNTER_INNER_Y + 5); + gfx_PrintUInt(kills, digits); +} + +// todo: ?????? +void update_game_kill_counter(uint8_t kills, bool force) { + static uint8_t last = -1; + if(!force && last == kills) return; + last = kills; + gfx_SetDrawScreen(); + update_game_kill_counter_current_buffer(kills); + gfx_SetDrawBuffer(); + update_game_kill_counter_current_buffer(kills); +} + +void display_game_kill_counter() { + gfx_SetColor(COL_LIVES_TXT); // todo: add a bluer color + gfx_FillCircle_NoClip(KILL_COUNTER_END_X - KILL_COUNTER_RADIUS, KILL_COUNTER_Y + KILL_COUNTER_RADIUS, + KILL_COUNTER_RADIUS); + gfx_FillRectangle_NoClip(0, KILL_COUNTER_Y, KILL_COUNTER_END_X - KILL_COUNTER_RADIUS, KILL_COUNTER_HEIGHT); + + gfx_SetColor(COL_WHITE); + gfx_FillCircle_NoClip(KILL_COUNTER_INNER_END_X - KILL_COUNTER_INNER_RADIUS - 1, + KILL_COUNTER_INNER_Y + KILL_COUNTER_INNER_RADIUS, KILL_COUNTER_INNER_RADIUS); + gfx_FillRectangle_NoClip(0, KILL_COUNTER_INNER_Y, KILL_COUNTER_INNER_END_X - KILL_COUNTER_INNER_RADIUS - 1, + KILL_COUNTER_INNER_HEIGHT); +} + +// todo: apparently this shows the number of alive tanks, not the number of lives +void display_game_banner(uint8_t mission, uint8_t lives) { + // todo: check if the compiler optimizes these properly + const uint8_t banner_width = SCREEN_DELTA_X_CONST(10.5 * TILE_SIZE); + const uint8_t banner_height = 14; + const uint base_x = (LCD_WIDTH - banner_width) / 2; + const uint8_t base_y = SCREEN_Y_CONST((LEVEL_SIZE_Y - 2) * TILE_SIZE) + 3; + const uint8_t text_x = base_x + 18; + const uint text2_x = base_x + 122; + const uint8_t text_y = base_y + 3; + const uint8_t rhomb_width = 7; + + gfx_SetColor(COL_RHOM_1); + gfx_HorizLine(base_x + 1, base_y, banner_width - 2); + gfx_HorizLine(base_x + 1, base_y + banner_height - 1, banner_width - 2); + gfx_FillRectangle_NoClip(base_x, base_y + 1, banner_width, banner_height - 2); + + gfx_SetColor(COL_RHOM_2); + for(uint x = base_x; x < base_x + banner_width; x += rhomb_width) { + gfx_FillTriangle_NoClip(x, base_y + banner_height / 2 - 1, x + rhomb_width - 1, base_y + banner_height / 2 - 1, + x + rhomb_width / 2, base_y); + gfx_FillTriangle_NoClip(x, base_y + banner_height / 2 - 1, x + rhomb_width - 1, base_y + banner_height / 2 - 1, + x + rhomb_width / 2, base_y + banner_height - 1); + } + + gfx_SetTextXY(text_x + 1, text_y + 1); + gfx_SetTextFGColor(COL_TXT_SHADOW); + gfx_PrintString("Mission "); + gfx_PrintUInt(mission, 1); + gfx_SetTextXY(text2_x + 1, text_y + 1); + gfx_PrintString("x "); + gfx_PrintUInt(lives, 1); + + gfx_SetTextXY(text_x, text_y); + gfx_SetTextFGColor(COL_BG); + gfx_PrintString("Mission "); + gfx_PrintUInt(mission, 1); + gfx_SetTextXY(text2_x, text_y); + gfx_PrintString("x "); + gfx_PrintUInt(lives, 1); +} \ No newline at end of file diff --git a/src/gui/banner.h b/src/gui/banner.h new file mode 100644 index 0000000..b908ba7 --- /dev/null +++ b/src/gui/banner.h @@ -0,0 +1,13 @@ +#ifndef TANKS_BANNER_H +#define TANKS_BANNER_H + +void update_game_kill_counter(uint8_t kills, bool force); + +void display_game_kill_counter(); + +void display_game_banner(uint8_t mission, uint8_t lives); + +#include +#include + +#endif //TANKS_BANNER_H diff --git a/src/gui/error.cpp b/src/gui/error.cpp new file mode 100644 index 0000000..d0ad629 --- /dev/null +++ b/src/gui/error.cpp @@ -0,0 +1,67 @@ +#include "error.h" + +#include +#include +#include +#include +#include + +#ifndef COMMIT +#define COMMIT "non-git" +#endif + +[[noreturn]] void error_screen(const char *error, const char *file, uint24_t line) { + uint24_t *sp; + asm("\tld\thl,0\n" + "\tadd\thl,sp" + : "=l" (sp)); +#ifndef NDEBUG + dbg_sprintf(dbgerr, "Error: \"%s\" at %s:%u\n", error, file, line); + *(char*)-1 = 2; // Open CEmu debugger, if the other call fails + dbg_Debugger(); +#endif + gfx_palette[0] = gfx_RGBTo1555(0, 120, 215); + gfx_palette[255] = gfx_RGBTo1555(255, 255, 255); + gfx_SetDrawScreen(); + gfx_ZeroScreen(); + gfx_SetTextFGColor(255); + gfx_SetTextBGColor(0); + gfx_SetTextTransparentColor(0); + + gfx_SetTextScale(4, 4); + const char *messages[] = {":(", "bruh", "wat", ">_<"}; + gfx_PrintStringXY(messages[randInt(0,3)], 16, 16); + + gfx_SetTextScale(1, 1); + gfx_PrintStringXY("Tanks ran into a problem.", 16, 60); + gfx_PrintStringXY("Press clear to return to TI-OS.", 16, 72); + + gfx_PrintStringXY("If you're using the latest", 16, 96); + gfx_PrintStringXY("version of Tanks,", 16, 108); + gfx_PrintStringXY("please submit a bug report:", 16, 120); + gfx_PrintStringXY("https://git.io/JtJpA", 16, 132); + + gfx_PrintStringXY("Include this info:", 16, 156); + gfx_PrintStringXY(error, 16, 168); + + gfx_PrintStringXY(file, 16, 180); + gfx_PrintChar(':'); + gfx_PrintUInt(line, 1); + gfx_PrintString(" @ " COMMIT); + + gfx_PrintStringXY("Stack trace:", LCD_WIDTH - 16 - gfx_GetStringWidth("Stack trace:"), 16); + uint8_t width = gfx_GetCharWidth('0') * 8; + for(uint8_t y = 28; y < LCD_HEIGHT - 10; y += 10, sp++) { + gfx_SetTextXY(LCD_WIDTH - 16 - width, y); + gfx_PrintUInt(*sp, 8); + } + + asm("ei"); // For screenshots + while(kb_IsDown(kb_KeyClear)); + while(!kb_IsDown(kb_KeyClear)); + asm("di"); + + gfx_End(); + ti_CloseAll(); + exit(1); +} \ No newline at end of file diff --git a/src/gui/error.h b/src/gui/error.h new file mode 100644 index 0000000..1d2f484 --- /dev/null +++ b/src/gui/error.h @@ -0,0 +1,11 @@ +#ifndef TANKS_ERROR_H +#define TANKS_ERROR_H + +#include +#include + +#define ERROR(msg) error_screen(msg, __FILE_NAME__, __LINE__) + +[[noreturn]] void error_screen(const char *error, const char *file, uint24_t line); + +#endif //TANKS_ERROR_H diff --git a/src/gui/kill_counts.cpp b/src/gui/kill_counts.cpp new file mode 100644 index 0000000..5ed3675 --- /dev/null +++ b/src/gui/kill_counts.cpp @@ -0,0 +1,101 @@ +#include "kill_counts.h" + +#include "../game.h" +#include "../graphics/repalettize.h" +#include "../graphics/partial_redraw.h" +#include "../graphics/graphics.h" +#include +#include +#include + +// todo: improve +void display_kill_counts() { + const uint bg_width = 120; + const uint base_x = (LCD_WIDTH - bg_width) / 2; + + const uint8_t bands_base_y = 17; + const uint8_t band_height = 3; + const uint8_t num_bands = 4; + const uint8_t bands_total_height = num_bands * 2 * band_height - band_height; + + const uint8_t text_base_y = bands_base_y + bands_total_height + 20; + const uint8_t line_spacing = 18; + const uint text_center_point = LCD_WIDTH / 2 + 19; + const uint8_t char_width = 8; + + const uint sprite_center = LCD_WIDTH / 2 - 19; + const uint sprite_base_y = text_base_y - 2; + + const uint8_t bottom_band_y = LCD_HEIGHT - 36; + + const uint final_box_width = 38; + const uint8_t final_box_height = 19; + const uint final_box_x = text_center_point - final_box_width / 2; + const uint8_t final_box_y = bottom_band_y + 2 * band_height; + const uint8_t box_text_y = final_box_y + (final_box_height - 6 * 2) / 2; + + const char results[] = "Results"; + + gfx_SetColor(COL_BG); + gfx_FillRectangle(base_x, 0, bg_width, LCD_HEIGHT); + + gfx_SetColor(COL_OLIVE_BANDS); + for(uint8_t i = 0; i < num_bands; i++) + gfx_FillRectangle(base_x, bands_base_y + i * 2 * band_height, bg_width, band_height); + + gfx_FillRectangle(base_x, bottom_band_y, bg_width, band_height); + + gfx_SetColor(COL_WHITE); + gfx_FillRectangle(final_box_x, final_box_y, final_box_width, final_box_height); + gfx_FillCircle(final_box_x, final_box_y + final_box_height / 2, final_box_height / 2); + gfx_FillCircle(final_box_x + final_box_width, final_box_y + final_box_height / 2, final_box_height / 2); + + gfx_SetTextFGColor(COL_BLACK); + gfx_SetTextScale(2, 2); + gfx_PrintStringXY(results, (LCD_WIDTH - gfx_GetStringWidth(results)) / 2, bands_base_y + band_height); + + // todo: P1 text + + gfx_UninitedSprite(en_tank_shadow, en_tank_width, en_tank_height); + get_sprite_shadow(en_tank_shadow, en_tank, COL_RIB_SHADOW); + + gfx_SetTextScale(1, 1); + for(uint8_t i = 0; i < NUM_TANK_TYPES - 1; i++) { + uint8_t num_kills = game.kills[i + 1]; + if(!num_kills) continue; + + uint8_t palette_map[9]; + get_enemy_palette_map(palette_map, i + 1); + gfx_UninitedSprite(en_tank_palettized, en_tank_width, en_tank_height); + repalettize_sprite(en_tank_palettized, en_tank, palette_map); + + gfx_TransparentSprite_NoClip(en_tank_shadow, sprite_center - en_tank_width / 2 + 2, sprite_base_y + line_spacing * i + 2); + gfx_TransparentSprite_NoClip(en_tank_palettized, sprite_center - en_tank_width / 2, sprite_base_y + line_spacing * i); + + if(num_kills >= 10) { + gfx_SetTextXY(text_center_point - char_width, text_base_y + line_spacing * i); + gfx_PrintUInt(num_kills, 2); + } else { + gfx_SetTextXY(text_center_point - char_width / 2, text_base_y + line_spacing * i); + gfx_PrintUInt(num_kills, 1); + } + gfx_BlitBuffer(); + delay(500); + } + + gfx_SetTextScale(2, 2); + if(game.total_kills > 100) { + gfx_SetTextXY(text_center_point - char_width * 3 / 2, box_text_y); + gfx_PrintUInt(game.total_kills, 3); + } else if(game.total_kills > 10) { + gfx_SetTextXY(text_center_point - char_width, box_text_y); + gfx_PrintUInt(game.total_kills, 2); + } else { + gfx_SetTextXY(text_center_point - char_width / 2, box_text_y); + gfx_PrintUInt(game.total_kills, 1); + } + + gfx_BlitBuffer(); + + while(!kb_IsDown(kb_KeyEnter) && !kb_IsDown(kb_KeyClear)); +} \ No newline at end of file diff --git a/src/gui/kill_counts.h b/src/gui/kill_counts.h new file mode 100644 index 0000000..2a5655b --- /dev/null +++ b/src/gui/kill_counts.h @@ -0,0 +1,9 @@ +#ifndef TANKS_KILL_COUNTS_H +#define TANKS_KILL_COUNTS_H + +#include +#include + +void display_kill_counts(); + +#endif //TANKS_KILL_COUNTS_H diff --git a/src/gui/pause.cpp b/src/gui/pause.cpp new file mode 100644 index 0000000..72860e9 --- /dev/null +++ b/src/gui/pause.cpp @@ -0,0 +1,82 @@ +#include "pause.h" + +#include "../graphics/graphics.h" +#include +#include +#include + +void rounded_rectangle(uint24_t x, uint8_t y, uint24_t width, uint8_t height, uint8_t radius) { + gfx_FillCircle_NoClip(x + radius + 1, y + radius + 1, radius); + gfx_FillCircle_NoClip(x + radius + 1, y + height - radius - 1, radius); + gfx_FillCircle_NoClip(x + width - radius - 1, y + radius + 1, radius); + gfx_FillCircle_NoClip(x + width - radius - 1, y + height - radius - 1, radius); + + gfx_FillRectangle_NoClip(x + radius + 1, y, x + width - x - 2 * radius - 2, radius + 1); + gfx_FillRectangle_NoClip(x, y + radius + 1, x + width - x, y + height - y - 2 * radius - 2); + gfx_FillRectangle_NoClip(x + radius + 1, y + height - radius - 1, x + width - x - 2 * radius - 2, radius + 1); +} + +uint8_t pause_menu() { + const uint8_t OUTER_BORDER_WIDTH = 2; + const uint8_t INNER_BORDER_WIDTH = 2; + const uint8_t BORDER_CURVE_RADIUS = 7; + const uint24_t BUTTON_WIDTH = 244; + //const uint24_t BUTTON_WIDTH_SELECTED = 269; + const uint8_t BUTTON_HEIGHT = 58; + //const uint8_t BUTTON_HEIGHT_SELECTED = 63; + const uint8_t BUTTON_GAP = 7; + + const uint8_t TOP_BUTTON_CENTER_Y = LCD_HEIGHT / 2 - BUTTON_GAP - BUTTON_HEIGHT; + + needs_redraw = true; + + int8_t selection = 0; + + gfx_SetTextFGColor(COL_BLACK); + gfx_SetTextScale(2, 2); + + while(true) { + if(kb_IsDown(kb_KeyClear)) { + gfx_SetTextScale(1, 1); + return 0; + } + if(kb_IsDown(kb_Key2nd) || kb_IsDown(kb_KeyClear)) { + gfx_SetTextScale(1, 1); + return selection; + } + if(kb_IsDown(kb_KeyEnter)) return selection; + + if(kb_IsDown(kb_KeyUp)) selection--; + if(kb_IsDown(kb_KeyDown)) selection++; + if(selection < 0) selection = 2; + if(selection >= 3) selection = 0; + + while(kb_IsDown(kb_KeyUp) || kb_IsDown(kb_KeyDown)); + + for(int8_t button = 0; button < 3; button++) { + const char *strings[3] = {"Continue", "Start Over", "Quit"}; + uint8_t center_y = TOP_BUTTON_CENTER_Y + button * (BUTTON_HEIGHT + BUTTON_GAP); + uint24_t x = LCD_WIDTH / 2 - BUTTON_WIDTH / 2; + uint8_t y = center_y - BUTTON_HEIGHT / 2; + + gfx_SetColor(COL_WHITE); + rounded_rectangle(x, y, BUTTON_WIDTH, BUTTON_HEIGHT, BORDER_CURVE_RADIUS); + + gfx_SetColor(COL_LIVES_TXT); + rounded_rectangle(x + OUTER_BORDER_WIDTH, y + OUTER_BORDER_WIDTH, + BUTTON_WIDTH - 2 * OUTER_BORDER_WIDTH, BUTTON_HEIGHT - 2 * OUTER_BORDER_WIDTH, + BORDER_CURVE_RADIUS - OUTER_BORDER_WIDTH); + + gfx_SetColor(button == selection ? COL_RIB_SHADOW : COL_WHITE); + rounded_rectangle(x + OUTER_BORDER_WIDTH + INNER_BORDER_WIDTH, + y + OUTER_BORDER_WIDTH + INNER_BORDER_WIDTH, + BUTTON_WIDTH - 2 * (OUTER_BORDER_WIDTH + INNER_BORDER_WIDTH), + BUTTON_HEIGHT - 2 * (OUTER_BORDER_WIDTH + INNER_BORDER_WIDTH), + BORDER_CURVE_RADIUS - OUTER_BORDER_WIDTH - INNER_BORDER_WIDTH); + + gfx_SetTextXY(LCD_WIDTH / 2 - gfx_GetStringWidth(strings[button]) / 2, y + BUTTON_HEIGHT / 2 - 8); + gfx_PrintString(strings[button]); + } + gfx_BlitBuffer(); + }; +} \ No newline at end of file diff --git a/src/gui/pause.h b/src/gui/pause.h new file mode 100644 index 0000000..944e2da --- /dev/null +++ b/src/gui/pause.h @@ -0,0 +1,9 @@ +#ifndef TANKS_PAUSE_H +#define TANKS_PAUSE_H + +#include +#include + +uint8_t pause_menu(); + +#endif //TANKS_PAUSE_H diff --git a/src/gui/transition.cpp b/src/gui/transition.cpp new file mode 100644 index 0000000..07638d9 --- /dev/null +++ b/src/gui/transition.cpp @@ -0,0 +1,238 @@ +#include "transition.h" + +#include "../graphics/repalettize.h" +#include "../graphics/partial_redraw.h" +#include "../graphics/graphics.h" +#include +#include +#include + +#define MISSION_START_TIME 3000 +#define MISSION_NUMBER_TEXT 3 +#define ENEMY_TANK_TEXT 2 + +void draw_tank_background(uint8_t min_y, uint8_t max_y, uint8_t lives, uint8_t life_color, const gfx_sprite_t *tank_shadow) { + static uint8_t frame = 0, offset = 0; + gfx_SetColor(COL_BG); + gfx_FillRectangle_NoClip(0, 0, LCD_WIDTH, min_y); + gfx_FillRectangle_NoClip(0, max_y, LCD_WIDTH, LCD_HEIGHT - max_y); + + const uint TANK_TILE_SIZE = 34; + const uint8_t FRAMES_PER_PX = 3; + + frame++; + if(frame == FRAMES_PER_PX) { + frame = 0; + offset++; + if(offset == TANK_TILE_SIZE) { + offset = 0; + } + } + + // Draw the background tank sprite + for(int x = offset - TANK_TILE_SIZE; x < LCD_WIDTH; x += TANK_TILE_SIZE) { + gfx_SetClipRegion(0, 0, LCD_WIDTH, min_y); + for(int y = -offset; y < min_y; y += TANK_TILE_SIZE) { + gfx_TransparentSprite(bg_tank, x, y); + } + gfx_SetClipRegion(0, max_y, LCD_WIDTH, LCD_HEIGHT); + for(int y = -offset; y < LCD_WIDTH; y += TANK_TILE_SIZE) { + gfx_TransparentSprite(bg_tank, x, y); + } + } + gfx_SetClipRegion(0, 0, LCD_WIDTH, LCD_HEIGHT); + + gfx_SetTextXY((LCD_WIDTH - 8 * MISSION_NUMBER_TEXT) / 2 + 2, 186 + 2); + gfx_SetTextFGColor(COL_RIB_SHADOW); + gfx_PrintString("x "); + gfx_PrintUInt(lives, 1); + + gfx_SetTextXY((LCD_WIDTH - 8 * MISSION_NUMBER_TEXT) / 2, 186); + gfx_SetTextFGColor(life_color); + gfx_PrintString("x "); + gfx_PrintUInt(lives, 1); + + gfx_TransparentSprite_NoClip(tank_shadow, LCD_WIDTH / 2 - fg_tank_width - 30, 189); + gfx_TransparentSprite_NoClip(fg_tank, LCD_WIDTH / 2 - fg_tank_width - 32, 187); +} + +//Screen is 700 (240) pixels tall +//Background is (228,230,173) +//140 (48) pixels between the top and banner +//294 (?) pixel tall red banner +//Red banner has checkerboard with 38x52 (12x15) pixel rhombi +//Colors of rhombi are (174,49,48) and (164,45,46) +//16 (5) pixel shadow underneath (183,185,139) +//10 (3) pixel gap on top, 12 (4) on bottom +//12 (4) pixel tall gold (193,162,43) band on top and bottom +//40 (14) (12) pixels between band and text +//60 (21) (24) pixel tall mission number text w/ shadow +//50 (17) (16) pixels between +//44 (15) (16) pixel tall Enemies Remaining text w/ shadow +//50 (17) (16) pixels between text and bottom band +//Text shadow (134,36,37) has 8px (3px) offset +//# of lives text (70,127,111) - centered between bottom or ribbon and bottom of screen +void mission_start_screen(uint8_t mission, uint8_t lives, uint8_t num_tanks) { + timer_Disable(1); + timer_Set(1, 33 * MISSION_START_TIME); + timer_SetReload(1, 33 * MISSION_START_TIME); + timer_AckInterrupt(1, TIMER_RELOADED); + timer_Enable(1, TIMER_32K, TIMER_0INT, TIMER_DOWN); + + gfx_FillScreen(COL_BG); + + gfx_SetColor(COL_RHOM_1); + + const int BANNER_TOP = 48; + const int BANNER_BOTTOM = 148; + const int SHADOW_BOTTOM = BANNER_BOTTOM + 4; + + gfx_FillRectangle_NoClip(0, BANNER_TOP, LCD_WIDTH, BANNER_BOTTOM - BANNER_TOP); + gfx_SetColor(COL_RHOM_2); + for(int x = 0; x <= LCD_WIDTH / 12; x++) { + for(int y = 0; y < 6; y++) { + gfx_FillTriangle(x * 12 + 6, 55 + y * 15, x * 12, y * 15 + 55 + 7, x * 12 + 12, y * 15 + 55 + 7); + gfx_FillTriangle(x * 12 + 6, 55 + y * 15 + 15, x * 12, y * 15 + 55 + 7, x * 12 + 12, y * 15 + 55 + 7); + } + } + + gfx_SetColor(COL_RIB_SHADOW); + gfx_FillRectangle_NoClip(0, BANNER_BOTTOM, LCD_WIDTH, SHADOW_BOTTOM - BANNER_BOTTOM); + + gfx_SetColor(COL_GOLD); + gfx_FillRectangle_NoClip(0, 51, LCD_WIDTH, 4); + gfx_FillRectangle_NoClip(0, 140, LCD_WIDTH, 4); + + //Print mission number + gfx_SetTextScale(MISSION_NUMBER_TEXT, MISSION_NUMBER_TEXT); + gfx_SetTextFGColor(COL_TXT_SHADOW); + gfx_SetTextXY((LCD_WIDTH - 60 * MISSION_NUMBER_TEXT) / 2 + 3, 70 + 3); + gfx_PrintString("Mission "); + gfx_PrintUInt(mission + 1, 1 + (mission >= 9) + (mission >= 99)); + + gfx_SetTextFGColor(COL_BG); + gfx_SetTextXY((LCD_WIDTH - 60 * MISSION_NUMBER_TEXT) / 2, 70); + gfx_PrintString("Mission "); + gfx_PrintUInt(mission + 1, 1 + (mission >= 9) + (mission >= 99)); + + + gfx_SetTextScale(ENEMY_TANK_TEXT, ENEMY_TANK_TEXT); + gfx_SetTextFGColor(COL_TXT_SHADOW); + gfx_SetTextXY((LCD_WIDTH - 97 * ENEMY_TANK_TEXT) / 2 + 3, 110 + 3); + gfx_PrintString("Enemy Tanks: "); + gfx_PrintUInt(num_tanks, 1); + gfx_SetTextXY((LCD_WIDTH - 8 * MISSION_NUMBER_TEXT) / 2, 150); + + gfx_SetTextFGColor(COL_BG); + gfx_SetTextXY((LCD_WIDTH - 97 * ENEMY_TANK_TEXT) / 2, 110); + gfx_PrintString("Enemy Tanks: "); + gfx_PrintUInt(num_tanks, 1); + + gfx_BlitBuffer(); + + gfx_UninitedSprite(fg_tank_shadow, fg_tank_width, fg_tank_height); + get_sprite_shadow(fg_tank_shadow, fg_tank, COL_RIB_SHADOW); + + while(true) { + if(timer_ChkInterrupt(1, TIMER_RELOADED)) { + timer_AckInterrupt(1, TIMER_RELOADED); + break; + } + if(kb_Data[1] & kb_2nd || kb_Data[1] & kb_Del || kb_Data[6] & kb_Clear) { + while(kb_Data[1] || kb_Data[6]); + break; + } + + draw_tank_background(BANNER_TOP, SHADOW_BOTTOM, lives, COL_LIVES_TXT, fg_tank_shadow); + + gfx_SwapDraw(); + } + + gfx_SetTextFGColor(COL_BLACK); + gfx_SetTextScale(1, 1); +} + +void extra_life_screen(uint8_t old_lives) { + const uint8_t BANNER_START_Y = 86; + const uint8_t BANNER_HEIGHT = 35; + const uint BANNER_WIDTH = 306; + const uint8_t NOTCH_WIDTH = 8; + const uint8_t SHADOW_HEIGHT = 5; + const uint8_t STAR_X_OFFSET = 10; + + const uint BANNER_START_X = LCD_WIDTH / 2 - BANNER_WIDTH / 2; + const uint BANNER_END_X = BANNER_START_X + BANNER_WIDTH; + const uint8_t BANNER_END_Y = BANNER_START_Y + BANNER_HEIGHT; + + const uint WAIT_TIME = 33 * MISSION_START_TIME; + + timer_Disable(1); + timer_Set(1, WAIT_TIME); + timer_SetReload(1, WAIT_TIME); + timer_AckInterrupt(1, TIMER_RELOADED); + timer_Enable(1, TIMER_32K, TIMER_0INT, TIMER_DOWN); + + gfx_FillScreen(COL_BG); + + gfx_SetColor(COL_LIGHT_GREEN); + gfx_FillRectangle_NoClip(BANNER_START_X, BANNER_START_Y, BANNER_WIDTH, BANNER_HEIGHT); + + gfx_SetColor(COL_DARK_GREEN); + gfx_FillRectangle_NoClip(BANNER_START_X, BANNER_START_Y, BANNER_WIDTH, 2); + gfx_FillRectangle_NoClip(BANNER_START_X, BANNER_END_Y - 2, BANNER_WIDTH, 2); + gfx_HorizLine_NoClip(BANNER_START_X, BANNER_START_Y + 3, BANNER_WIDTH); + gfx_HorizLine_NoClip(BANNER_START_X, BANNER_END_Y - 4, BANNER_WIDTH); + + gfx_SetColor(COL_BG); + gfx_FillTriangle_NoClip(BANNER_START_X, BANNER_START_Y, + BANNER_START_X, BANNER_END_Y, + BANNER_START_X + NOTCH_WIDTH, BANNER_START_Y + BANNER_HEIGHT / 2); + gfx_FillTriangle_NoClip(BANNER_END_X, BANNER_START_Y, + BANNER_END_X, BANNER_END_Y, + BANNER_END_X - NOTCH_WIDTH, BANNER_START_Y + BANNER_HEIGHT / 2); + + gfx_SetTextScale(2, 2); + uint text_x = LCD_WIDTH / 2 - gfx_GetStringWidth("Bonus Tank!") / 2; + uint text_y = BANNER_START_Y + BANNER_HEIGHT / 2 - 8; + + gfx_SetTextFGColor(COL_DARK_GREEN); + gfx_PrintStringXY("Bonus Tank!", text_x + 2, text_y + 2); + + gfx_SetTextFGColor(COL_GOLD); + gfx_PrintStringXY("Bonus Tank!", text_x, text_y); + + gfx_TransparentSprite_NoClip(star, BANNER_START_X + STAR_X_OFFSET, BANNER_START_Y + BANNER_HEIGHT / 2 - star_height / 2); + gfx_TransparentSprite_NoClip(star, BANNER_END_X - STAR_X_OFFSET - star_width, BANNER_START_Y + BANNER_HEIGHT / 2 - star_height / 2); + + gfx_BlitBuffer(); + + gfx_UninitedSprite(fg_tank_shadow, fg_tank_width, fg_tank_height); + get_sprite_shadow(fg_tank_shadow, fg_tank, COL_RIB_SHADOW); + + while(true) { + if(timer_ChkInterrupt(1, TIMER_RELOADED)) { + timer_AckInterrupt(1, TIMER_RELOADED); + break; + } + if(kb_Data[1] & kb_2nd || kb_Data[1] & kb_Del || kb_Data[6] & kb_Clear) { + while(kb_Data[1] || kb_Data[6]); + break; + } + + if(timer_GetLow(1) > WAIT_TIME * 2 / 3) { + draw_tank_background(BANNER_START_Y, BANNER_START_Y + BANNER_HEIGHT + SHADOW_HEIGHT, + old_lives, COL_LIVES_TXT, fg_tank_shadow); + } else if(timer_GetLow(1) > WAIT_TIME / 3) { + draw_tank_background(BANNER_START_Y, BANNER_START_Y + BANNER_HEIGHT + SHADOW_HEIGHT, + old_lives + 1, COL_GOLD, fg_tank_shadow); + } else { + draw_tank_background(BANNER_START_Y, BANNER_START_Y + BANNER_HEIGHT + SHADOW_HEIGHT, + old_lives + 1, COL_LIVES_TXT, fg_tank_shadow); + } + + gfx_SwapDraw(); + } + + gfx_SetTextFGColor(COL_BLACK); + gfx_SetTextScale(1, 1); +} \ No newline at end of file diff --git a/src/gui/transition.h b/src/gui/transition.h new file mode 100644 index 0000000..908eeff --- /dev/null +++ b/src/gui/transition.h @@ -0,0 +1,11 @@ +#ifndef TANKS_TRANSITION_H +#define TANKS_TRANSITION_H + +#include +#include + +void mission_start_screen(uint8_t mission, uint8_t lives, uint8_t num_tanks); + +void extra_life_screen(uint8_t old_lives); + +#endif //TANKS_TRANSITION_H diff --git a/src/input.cpp b/src/input.cpp index c1f3b28..1c49c15 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -1,9 +1,9 @@ -#include -#include "input.h" -#include "util/profiler.h" +#include "game.h" +#include "gui/pause.h" #include "objects/tank.h" -#include "globals.h" -#include "graphics/gui.h" +#include "util/profiler.h" + +#include #define PLAYER_BARREL_ROTATION DEGREES_TO_ANGLE(5) //1/3 of a second for 90 degree rotation diff --git a/src/level.cpp b/src/level.cpp index 8c3a561..b41c667 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -1,17 +1,16 @@ #include "level.h" +#include "game.h" #include -#include "globals.h" - void decompress_tiles(const void *comp_tiles) { //Decompress tile data - zx7_Decompress(tiles, comp_tiles); + zx7_Decompress(game.tiles, comp_tiles); for(uint8_t row = LEVEL_SIZE_Y - 2; row > 0; row--) { - auto *orig_tiles = (tile_t(*)[LEVEL_SIZE_X - 2])tiles; - memmove(&tiles[row][1], orig_tiles[row - 1], LEVEL_SIZE_X - 2); - tiles[row][0] = tiles[row][LEVEL_SIZE_X - 1] = 1; + auto orig_tiles = (tile_t(*)[LEVEL_SIZE_X - 2])game.tiles; + memmove(&game.tiles[row][1], orig_tiles[row - 1], LEVEL_SIZE_X - 2); + game.tiles[row][0] = game.tiles[row][LEVEL_SIZE_X - 1] = 1; } - memset(tiles[0], 1, LEVEL_SIZE_X); - memset(tiles[LEVEL_SIZE_Y - 1], 1, LEVEL_SIZE_X); + memset(game.tiles[0], 1, LEVEL_SIZE_X); + memset(game.tiles[LEVEL_SIZE_Y - 1], 1, LEVEL_SIZE_X); } diff --git a/src/level.h b/src/level.h index 03675cb..9a47462 100644 --- a/src/level.h +++ b/src/level.h @@ -28,16 +28,16 @@ enum { #define TILE_HEIGHT(tile) ((tile) & 7) #define TILE_TYPE(tile) ((tile) & TYPE_MASK) -typedef struct { +struct level { uint8_t compressed_tile_size; //Compressed size of tile data uint8_t num_tanks; //Number of tanks in the level -} level_t; +}; -typedef struct { +struct level_pack { char name[15]; uint8_t num_levels; uint8_t scores[5]; -} level_pack_t; +}; void decompress_tiles(const void *comp_tiles); diff --git a/src/main.cpp b/src/main.cpp index b85bc74..9800d47 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,10 +17,11 @@ #include "level.h" #include "graphics/graphics.h" -#include "globals.h" #include "util/profiler.h" -#include "graphics/gui.h" #include "game.h" +#include "gui/error.h" +#include "gui/kill_counts.h" +#include "gui/transition.h" int main() { dbg_printf("\n\n[TANKS] Program started.\n"); @@ -52,23 +53,23 @@ int main() { ERROR("TANKSLPK not found"); } - level_pack_t lvl_pack; - ti_Read(&lvl_pack, sizeof(level_pack_t), 1, appVar); + struct level_pack lvl_pack; + ti_Read(&lvl_pack, sizeof(struct level_pack), 1, appVar); dbg_printf("Found %u levels.\n", lvl_pack.num_levels); for(game.mission = 0; game.mission < lvl_pack.num_levels; game.mission++) { //Level loop const uint8_t *comp_tiles; //Compressed tile data - const serialized_tank_t *ser_tanks; + const struct serialized_tank *ser_tanks; dbg_printf("Loading level %u.\n", game.mission); //Read level from appvar - ti_Read(&game.level, sizeof(level_t), 1, appVar); + ti_Read(&game.level, sizeof(struct level), 1, appVar); comp_tiles = (const uint8_t *)ti_GetDataPtr(appVar); ti_Seek(game.level.compressed_tile_size, SEEK_CUR, appVar); - ser_tanks = (const serialized_tank_t *)ti_GetDataPtr(appVar); - ti_Seek(sizeof(serialized_tank_t) * game.level.num_tanks, SEEK_CUR, appVar); + ser_tanks = (const struct serialized_tank *)ti_GetDataPtr(appVar); + ti_Seek(sizeof(struct serialized_tank) * game.level.num_tanks, SEEK_CUR, appVar); if(game.level.num_tanks > MAX_NUM_TANKS) { dbg_printf("Too many tanks in level (%u)\n", game.level.num_tanks); @@ -82,7 +83,7 @@ int main() { break; case LOSE: { display_kill_counts(); - display_scores(); + // todo: display high scores goto exit; } case QUIT: @@ -92,7 +93,6 @@ int main() { default: { dbg_printf("Status: %u\n", status); ERROR("Unknown game status"); - goto exit; } } diff --git a/src/objects/mine.cpp b/src/objects/mine.cpp index ba2a99a..ef4022d 100644 --- a/src/objects/mine.cpp +++ b/src/objects/mine.cpp @@ -1,19 +1,18 @@ #include "mine.h" -#include - -#include "../globals.h" -#include "../graphics/gui.h" #include "../graphics/graphics.h" #include "../graphics/dynamic_sprites.h" #include "../util/profiler.h" #include "../graphics/partial_redraw.h" #include "mine_detector.h" #include "../graphics/tiles.h" +#include "../game.h" + +#include -Mine::Mine(Tank *tank) { - width = MINE_SIZE; - height = MINE_SIZE; +Mine::Mine(Tank *tank): + PhysicsBody(MINE_SIZE, MINE_SIZE), + countdown(MINE_COUNTDOWN) { tile_collisions = false; respect_holes = false; @@ -23,8 +22,6 @@ Mine::Mine(Tank *tank) { parent = tank; new (std::nothrow) MineDetector(this); - - countdown = MINE_COUNTDOWN; } Mine::~Mine() { @@ -103,7 +100,7 @@ void Mine::detonate() { for(uint8_t k = COORD_TO_Y_TILE(center_y() - MINE_EXPLOSION_RADIUS); k <= COORD_TO_Y_TILE(center_y() + MINE_EXPLOSION_RADIUS); k++) { if(k < 0 || k >= LEVEL_SIZE_Y) continue; - if(TILE_TYPE(tiles[k][j]) == DESTRUCTIBLE) tiles[k][j] |= DESTROYED; + if(TILE_TYPE(game.tiles[k][j]) == DESTRUCTIBLE) game.tiles[k][j] |= DESTROYED; needs_redraw = true; } } @@ -126,18 +123,6 @@ void Mine::handle_collision(PhysicsBody *other) { other->collide(this); } -void Mine::collide(__attribute__((unused)) Tank *tank) { - // don't do anything -} - void Mine::collide(Shell *shell) { shell->collide(this); } - -void Mine::collide(__attribute__((unused)) Mine *mine) { - // don't do anything -} - -void Mine::collide(__attribute__((unused)) MineDetector *detector) { - // don't do anything -} diff --git a/src/objects/mine.h b/src/objects/mine.h index 2fc01c2..12e710e 100644 --- a/src/objects/mine.h +++ b/src/objects/mine.h @@ -46,10 +46,7 @@ class Mine: public PhysicsBody { void handle_explosion(); void handle_collision(PhysicsBody *other); - void collide(Tank *tank); void collide(Shell *shell); - void collide(Mine *mine); - void collide(MineDetector *detector); }; #endif //TANKS_MINE_H diff --git a/src/objects/mine_detector.cpp b/src/objects/mine_detector.cpp index bc1a8fd..5155313 100644 --- a/src/objects/mine_detector.cpp +++ b/src/objects/mine_detector.cpp @@ -3,11 +3,9 @@ #include "mine.h" #include "tank.h" -MineDetector::MineDetector(Mine *mine) { - primed = false; - - width = MINE_EXPLOSION_RADIUS * 2; - height = MINE_EXPLOSION_RADIUS * 2; +MineDetector::MineDetector(Mine *mine): + PhysicsBody(MINE_EXPLOSION_RADIUS * 2, MINE_EXPLOSION_RADIUS * 2), + primed(false) { tile_collisions = false; position_x = mine->center_x() - MINE_EXPLOSION_RADIUS; @@ -43,15 +41,3 @@ void MineDetector::collide(__attribute__((unused)) Tank *tank) { active = false; } } - -void MineDetector::collide(__attribute__((unused)) Shell *shell) { - // don't do anything -} - -void MineDetector::collide(__attribute__((unused)) Mine *mine) { - // don't do anything -} - -void MineDetector::collide(__attribute__((unused)) MineDetector *detector) { - // don't do anything -} diff --git a/src/objects/mine_detector.h b/src/objects/mine_detector.h index e76d719..e46721f 100644 --- a/src/objects/mine_detector.h +++ b/src/objects/mine_detector.h @@ -18,9 +18,6 @@ class MineDetector: public PhysicsBody { void handle_collision(PhysicsBody *other); void collide(Tank *tank); - void collide(Shell *shell); - void collide(Mine *mine); - void collide(MineDetector *detector); }; #endif //TANKS_MINE_DETECTOR_H diff --git a/src/objects/physicsbody.cpp b/src/objects/physicsbody.cpp index 2fcbddc..c776805 100644 --- a/src/objects/physicsbody.cpp +++ b/src/objects/physicsbody.cpp @@ -15,12 +15,9 @@ uint PhysicsBody::center_y() const { return position_y + height / 2; } -PhysicsBody::PhysicsBody() { - active = true; - parent = nullptr; - velocity_x = 0; - velocity_y = 0; - +PhysicsBody::PhysicsBody(uint width, uint height): + width(width), + height(height) { // todo: compiler bug triggers if this line is the only thing in here objects.push_back(this); @@ -28,9 +25,7 @@ PhysicsBody::PhysicsBody() { dummy = this; } -PhysicsBody::~PhysicsBody() { - -} +PhysicsBody::~PhysicsBody() = default; void PhysicsBody::sort() { // Wikipedia Insertion Sort @@ -45,11 +40,18 @@ void PhysicsBody::sort() { } } +void PhysicsBody::remove_all() { + for(auto & obj : PhysicsBody::objects) { + obj->active = false; + } + PhysicsBody::remove_inactive(); +} + void PhysicsBody::remove_inactive() { for(auto & obj : objects) { if(!obj->parent->active) obj->parent = nullptr; } - for(auto *it = objects.begin(); it < objects.end();) { + for(auto it = objects.begin(); it != objects.end();) { if(!(*it)->active) { delete *it; it = objects.erase(it); @@ -129,8 +131,8 @@ bool PhysicsBody::center_distance_less_than(PhysicsBody *other, uint dis) const } // todo: optimize -bool PhysicsBody::collides_line(line_seg_t *ls) const { - line_seg_t border; +bool PhysicsBody::collides_line(struct line_seg *ls) const { + struct line_seg border; //top border.x1 = position_x; border.x2 = position_x + width; @@ -181,3 +183,8 @@ void PhysicsBody::handle_tile_collision(__attribute__((unused)) direction_t dir) void PhysicsBody::handle_explosion() { // Do nothing, by default } + +void PhysicsBody::collide([[maybe_unused]] Tank *tank) {} +void PhysicsBody::collide([[maybe_unused]] Shell *shell) {} +void PhysicsBody::collide([[maybe_unused]] Mine *mine) {} +void PhysicsBody::collide([[maybe_unused]] MineDetector *detector) {} diff --git a/src/objects/physicsbody.h b/src/objects/physicsbody.h index 3976402..aff22b9 100644 --- a/src/objects/physicsbody.h +++ b/src/objects/physicsbody.h @@ -12,17 +12,17 @@ class PhysicsBody { public: - PhysicsBody(); + PhysicsBody(uint width, uint height); virtual ~PhysicsBody(); int position_x; int position_y; - int velocity_x; - int velocity_y; - uint width; - uint height; - PhysicsBody *parent; - bool active; + int velocity_x{0}; + int velocity_y{0}; + const uint width; + const uint height; + PhysicsBody *parent{nullptr}; + bool active{true}; bool tile_collisions; // Whether or not to collide with holes @@ -38,11 +38,12 @@ class PhysicsBody { bool is_point_inside(int x, int y) const; direction_t process_tile_collision(); bool center_distance_less_than(PhysicsBody *other, uint dis) const; - bool collides_line(line_seg_t *seg) const; + bool collides_line(struct line_seg *seg) const; void tick(); static void sort(); + static void remove_all(); static void remove_inactive(); virtual void process() = 0; @@ -53,10 +54,10 @@ class PhysicsBody { // Polymorphic ping-pong (aka "visitor pattern," apparently) virtual void handle_collision(PhysicsBody *other) = 0; - virtual void collide(Tank *tank) = 0; - virtual void collide(Shell *shell) = 0; - virtual void collide(Mine *mine) = 0; - virtual void collide(MineDetector *detector) = 0; + virtual void collide([[maybe_unused]] Tank *tank); + virtual void collide([[maybe_unused]] Shell *shell); + virtual void collide([[maybe_unused]] Mine *mine); + virtual void collide([[maybe_unused]] MineDetector *detector); }; #endif //TANKS_PHYSICSBODY_H diff --git a/src/objects/shell.cpp b/src/objects/shell.cpp index 105ad27..c4d44dd 100644 --- a/src/objects/shell.cpp +++ b/src/objects/shell.cpp @@ -6,14 +6,12 @@ #include "../graphics/partial_redraw.h" #include "../graphics/tiles.h" -Shell::Shell(Tank *tank) { - width = SHELL_SIZE; - height = SHELL_SIZE; +Shell::Shell(Tank *tank): + PhysicsBody(SHELL_SIZE, SHELL_SIZE), + bounces(tank->max_bounces()) { tile_collisions = true; respect_holes = false; - bounces = Tank::max_bounces[tank->type]; - int vector_x = fast_cos(tank->barrel_rot); int vector_y = fast_sin(tank->barrel_rot); @@ -114,10 +112,6 @@ void Shell::handle_tile_collision(direction_t dir) { } } -void Shell::collide(__attribute__((unused)) MineDetector *detector) { - // don't do anything -} - void Shell::kill() { active = false; } diff --git a/src/objects/shell.h b/src/objects/shell.h index 4870de8..88e9b43 100644 --- a/src/objects/shell.h +++ b/src/objects/shell.h @@ -49,7 +49,6 @@ class Shell: public PhysicsBody { void collide(Tank *tank); void collide(Shell *shell); void collide(Mine *mine); - void collide(MineDetector *detector); void handle_tile_collision(direction_t dir); }; diff --git a/src/objects/tank.cpp b/src/objects/tank.cpp index 53e373a..377425a 100644 --- a/src/objects/tank.cpp +++ b/src/objects/tank.cpp @@ -6,7 +6,6 @@ #include #include "../ai/ai.h" -#include "../globals.h" #include "../util/profiler.h" #include "../graphics/dynamic_sprites.h" #include "../graphics/graphics.h" @@ -14,40 +13,34 @@ #include "mine_detector.h" #include "../graphics/tiles.h" #include "../graphics/tank_sprite.h" +#include "../game.h" + +const struct Tank::type_data Tank::types[NUM_TANK_TYPES] = { + {5, 2, 1, (uint8_t)TANK_SPEED_HIGH}, + {1, 0, 1, (uint8_t)0}, + {1, 0, 1, (uint8_t)TANK_SPEED_SLOW}, + {1, 0, 0, (uint8_t)TANK_SPEED_SLOW}, + {1, 4, 1, (uint8_t)TANK_SPEED_HIGH}, + {3, 0, 1, (uint8_t)TANK_SPEED_NORMAL}, + {2, 0, 2, (uint8_t)0}, + {5, 2, 1, (uint8_t)TANK_SPEED_HIGH}, + {5, 2, 1, (uint8_t)TANK_SPEED_NORMAL}, + {2, 2, 0, (uint8_t)TANK_SPEED_BLACK}, +}; + +Tank::Tank(const struct serialized_tank *ser_tank, uint8_t id): + PhysicsBody(TANK_SIZE, TANK_SIZE), + type(ser_tank->type), + id(id), + // add 1 because the level system uses coordinates from the first non-border block + start_x(ser_tank->start_x + 1), + start_y(ser_tank->start_y + 1) { -const uint8_t Tank::max_shells[] = {5, 1, 1, 1, 1, 3, 2, 5, 5, 2}; -const uint8_t Tank::max_mines[] = {2, 0, 0, 0, 4, 0, 0, 2, 2, 2}; -const uint8_t Tank::max_bounces[] = {1, 1, 1, 0, 1, 1, 2, 1, 1, 0}; -const uint8_t Tank::velocities[] = {(uint8_t)TANK_SPEED_HIGH, - (uint8_t)0, - (uint8_t)TANK_SPEED_SLOW, - (uint8_t)TANK_SPEED_SLOW, - (uint8_t)TANK_SPEED_HIGH, - (uint8_t)TANK_SPEED_NORMAL, - (uint8_t)0, - (uint8_t)TANK_SPEED_HIGH, - (uint8_t)TANK_SPEED_NORMAL, - (uint8_t)TANK_SPEED_BLACK}; - -Tank::Tank(const serialized_tank_t *ser_tank, uint8_t id) { - width = TANK_SIZE; - height = TANK_SIZE; tile_collisions = true; respect_holes = true; - type = ser_tank->type; - this->id = id; - // add 1 because the level system uses coordinates from the first non-border block - start_x = ser_tank->start_x + 1; - start_y = ser_tank->start_y + 1; position_x = TILE_TO_X_COORD(start_x); position_y = TILE_TO_Y_COORD(start_y); - barrel_rot = 0; - tread_rot = 0; - shot_cooldown = 0; - mine_cooldown = 0; - tread_distance = TREAD_DISTANCE; - draw_treads = false; if(id == 0) { game.player = this; @@ -155,7 +148,7 @@ void Tank::fire_shell() { } bool Tank::can_shoot() const { - return !shot_cooldown && num_shells < max_shells[type]; + return !shot_cooldown && num_shells < max_shells(); } void Tank::lay_mine() { @@ -171,7 +164,7 @@ void Tank::lay_mine() { } bool Tank::can_lay_mine() const { - return !mine_cooldown && num_mines < max_mines[type]; + return !mine_cooldown && num_mines < max_mines(); } void Tank::set_velocity(int velocity) { @@ -242,10 +235,6 @@ void Tank::collide(Shell *shell) { shell->collide(this); } -void Tank::collide(__attribute__((unused)) Mine *mine) { - // don't do anything -} - void Tank::collide(MineDetector *detector) { detector->collide(this); } diff --git a/src/objects/tank.h b/src/objects/tank.h index d387e4f..2640779 100644 --- a/src/objects/tank.h +++ b/src/objects/tank.h @@ -46,38 +46,36 @@ enum { NUM_TANK_TYPES = 10 }; -typedef struct { +struct serialized_tank { //A tank as stored in the level file tank_type_t type; uint8_t start_x; //Tile the tank starts on uint8_t start_y; -} serialized_tank_t; +}; class Tank: public PhysicsBody { public: - Tank(const serialized_tank_t *ser_tank, uint8_t id); + Tank(const struct serialized_tank *ser_tank, uint8_t id); ~Tank(); tank_type_t type; uint8_t id; uint8_t start_x; uint8_t start_y; - angle_t tread_rot; //Rotation of tank treads. Determines the direction of the tank. - angle_t barrel_rot; //Rotation of the barrel. Determines the direction shots are fired in - uint8_t num_shells = 0; - uint8_t num_mines = 0; - uint8_t shot_cooldown; - uint8_t mine_cooldown; - uint tread_distance; - ai_move_state_t ai_move; - ai_fire_state_t ai_fire; + angle_t tread_rot{0}; //Rotation of tank treads. Determines the direction of the tank. + angle_t barrel_rot{0}; //Rotation of the barrel. Determines the direction shots are fired in + uint8_t num_shells{0}; + uint8_t num_mines{0}; - // Used to (somewhat lazily) draw stuff on the background across two frame buffers - bool draw_treads; - gfx_sprite_t *tread_sprite; - gfx_region_t tread_pos; + // todo: make private + union ai_move_state ai_move{}; + union ai_fire_state ai_fire{}; + + inline uint8_t max_shells() const { return types[type].max_shells; } + inline uint8_t max_bounces() const { return types[type].max_bounces; } + inline uint8_t max_mines() const { return types[type].max_mines; } + inline uint8_t velocity() const { return types[type].velocity; } - // Kill this physics body, then destroy it void kill(); void process(); void render(uint8_t layer); @@ -87,21 +85,30 @@ class Tank: public PhysicsBody { bool can_lay_mine() const; void set_velocity(int velocity); - //Number of shots each type of tank can have on-screen at any one time - static const uint8_t max_shells[]; - //Number of times a shell from a certain tank type can bounce off a wall - static const uint8_t max_bounces[]; - //Number of mines each type of tank can have on-screen at any one time - static const uint8_t max_mines[]; - static const uint8_t velocities[]; - void handle_explosion(); void handle_collision(PhysicsBody *other); void collide(Tank *tank); void collide(Shell *shell); - void collide(Mine *mine); void collide(MineDetector *detector); + +private: + struct type_data { + uint8_t max_shells; + uint8_t max_bounces; + uint8_t max_mines; + uint8_t velocity; + }; + static const type_data types[NUM_TANK_TYPES]; + + uint8_t shot_cooldown{0}; + uint8_t mine_cooldown{0}; + uint tread_distance{TREAD_DISTANCE}; + + // Used to (somewhat lazily) draw stuff on the background across two frame buffers + bool draw_treads{false}; + gfx_sprite_t *tread_sprite{nullptr}; + gfx_region_t tread_pos{}; }; #endif //TANKS_TANK_H diff --git a/src/physics/collision.cpp b/src/physics/collision.cpp index ffc03ab..6228d9a 100644 --- a/src/physics/collision.cpp +++ b/src/physics/collision.cpp @@ -3,12 +3,12 @@ #include #include "../level.h" -#include "../globals.h" #include "../util/profiler.h" +#include "../game.h" //Check if a point is colliding with a tile bool check_tile_collision(uint x, uint y, bool respect_holes) { - tile_t tile = tiles[COORD_TO_Y_TILE(y)][COORD_TO_X_TILE(x)]; + tile_t tile = game.tiles[COORD_TO_Y_TILE(y)][COORD_TO_X_TILE(x)]; bool colliding = (TILE_HEIGHT(tile) && TILE_TYPE(tile) != DESTROYED) || (respect_holes && TILE_TYPE(tile) == HOLE); return colliding; } @@ -17,7 +17,7 @@ bool check_tile_collision(uint x, uint y, bool respect_holes) { //credit: https://theshoemaker.de/2016/02/ray-casting-in-2d-grids/ //though I've rewritten a lot of it //returns 0 if hits across x axis, non-zero if y axis -uint8_t raycast(uint startX, uint startY, angle_t angle, line_seg_t *result) { +uint8_t raycast(uint startX, uint startY, angle_t angle, struct line_seg *result) { int dirX = fast_sec(angle); int dirY = fast_csc(angle); @@ -27,7 +27,7 @@ uint8_t raycast(uint startX, uint startY, angle_t angle, line_seg_t *result) { int8_t tileX = COORD_TO_X_TILE(startX); int8_t tileY = COORD_TO_Y_TILE(startY); - tile_t *tile_ptr = &tiles[tileY][tileX]; + tile_t *tile_ptr = &game.tiles[tileY][tileX]; int t = 0; int dtX = (TILE_TO_X_COORD(tileX + (dirX >= 0)) - startX) * dirX; @@ -83,7 +83,7 @@ uint8_t raycast(uint startX, uint startY, angle_t angle, line_seg_t *result) { } //todo: optimize? -bool seg_collides_seg_(line_seg_t *l1, line_seg_t *l2, int *intercept_x, int *intercept_y) { +bool seg_collides_seg_(struct line_seg *l1, struct line_seg *l2, int *intercept_x, int *intercept_y) { int p0_x = l1->x1, p1_x = l1->x2, p2_x = l2->x1, p3_x = l2->x2; int p0_y = l1->y1, p1_y = l1->y2, p2_y = l2->y1, p3_y = l2->y2; int s1_x = p1_x - p0_x; @@ -107,30 +107,30 @@ bool seg_collides_seg_(line_seg_t *l1, line_seg_t *l2, int *intercept_x, int *in return false; // No collision } -bool seg_collides_seg(line_seg_t *l1, line_seg_t *l2) { +bool seg_collides_seg(struct line_seg *l1, struct line_seg *l2) { return seg_collides_seg(l1, l2, nullptr, nullptr); } -bool seg_collides_seg(line_seg_t *l1, line_seg_t *l2, int *intercept_x, int *intercept_y) { +bool seg_collides_seg(struct line_seg *l1, struct line_seg *l2, int *intercept_x, int *intercept_y) { profiler_add(seg_collision); bool x = seg_collides_seg_(l1, l2, intercept_x, intercept_y); profiler_end(seg_collision); return x; } -int y_intercept(line_seg_t *line, int x_pos) { +int y_intercept(struct line_seg *line, int x_pos) { return line->y1 + (line->y2 - line->y1) * (x_pos - line->x1) / (line->x2 - line->x1); } -int x_intercept(line_seg_t *line, int y_pos) { +int x_intercept(struct line_seg *line, int y_pos) { return line->x1 + (line->x2 - line->x1) * (y_pos - line->y1) / (line->y2 - line->y1); } void process_collisions() { - for(auto *it = PhysicsBody::objects.begin(); it < PhysicsBody::objects.end();) { + for(auto it = PhysicsBody::objects.begin(); it != PhysicsBody::objects.end();) { PhysicsBody *old_ptr = *it; int bottom_y = (**it).position_y + (int)(**it).height; - for(auto *other = it + 1; other < PhysicsBody::objects.end() && (**other).position_y <= bottom_y;) { + for(auto other = it + 1; other != PhysicsBody::objects.end() && (**other).position_y <= bottom_y;) { PhysicsBody *old_other_ptr = *other; if((**other).position_x < (**it).position_x + (int)(**it).width && (**it).position_x < (**other).position_x + (int)(**other).width) { diff --git a/src/physics/collision.h b/src/physics/collision.h index 24e7d5b..8eb361f 100644 --- a/src/physics/collision.h +++ b/src/physics/collision.h @@ -13,24 +13,24 @@ #include "../util/trig.h" -typedef struct { +struct line_seg { int x1; int y1; int x2; int y2; -} line_seg_t; +}; void process_collisions(); -uint8_t raycast(uint startX, uint startY, angle_t angle, line_seg_t *result); +uint8_t raycast(uint startX, uint startY, angle_t angle, struct line_seg *result); bool check_tile_collision(uint x, uint y, bool respect_holes); -bool seg_collides_seg(line_seg_t *l1, line_seg_t *l2); -bool seg_collides_seg(line_seg_t *l1, line_seg_t *l2, int *intercept_x, int *intercept_y); +bool seg_collides_seg(struct line_seg *l1, struct line_seg *l2); +bool seg_collides_seg(struct line_seg *l1, struct line_seg *l2, int *intercept_x, int *intercept_y); -int y_intercept(line_seg_t *line, int x_pos); +int y_intercept(struct line_seg *line, int x_pos); -int x_intercept(line_seg_t *line, int y_pos); +int x_intercept(struct line_seg *line, int y_pos); #endif /* H_COLLISION */ diff --git a/src/util/cpp_internals.cpp b/src/util/cpp_internals.cpp index 96ff960..49d9046 100644 --- a/src/util/cpp_internals.cpp +++ b/src/util/cpp_internals.cpp @@ -1,5 +1,5 @@ #include -#include "../graphics/gui.h" +#include "../gui/error.h" #ifdef __EZ80__ diff --git a/src/util/profiler.c b/src/util/profiler.c index 97ea28c..64e8847 100644 --- a/src/util/profiler.c +++ b/src/util/profiler.c @@ -5,9 +5,9 @@ #if USE_PROFILER -profiler_set_t current_profiler; -profiler_set_t profiler_sum; -profiler_set_t profiler_frames[256]; +union profiler_set current_profiler; +union profiler_set profiler_sum; +union profiler_set profiler_frames[256]; uint8_t profiler_frame_index; diff --git a/src/util/profiler.h b/src/util/profiler.h index fc89c95..71a10b3 100644 --- a/src/util/profiler.h +++ b/src/util/profiler.h @@ -14,7 +14,7 @@ extern "C" { #if USE_PROFILER -typedef union { +union profiler_set { struct { unsigned int total; unsigned int graphics; @@ -51,9 +51,9 @@ typedef union { unsigned int temp3; }; unsigned int array[0]; -} profiler_set_t; +}; -#define NUM_PROFILER_FIELDS (sizeof(profiler_set_t) / sizeof(unsigned int)) +#define NUM_PROFILER_FIELDS (sizeof(union profiler_set) / sizeof(unsigned int)) #define profiler_start(name) current_profiler.name = (unsigned int)timer_2_Counter #define profiler_add(name) (current_profiler.name = (unsigned int)timer_2_Counter - current_profiler.name) @@ -65,7 +65,7 @@ void profiler_tick(void); void profiler_print(void); -extern profiler_set_t current_profiler; +extern union profiler_set current_profiler; #else