diff --git a/src/entity.h b/src/entity.h index ec75469..d6eeaf3 100644 --- a/src/entity.h +++ b/src/entity.h @@ -1,16 +1,20 @@ #pragma once #include "main.h" +#include "timers.h" enum EntityType { ENTITY_TYPE_NIL = 0, ENTITY_TYPE_PLAYER, - ENTITY_TYPE_WALL + ENTITY_TYPE_DAMAGE, + ENTITY_TYPE_WALL, }; struct EntityFlags { bool has_friction : 1; bool has_max_speed : 1; + + bool remove_offscsreen : 1; }; struct Entity { @@ -25,5 +29,9 @@ struct Entity { // TODO: Remove this, because this is just a temporary value for resolving collisions Vector2 step; + uint32_t health; + + TimerId invincibility_timer; + Rectangle collision_rect; }; diff --git a/src/game.c b/src/game.c index ac41f68..ab01f91 100644 --- a/src/game.c +++ b/src/game.c @@ -1,17 +1,26 @@ #include "game.h" #include "entity_id_list.h" #include "float_list.h" +#include "raylib.h" +#include "rect.h" +#include +#include + +#define MAX_PLAYER_HEALTH 5 +#define PLAYER_INVINCIBILITY 1.5 void game_init(struct Game *game) { *game = (struct Game){ - .canvas_size = { 800, 600 } + .canvas_size = { 800, 600 }, + .show_damage_rects = true }; { struct Entity *player = entity_list_add(&game->entities); player->type = ENTITY_TYPE_PLAYER; player->position = Vector2Scale(game->canvas_size, 0.5); + player->health = MAX_PLAYER_HEALTH; player->collision_rect = (Rectangle){ .x = -10, .y = -10, @@ -20,10 +29,10 @@ void game_init(struct Game *game) }; } - float wall_thickness = 10; + float wall_thickness = 50; // Canvas bounds - { + if (false) { game_add_wall(game, (Rectangle){ .x = -wall_thickness, .y = -wall_thickness, @@ -52,74 +61,15 @@ void game_init(struct Game *game) .height = game->canvas_size.y + 2*wall_thickness }); } - - for (int i = 0; i < 5; i++) { - game_add_wall(game, (Rectangle){ - .x = 10 + 45 * i, - .y = 10, - .width = 40, - .height = 40 - }); - } - - game_add_wall(game, (Rectangle){ - .x = 300, - .y = 10, - .width = 40, - .height = 40 - }); - - { - EntityId wall_id = game_add_wall(game, (Rectangle){ - .x = 10, - .y = 10, - .width = 40, - .height = 200 - }); - struct Entity *wall = entity_list_get(&game->entities, wall_id); - wall->velocity.x = 100; - } - - { - EntityId wall_id = game_add_wall(game, (Rectangle){ - .x = 700, - .y = 10, - .width = 40, - .height = 200 - }); - struct Entity *wall = entity_list_get(&game->entities, wall_id); - wall->velocity.x = -100; - } - - { - EntityId wall_id = game_add_wall(game, (Rectangle){ - .x = 10, - .y = 00, - .width = 200, - .height = 40 - }); - struct Entity *wall = entity_list_get(&game->entities, wall_id); - wall->velocity.y = 100; - } - - { - EntityId wall_id = game_add_wall(game, (Rectangle){ - .x = 10, - .y = 500, - .width = 200, - .height = 40 - }); - struct Entity *wall = entity_list_get(&game->entities, wall_id); - wall->velocity.y = -100; - } } void game_free(struct Game *game) { entity_list_free(&game->entities); + timers_free(&game->timers); } -EntityId game_add_wall(struct Game *game, Rectangle rect) +struct Entity *game_add_wall(struct Game *game, Rectangle rect) { struct Entity *wall = entity_list_add(&game->entities); @@ -133,32 +83,7 @@ EntityId game_add_wall(struct Game *game, Rectangle rect) .height = rect.height }; - return entity_list_get_id(&game->entities, wall); -} - -static float game_aabb(Rectangle b1, Rectangle b2) -{ - return CheckCollisionRecs(b1, b2); -} - -static float rect_get_left(Rectangle rect) -{ - return rect.x; -} - -static float rect_get_right(Rectangle rect) -{ - return rect.x + rect.width; -} - -static float rect_get_top(Rectangle rect) -{ - return rect.y; -} - -static float rect_get_bottom(Rectangle rect) -{ - return rect.y + rect.height; + return wall; } struct CollisionResult { @@ -228,13 +153,6 @@ static bool game_swept_aabb(Rectangle b1, Vector2 v1, Rectangle b2, struct Colli return true; } -static Rectangle rect_add_position(Rectangle rect, Vector2 pos) -{ - rect.x += pos.x; - rect.y += pos.y; - return rect; -} - static Rectangle rect_get_step_bounds(Rectangle rect, Vector2 step) { if (step.x >= 0) { @@ -254,14 +172,6 @@ static Rectangle rect_get_step_bounds(Rectangle rect, Vector2 step) return rect; } -static Vector2 rect_get_center(Rectangle rect) -{ - return (Vector2){ - .x = rect.x + rect.width / 2, - .y = rect.y + rect.height / 2, - }; -} - static void game_list_nearest_walls(struct Game *game, struct EntityIdList *walls, Rectangle bounds, struct EntityIdList *result) { struct FloatList distances = { 0 }; @@ -273,7 +183,7 @@ static void game_list_nearest_walls(struct Game *game, struct EntityIdList *wall ASSERT(wall != NULL); Rectangle wall_collider = rect_add_position(wall->collision_rect, wall->position); - if (game_aabb(wall_collider, bounds)) { + if (rect_is_overlap(wall_collider, bounds)) { float distance_sqr = Vector2DistanceSqr( rect_get_center(wall_collider), rect_get_center(bounds) @@ -350,9 +260,172 @@ static void game_collide_against_walls(struct Game *game, struct EntityIdList *w TracyCZoneEnd(game_collide_against_walls) } -void game_tick(struct Game *game) +static void game_collide_push_players(struct Game *game, struct Entity *wall, struct EntityIdList *players) { - TracyCZoneN(game_tick, "game_tick", true); + for (size_t i = 0; i < players->len; i++) { + struct Entity *player = entity_list_get(&game->entities, players->items[i]); + ASSERT(player != NULL); + + Rectangle wall_collider = rect_add_position(wall->collision_rect, wall->position); + Rectangle player_collider = rect_add_position(player->collision_rect, player->position); + + if (!rect_is_overlap(player_collider, rect_get_step_bounds(wall_collider, wall->step))) { + continue; + } + + struct CollisionResult collision = { 0 }; + if (game_swept_aabb(wall_collider, wall->step, player_collider, &collision)) { + if (collision.normal.x != 0 && signf(collision.normal.x) == signf(player->step.x)) { + player->step.x = 0; + } + if (collision.normal.y != 0 && signf(collision.normal.y) == signf(player->step.y)) { + player->step.y = 0; + } + + player->step.x += wall->step.x * (1 - collision.time); + player->step.y += wall->step.y * (1 - collision.time); + } + } +} + +static Vector2 game_get_nudge_to_outside(Rectangle wall_collider, Rectangle player_collider) +{ + Vector2 player_center = rect_get_center(player_collider); + Vector2 wall_center = rect_get_center(wall_collider); + + float x_overlap = 0; + if (player_center.x < wall_center.x) { + x_overlap = MIN(rect_get_left(wall_collider) - rect_get_right(player_collider), 0); + } else { + x_overlap = MAX(rect_get_right(wall_collider) - rect_get_left(player_collider), 0); + } + + float y_overlap = 0; + if (player_center.y < wall_center.y) { + y_overlap = MIN(rect_get_top(wall_collider) - rect_get_bottom(player_collider), 0); + } else { + y_overlap = MAX(rect_get_bottom(wall_collider) - rect_get_top(player_collider), 0); + } + + if (fabs(x_overlap) < fabs(y_overlap)) { + return (Vector2){ + .x = x_overlap + }; + } else { + return (Vector2){ + .y = y_overlap + }; + } +} + +static Vector2 game_get_nudge_to_inside(Rectangle container, Rectangle player_collider) +{ + Vector2 player_center = rect_get_center(player_collider); + Vector2 cotainer_center = rect_get_center(container); + + float x_overlap = 0; + if (player_center.x < cotainer_center.x) { + x_overlap = MAX(rect_get_left(container) - rect_get_left(player_collider), 0); + } else { + x_overlap = MIN(rect_get_right(container) - rect_get_right(player_collider), 0); + } + + float y_overlap = 0; + if (player_center.y < cotainer_center.y) { + y_overlap = MAX(rect_get_top(container) - rect_get_top(player_collider), 0); + } else { + y_overlap = MIN(rect_get_bottom(container) - rect_get_bottom(player_collider), 0); + } + + return (Vector2){ + .x = x_overlap, + .y = y_overlap + }; +} + +static bool game_collide_nudge_out_of_walls( + struct Game *game, + Rectangle player_rect, + Vector2 *player_step, + struct EntityIdList *walls, + float max_nudge_amount +) +{ + bool max_nudge_reached = false; + + for (size_t i = 0; i < walls->len; i++) { + struct Entity *wall = entity_list_get(&game->entities, walls->items[i]); + ASSERT(wall != NULL); + + Rectangle player_collider = rect_add_position(player_rect, *player_step); + Rectangle wall_collider = rect_add_position(wall->collision_rect, wall->position); + + if (rect_is_overlap(player_collider, wall_collider)) { + Vector2 nudge = game_get_nudge_to_outside(wall_collider, player_collider); + if (Vector2Length(nudge) > max_nudge_amount) { + max_nudge_reached = true; + } + *player_step = Vector2Add(*player_step, Vector2ClampValue(nudge, 0, max_nudge_amount)); + } + } + + return max_nudge_reached; +} + +static void game_start_attack(struct Game *game, struct Attack attack) +{ + timers_stop_by_attack(&game->timers); + game->attack = attack; +} + +void game_attack_sweep_wave_tick(struct Game *game, struct AttackSweepWave *attack) +{ + float thickness = 100; + float speed = 300; + int wall_count = 20; + float time_between_walls = 0.2; + + if (timers_finished(&game->timers, attack->timer) && !attack->finished) { + float width = game->canvas_size.x / wall_count; + + struct Entity *damage = entity_list_add(&game->entities); + damage->type = ENTITY_TYPE_DAMAGE; + damage->flags.remove_offscsreen = true; + damage->position.x = attack->wall_index * width; + damage->collision_rect = (Rectangle){ + .width = width, + .height = thickness + }; + + if (attack->wall_index % 2 == 0) { + damage->position.y = -thickness; + damage->velocity.y = speed; + } else { + damage->position.y = game->canvas_size.y; + damage->velocity.y = -speed; + } + + attack->wall_index++; + if (attack->wall_index < wall_count) { + attack->timer = timers_start(&game->timers, time_between_walls); + timers_set_attack(&game->timers, attack->timer); + } else { + attack->finished = true; + } + } +} + +void game_draw_rect_outlined(Rectangle rect, Color color) +{ + DrawRectangleRec(rect, ColorAlpha(color, 0.5)); + DrawRectangleLinesEx(rect, 2, ColorAlpha(color, 0.5)); +} + +void game_get_input(struct Game *game, struct WindowScaler *scaler, struct Input *result) +{ + Vector2 mouse = GetMousePosition(); + mouse = Vector2Subtract(mouse, scaler->offset); + mouse = Vector2Scale(mouse, 1/scaler->scale); Vector2 dir = { 0 }; if (IsKeyDown(KEY_W)) { @@ -369,12 +442,42 @@ void game_tick(struct Game *game) } dir = Vector2Normalize(dir); + *result = (struct Input){ + .dt = GetFrameTime(), + .mouse = mouse, + .move_dir = dir, + .focus = IsKeyDown(KEY_SPACE), + .restart = IsKeyPressed(KEY_R) + }; +} + +void game_tick(struct Game *game) +{ + TracyCZoneN(game_tick, "game_tick", true); + + if (game->input.restart) { + game_free(game); + game_init(game); + } + float dt = game->input.dt; + game->timers.now += dt; + + if (game->attack.type == ATTACK_TYPE_NIL) { + game_start_attack(game, (struct Attack){ + .type = ATTACK_TYPE_SWEEP_WAVE, + }); + } + + if (game->attack.type == ATTACK_TYPE_SWEEP_WAVE) { + game_attack_sweep_wave_tick(game, &game->attack.sweep_wave); + } + struct Entity *entity = NULL; entity_list_foreach(&game->entities, entity) { if (entity->type == ENTITY_TYPE_PLAYER) { - entity->velocity = Vector2Scale(dir, 200); + entity->velocity = Vector2Scale(game->input.move_dir, 200); } entity->velocity = Vector2Add(entity->velocity, Vector2Scale(entity->accelaration, dt)); @@ -388,6 +491,26 @@ void game_tick(struct Game *game) entity->step = Vector2Scale(entity->velocity, dt); } + entity_list_foreach(&game->entities, entity) { + if (entity->type != ENTITY_TYPE_WALL) { + continue; + } + if (!entity->flags.remove_offscsreen) { + continue; + } + + Rectangle bounding_box = rect_add_position(entity->collision_rect, entity->position); + Rectangle onscreen_area = { + .x = -0.5*game->canvas_size.x, + .y = -0.5*game->canvas_size.y, + .width = 2*game->canvas_size.x, + .height = 2*game->canvas_size.y, + }; + if (!rect_is_overlap(onscreen_area, bounding_box)) { + entity_list_remove(&game->entities, entity); + } + } + struct EntityIdList walls = { 0 }; struct EntityIdList players = { 0 }; @@ -406,36 +529,18 @@ void game_tick(struct Game *game) struct Entity *wall = entity_list_get(&game->entities, walls.items[i]); ASSERT(wall != NULL); - for (size_t j = 0; j < players.len; j++) { - struct Entity *player = entity_list_get(&game->entities, players.items[j]); - ASSERT(player != NULL); - - Rectangle wall_collider = rect_add_position(wall->collision_rect, wall->position); - Rectangle player_collider = rect_add_position(player->collision_rect, player->position); - - if (!game_aabb(player_collider, rect_get_step_bounds(wall_collider, wall->step))) { - continue; - } - - struct CollisionResult collision = { 0 }; - if (game_swept_aabb(wall_collider, wall->step, player_collider, &collision)) { - if (collision.normal.x != 0 && signf(collision.normal.x) == signf(player->step.x)) { - player->step.x = 0; - } - if (collision.normal.y != 0 && signf(collision.normal.y) == signf(player->step.y)) { - player->step.y = 0; - } - - player->step.x += wall->step.x * (1 - collision.time); - player->step.y += wall->step.y * (1 - collision.time); - } - } + game_collide_push_players( + game, + wall, + &players + ); wall->position = Vector2Add(wall->position, wall->step); } for (size_t i = 0; i < players.len; i++) { - struct Entity *player = entity_list_get(&game->entities, players.items[i]); + EntityId player_id = players.items[i]; + struct Entity *player = entity_list_get(&game->entities, player_id); ASSERT(player != NULL); game_collide_against_walls( @@ -445,69 +550,98 @@ void game_tick(struct Game *game) &player->step ); - for (size_t j = 0; j < walls.len; j++) { - struct Entity *wall = entity_list_get(&game->entities, walls.items[j]); - ASSERT(wall != NULL); + bool apply_damage = false; + for (int j = 0; j < 2; j++) { + if (game_collide_nudge_out_of_walls( + game, + rect_add_position(player->collision_rect, player->position), + &player->step, + &walls, + 10 + )) { + apply_damage = true; + } + } - Rectangle player_collider = rect_add_position(player->collision_rect, Vector2Add(player->position, player->step)); - Rectangle wall_collider = rect_add_position(wall->collision_rect, wall->position); - - if (game_aabb(player_collider, wall_collider)) { - Vector2 player_center = rect_get_center(player_collider); - Vector2 wall_center = rect_get_center(wall_collider); - - float x_overlap = 0; - if (player_center.x < wall_center.x) { - x_overlap = MIN(rect_get_left(wall_collider) - rect_get_right(player_collider), 0); - } else { - x_overlap = MAX(rect_get_right(wall_collider) - rect_get_left(player_collider), 0); - } - - float y_overlap = 0; - if (player_center.y < wall_center.y) { - y_overlap = MIN(rect_get_top(wall_collider) - rect_get_bottom(player_collider), 0); - } else { - y_overlap = MAX(rect_get_bottom(wall_collider) - rect_get_top(player_collider), 0); - } - - float overlap_size = 0; - if (fabs(x_overlap) < fabs(y_overlap)) { - overlap_size = fabs(x_overlap); - player->step.x += x_overlap; - } else { - overlap_size = fabs(y_overlap); - player->step.y += y_overlap; - } - - if (overlap_size > 1) { - // TODO: apply damage to player - } + { + Rectangle player_rect = rect_add_position(player->collision_rect, Vector2Add(player->position, player->step)); + Rectangle canvas_rect = { + .width = game->canvas_size.x, + .height = game->canvas_size.y, + }; + if (!rect_is_inside_rect(canvas_rect, player_rect)) { + Vector2 nudge = game_get_nudge_to_inside(canvas_rect, player_rect); + player->step = Vector2Add(player->step, nudge); } } player->position = Vector2Add(player->position, player->step); - } - for (size_t i = 0; i < walls.len; i++) { - struct Entity *wall = entity_list_get(&game->entities, walls.items[i]); - ASSERT(wall != NULL); + Rectangle player_rect = rect_add_position(player->collision_rect, player->position); + entity_list_foreach(&game->entities, entity) { + if (entity->type == ENTITY_TYPE_DAMAGE) { + Rectangle damage_rect = rect_add_position(entity->collision_rect, entity->position); + if (rect_is_overlap(damage_rect, player_rect)) { + apply_damage = true; + } + } + } + if (apply_damage) { + if (timers_finished(&game->timers, player->invincibility_timer)) { + printf("apply_damage!\n"); + player->health -= 1; + + player->invincibility_timer = timers_start_with_entity(&game->timers, PLAYER_INVINCIBILITY, player_id); + } + } } entity_list_foreach(&game->entities, entity) { if (entity->type == ENTITY_TYPE_PLAYER) { Vector2 size = (Vector2){ 20, 20 }; - DrawRectangleV(Vector2Subtract(entity->position, Vector2Scale(size, 0.5)), size, RED); + Color color = GREEN; + float time_passed = timers_time_since_start(&game->timers, entity->invincibility_timer); + if (!timers_finished(&game->timers, entity->invincibility_timer)) { + if (fmod(time_passed, 0.2) < 0.1) { + color = WHITE; + } + } + + DrawRectangleV(Vector2Subtract(entity->position, Vector2Scale(size, 0.5)), size, color); DrawCircleV(entity->position, 4, BLACK); + + float healthbar_width = 50; + Rectangle healthbar = { + .x = entity->position.x - healthbar_width/2, + .y = entity->position.y + 15, + .width = healthbar_width, + .height = 10 + }; + DrawRectangleRec(healthbar, BLACK); + + float health_percent = (float)entity->health / MAX_PLAYER_HEALTH; + Rectangle healthbar_filled = rect_shrink_all(healthbar, 1); + healthbar_filled.width *= health_percent; + DrawRectangleRec(healthbar_filled, GREEN); } - if (entity->type == ENTITY_TYPE_WALL || entity->type == ENTITY_TYPE_PLAYER) { - Rectangle rect = entity->collision_rect; - rect.x += entity->position.x; - rect.y += entity->position.y; - Color color = BLUE; - DrawRectangleRec(rect, ColorAlpha(color, 0.5)); - DrawRectangleLinesEx(rect, 2, ColorAlpha(color, 0.5)); + if (game->show_damage_rects) { + if (entity->type == ENTITY_TYPE_DAMAGE) { + game_draw_rect_outlined( + rect_add_position(entity->collision_rect, entity->position), + RED + ); + } + } + + if (game->show_collider) { + if (entity->type == ENTITY_TYPE_WALL || entity->type == ENTITY_TYPE_PLAYER) { + game_draw_rect_outlined( + rect_add_position(entity->collision_rect, entity->position), + BLUE + ); + } } } diff --git a/src/game.h b/src/game.h index d360336..f8319cd 100644 --- a/src/game.h +++ b/src/game.h @@ -2,20 +2,55 @@ #include "main.h" #include "entity_list.h" +#include "timers.h" +#include "window_scaler.h" + +enum AttackType { + ATTACK_TYPE_NIL = 0, + ATTACK_TYPE_SWEEP_WAVE +}; + +struct AttackSweepWave { + bool finished; + TimerId timer; + size_t wall_index; +}; + +struct Attack { + enum AttackType type; + union { + struct AttackSweepWave sweep_wave; + }; +}; struct Input { float dt; Vector2 mouse; + Vector2 move_dir; + bool focus; + bool restart; }; struct Game { Vector2 canvas_size; struct EntityList entities; + struct Timers timers; struct Input input; + // TODO: Add support for multiple attack running at the same time + struct Attack attack; + + bool show_collider; + bool show_damage_rects; }; void game_init(struct Game *game); void game_free(struct Game *game); void game_tick(struct Game *game); -EntityId game_add_wall(struct Game *game, Rectangle rect); +struct Entity *game_add_wall(struct Game *game, Rectangle rect); + +void game_get_input( + struct Game *game, + struct WindowScaler *scaler, + struct Input *result +); diff --git a/src/game_debug.c b/src/game_debug.c index 9a5cbec..2deecca 100644 --- a/src/game_debug.c +++ b/src/game_debug.c @@ -34,7 +34,14 @@ void game_debug_show(struct Game *game) snprintf(label, sizeof(label), "FPS: %.1f (%.2fms)", 1.0f / game->input.dt, game->input.dt * 1000); igTextEx(label, NULL, ImGuiTextFlags_None); - igTextEx("Hello, World", NULL, ImGuiTextFlags_None); + snprintf(label, sizeof(label), "Entity count: %zu", game->entities.used_count); + igTextEx(label, NULL, ImGuiTextFlags_None); + + snprintf(label, sizeof(label), "Timer count: %zu", game->timers.len); + igTextEx(label, NULL, ImGuiTextFlags_None); + + igCheckbox("Show colliders", &game->show_collider); + igCheckbox("Show damage", &game->show_damage_rects); } igEnd(); diff --git a/src/main.c b/src/main.c index 5286a8d..f55406d 100644 --- a/src/main.c +++ b/src/main.c @@ -2,86 +2,10 @@ #include "resources.h" #include "game.h" #include "game_debug.h" +#include "window_scaler.h" struct Resources g_resources = { 0 }; -struct WindowTransform { - Vector2 offset; - float scale; -}; - -static void begin_window_scaling(struct WindowTransform *transform, Vector2 virtual_screen_size) -{ - Vector2 screen_size = { - GetScreenWidth(), - GetScreenHeight() - }; - - float scale = MIN( - screen_size.x / virtual_screen_size.x, - screen_size.y / virtual_screen_size.y - ); - transform->scale = scale; - - Vector2 filler_size = Vector2Scale(Vector2Subtract(screen_size, Vector2Scale(virtual_screen_size, transform->scale)), 0.5); - transform->offset = filler_size; - - rlPushMatrix(); - rlTranslatef( - filler_size.x, - filler_size.y, - 0 - ); - rlScalef(scale, scale, 1); -} - -static void end_window_scaling(struct WindowTransform *transform, Color color) -{ - rlPopMatrix(); - - Vector2 filler_size = transform->offset; - - DrawRectangleRec( - (struct Rectangle){ - .x = 0, - .y = 0, - .width = GetScreenWidth(), - .height = filler_size.y - }, - color - ); - - DrawRectangleRec( - (struct Rectangle){ - .x = 0, - .y = GetScreenHeight() - filler_size.y, - .width = GetScreenWidth(), - .height = filler_size.y - }, - color - ); - - DrawRectangleRec( - (struct Rectangle){ - .x = 0, - .y = 0, - .width = filler_size.x, - .height = GetScreenHeight() - }, - color - ); - - DrawRectangleRec( - (struct Rectangle){ - .x = GetScreenWidth() - filler_size.x, - .y = 0, - .width = filler_size.x, - .height = GetScreenHeight() - }, - color - ); -} - int main(void) { InitWindow(800, 600, "gaem"); @@ -107,18 +31,13 @@ int main(void) BeginDrawing(); ClearBackground(RAYWHITE); - struct WindowTransform transform = { 0 }; - begin_window_scaling(&transform, game.canvas_size); - Vector2 mouse = GetMousePosition(); - mouse = Vector2Subtract(mouse, transform.offset); - mouse = Vector2Scale(mouse, 1/transform.scale); + struct WindowScaler scaler = { 0 }; + window_scaler_begin(&scaler, game.canvas_size); - game.input = (struct Input){ - .dt = dt, - .mouse = mouse - }; + game_get_input(&game, &scaler, &game.input); game_tick(&game); - end_window_scaling(&transform, BLACK); + + window_scaler_end(&scaler, BLACK); game_debug_show(&game); diff --git a/src/rect.c b/src/rect.c new file mode 100644 index 0000000..a1907c2 --- /dev/null +++ b/src/rect.c @@ -0,0 +1,81 @@ +#include "rect.h" + +float rect_get_left(Rectangle rect) +{ + return rect.x; +} + +float rect_get_right(Rectangle rect) +{ + return rect.x + rect.width; +} + +float rect_get_top(Rectangle rect) +{ + return rect.y; +} + +float rect_get_bottom(Rectangle rect) +{ + return rect.y + rect.height; +} + +Rectangle rect_add_position(Rectangle rect, Vector2 pos) +{ + rect.x += pos.x; + rect.y += pos.y; + return rect; +} + +Vector2 rect_get_center(Rectangle rect) +{ + return (Vector2){ + .x = rect.x + rect.width / 2, + .y = rect.y + rect.height / 2, + }; +} + +Rectangle rect_grow(Rectangle rect, float x_amount, float y_amount) +{ + return rect_grow_y(rect_grow_x(rect, x_amount), y_amount); +} + +Rectangle rect_shrink_all(Rectangle rect, float amount) +{ + return rect_grow(rect, -amount, -amount); +} + +Rectangle rect_grow_x(Rectangle rect, float amount) +{ + rect.x -= amount; + rect.width += 2*amount; + return rect; +} + +Rectangle rect_grow_y(Rectangle rect, float amount) +{ + rect.y -= amount; + rect.height += 2*amount; + return rect; +} + +bool rect_is_overlap(Rectangle rect1, Rectangle rect2) +{ + return CheckCollisionRecs(rect1, rect2); +} + +bool rect_is_inside_rect(Rectangle outer_rect, Rectangle inner_rect) +{ + return rect_get_left(outer_rect) <= rect_get_left(inner_rect) && + rect_get_right(outer_rect) >= rect_get_right(inner_rect) && + rect_get_top(outer_rect) <= rect_get_top(inner_rect) && + rect_get_bottom(outer_rect) >= rect_get_bottom(inner_rect); +} + +bool rect_is_inside_point(Rectangle outer_rect, Vector2 inner_point) +{ + bool is_x_inside = rect_get_left(outer_rect) <= inner_point.x && inner_point.x <= rect_get_right(outer_rect); + bool is_y_inside = rect_get_top(outer_rect) <= inner_point.y && inner_point.y <= rect_get_bottom(outer_rect); + + return is_x_inside && is_y_inside; +} diff --git a/src/rect.h b/src/rect.h new file mode 100644 index 0000000..95020fd --- /dev/null +++ b/src/rect.h @@ -0,0 +1,17 @@ +#pragma once + +#include "main.h" + +float rect_get_left(Rectangle rect); +float rect_get_right(Rectangle rect); +float rect_get_top(Rectangle rect); +float rect_get_bottom(Rectangle rect); +Rectangle rect_add_position(Rectangle rect, Vector2 pos); +Vector2 rect_get_center(Rectangle rect); +Rectangle rect_grow_x(Rectangle rect, float amount); +Rectangle rect_grow_y(Rectangle rect, float amount); +Rectangle rect_grow(Rectangle rect, float x_amount, float y_amount); +bool rect_is_inside_point(Rectangle outer_rect, Vector2 inner_point); +bool rect_is_inside_rect(Rectangle outer_rect, Rectangle inner_rect); +bool rect_is_overlap(Rectangle rect1, Rectangle rect2); +Rectangle rect_shrink_all(Rectangle rect, float amount); diff --git a/src/timers.c b/src/timers.c new file mode 100644 index 0000000..30857c9 --- /dev/null +++ b/src/timers.c @@ -0,0 +1,159 @@ +#include "timers.h" + +static void timers_ensure_capacity(struct Timers *timers, size_t expected_capacity) +{ + if (timers->capacity < expected_capacity) { + size_t larger_capacity = MAX(MAX(timers->capacity*2, expected_capacity), 8); + struct Timer *larger_items = realloc(timers->items, larger_capacity * sizeof(*larger_items)); + if (larger_items == NULL) { + PANIC_OOM(); + } + timers->items = larger_items; + timers->capacity = larger_capacity; + } +} + +static TimerId timers_next_id(struct Timers *timers) +{ + // Make sure that TIMER_ID_NIL is not used + if (timers->last_id.value == 0) { + timers->last_id.value++; + } + + TimerId id = (TimerId){ + .value = timers->last_id.value++ + }; + ASSERT(id.value != TIMER_ID_NIL.value); + + return id; +} + +static int timers_find_index(struct Timers *timers, TimerId id) +{ + for (size_t i = 0; i < timers->len; i++) { + if (timers->items[i].id.value == id.value) { + return i; + } + } + + return -1; +} + +static void timers_remove(struct Timers *timers, int timer_index) +{ + ASSERT(0 <= timer_index && timer_index < (int)timers->len); + ASSERT(timers->len > 0); + + timers->len--; + if (timers->len > 0) { + timers->items[timer_index] = timers->items[timers->len]; + } +} + +TimerId timers_start(struct Timers *timers, double duration) +{ + timers_ensure_capacity(timers, timers->len + 1); + struct Timer *timer = &timers->items[timers->len++]; + *timer = (struct Timer){ + .id = timers_next_id(timers), + .started_at = timers->now, + .finishes_at = timers->now + duration + }; + + return timer->id; +} + +TimerId timers_start_with_entity(struct Timers *timers, double duration, EntityId entity_id) +{ + TimerId timer_id = timers_start(timers, duration); + timers_set_entity(timers, timer_id, entity_id); + return timer_id; +} + +void timers_set_entity(struct Timers *timers, TimerId timer_id, EntityId entity_id) +{ + int timer_index = timers_find_index(timers, timer_id); + ASSERT(timer_index != -1); + + struct Timer *timer = &timers->items[timer_index]; + timer->entity_id = entity_id; +} + +void timers_set_attack(struct Timers *timers, TimerId timer_id) +{ + int timer_index = timers_find_index(timers, timer_id); + ASSERT(timer_index != -1); + + struct Timer *timer = &timers->items[timer_index]; + timer->attack = true; +} + +void timers_stop(struct Timers *timers, TimerId timer_id) +{ + int timer_index = timers_find_index(timers, timer_id); + if (timer_index == -1) { + return; + } + + timers_remove(timers, timer_index); +} + +void timers_stop_by_entity(struct Timers *timers, EntityId entity_id) +{ + ASSERT(!entity_id_is_nil(entity_id)); + + size_t index = 0; + while (index < timers->len) { + if (entity_id_eql(timers->items[index].entity_id, entity_id)) { + timers_remove(timers, index); + } else { + index++; + } + } +} + +void timers_stop_by_attack(struct Timers *timers) +{ + size_t index = 0; + while (index < timers->len) { + if (timers->items[index].attack) { + timers_remove(timers, index); + } else { + index++; + } + } +} + +bool timers_finished(struct Timers *timers, TimerId timer_id) +{ + int timer_index = timers_find_index(timers, timer_id); + if (timer_index == -1) { + return true; + } + struct Timer *timer = &timers->items[timer_index]; + + if (timers->now < timer->finishes_at) { + return false; + } + + timers_remove(timers, timer_index); + + return true; +} + +double timers_time_since_start(struct Timers *timers, TimerId id) +{ + int timer_index = timers_find_index(timers, id); + if (timer_index == -1) { + return 0; + } + struct Timer *timer = &timers->items[timer_index]; + + return timers->now - timer->started_at; +} + +void timers_free(struct Timers *timers) +{ + free(timers->items); + *timers = (struct Timers){ 0 }; +} diff --git a/src/timers.h b/src/timers.h new file mode 100644 index 0000000..1423763 --- /dev/null +++ b/src/timers.h @@ -0,0 +1,41 @@ +#pragma once + +#include "main.h" +#include "entity_id.h" + +typedef struct { + uint32_t value; +} TimerId; + +#define TIMER_ID_NIL (TimerId){ 0 } + +struct Timer { + TimerId id; + double started_at; + double finishes_at; + + EntityId entity_id; + bool attack; +}; + +struct Timers { + TimerId last_id; + struct Timer *items; + size_t len; + size_t capacity; + + double now; +}; + +TimerId timers_start(struct Timers *timers, double duration); +void timers_stop(struct Timers *timers, TimerId id); +void timers_free(struct Timers *timers); +bool timers_finished(struct Timers *timers, TimerId id); +double timers_time_since_start(struct Timers *timers, TimerId id); + +void timers_set_entity(struct Timers *timers, TimerId timer_id, EntityId entity_id); +void timers_stop_by_entity(struct Timers *timers, EntityId entity_id); +TimerId timers_start_with_entity(struct Timers *timers, double duration, EntityId entity_id); + +void timers_set_attack(struct Timers *timers, TimerId timer_id); +void timers_stop_by_attack(struct Timers *timers); diff --git a/src/window_scaler.c b/src/window_scaler.c new file mode 100644 index 0000000..8e38c1c --- /dev/null +++ b/src/window_scaler.c @@ -0,0 +1,73 @@ +#include "window_scaler.h" + +void window_scaler_begin(struct WindowScaler *transform, Vector2 virtual_screen_size) +{ + Vector2 screen_size = { + GetScreenWidth(), + GetScreenHeight() + }; + + float scale = MIN( + screen_size.x / virtual_screen_size.x, + screen_size.y / virtual_screen_size.y + ); + transform->scale = scale; + + Vector2 filler_size = Vector2Scale(Vector2Subtract(screen_size, Vector2Scale(virtual_screen_size, transform->scale)), 0.5); + transform->offset = filler_size; + + rlPushMatrix(); + rlTranslatef( + filler_size.x, + filler_size.y, + 0 + ); + rlScalef(scale, scale, 1); +} + +void window_scaler_end(struct WindowScaler *transform, Color color) +{ + rlPopMatrix(); + + Vector2 filler_size = transform->offset; + + DrawRectangleRec( + (struct Rectangle){ + .x = 0, + .y = 0, + .width = GetScreenWidth(), + .height = filler_size.y + }, + color + ); + + DrawRectangleRec( + (struct Rectangle){ + .x = 0, + .y = GetScreenHeight() - filler_size.y, + .width = GetScreenWidth(), + .height = filler_size.y + }, + color + ); + + DrawRectangleRec( + (struct Rectangle){ + .x = 0, + .y = 0, + .width = filler_size.x, + .height = GetScreenHeight() + }, + color + ); + + DrawRectangleRec( + (struct Rectangle){ + .x = GetScreenWidth() - filler_size.x, + .y = 0, + .width = filler_size.x, + .height = GetScreenHeight() + }, + color + ); +} diff --git a/src/window_scaler.h b/src/window_scaler.h new file mode 100644 index 0000000..a80bfae --- /dev/null +++ b/src/window_scaler.h @@ -0,0 +1,11 @@ +#pragma once + +#include "main.h" + +struct WindowScaler { + Vector2 offset; + float scale; +}; + +void window_scaler_begin(struct WindowScaler *transform, Vector2 virtual_screen_size); +void window_scaler_end(struct WindowScaler *transform, Color color);