From c15c603b35e86fd42e6db94a7382ba7a1ddadd6e Mon Sep 17 00:00:00 2001 From: Thomas Guillermo Albers Raviola Date: Fri, 16 Jan 2026 23:26:01 +0100 Subject: Initial commit --- Makefile | 13 +++ README.md | 10 ++ bullet.c | 117 +++++++++++++++++++ collisions.c | 130 +++++++++++++++++++++ config | 6 + file.c | 42 +++++++ game.h | 272 ++++++++++++++++++++++++++++++++++++++++++++ main.c | 265 +++++++++++++++++++++++++++++++++++++++++++ manifest.scm | 6 + menu.c | 179 +++++++++++++++++++++++++++++ player.c | 298 +++++++++++++++++++++++++++++++++++++++++++++++++ render.c | 64 +++++++++++ resources/block.png | Bin 0 -> 343 bytes resources/blue.png | Bin 0 -> 178 bytes resources/bullet.png | Bin 0 -> 230 bytes resources/cowboy.png | Bin 0 -> 1207 bytes resources/font.ttf | Bin 0 -> 65932 bytes resources/heart.png | Bin 0 -> 389 bytes resources/heart.xcf | Bin 0 -> 1752 bytes resources/revolver.ogg | Bin 0 -> 34476 bytes resources/revolver.png | Bin 0 -> 492 bytes resources/rifle.ogg | Bin 0 -> 65753 bytes resources/rifle.png | Bin 0 -> 467 bytes resources/shotgun.png | Bin 0 -> 463 bytes util.c | 61 ++++++++++ 25 files changed, 1463 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 bullet.c create mode 100644 collisions.c create mode 100644 config create mode 100644 file.c create mode 100644 game.h create mode 100644 main.c create mode 100644 manifest.scm create mode 100644 menu.c create mode 100644 player.c create mode 100644 render.c create mode 100644 resources/block.png create mode 100644 resources/blue.png create mode 100644 resources/bullet.png create mode 100644 resources/cowboy.png create mode 100644 resources/font.ttf create mode 100644 resources/heart.png create mode 100644 resources/heart.xcf create mode 100644 resources/revolver.ogg create mode 100644 resources/revolver.png create mode 100644 resources/rifle.ogg create mode 100644 resources/rifle.png create mode 100644 resources/shotgun.png create mode 100644 util.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..628d7d4 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +OBJ = main.o menu.o render.o util.o bullet.o player.o collisions.o file.o +CFLAGS = -std=c99 -g -Wall +LDFLAGS = -lSDL2 -lSDL2_image -lSDL2_ttf -lm -lSDL2_mixer + +all: $(OBJ) + @$(CC) $(OBJ) -o highnoon $(LDFLAGS) + +%.o : %.c + @$(CC) $(CFLAGS) -c -o $@ $< + +.PHONY : clean +clean: + @rm -f $(OBJ) highnoon diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b72599 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# Highnoon: The Second Attempt + +My second attempt at writing highnoon. This version is actually playable. I have +compiled may times and playes with friends. Though it is by no means done. + +## Dependencies +- SDL2 +- SDL2 image +- SDL2 mixer +- SDL2 ttf diff --git a/bullet.c b/bullet.c new file mode 100644 index 0000000..7885862 --- /dev/null +++ b/bullet.c @@ -0,0 +1,117 @@ +#include "game.h" +#include + +static const float BULLET_SPEED = 600.0f; + +static void destroy_bullet(unsigned char index) +{ + struct Bullet *b = game->bullets[index]; + + free(b); + game->bullets[index] = NULL; + + game->bullets_left ++; +} + +static void bullet_on_collision(struct Entity *this, struct Entity *other) +{ + int i, j; + // check if it is the enemy player + // TODO: Ask if this is good practice + struct Bullet *b = (struct Bullet *)this - offsetof(struct Bullet, entity); + + if((void *)b->owner == (void *)other) + return; + + if(!this->collidable && !other->collidable) + return; + + for(i = 0; i < game->num_entities; i ++) + { + if(game->entities[i] == this) + { + for(j = i; j < game->num_entities - 1; j ++) + game->entities[j] = game->entities[j + 1]; + //game->entities[i] = NULL; + } + } + game->num_entities --; + destroy_bullet(b->index); +} + +void shoot_bullet(struct Player *owner) +{ + int i, j; + float speed, angle; + struct Bullet *b; + struct Gun *g = &owner->guns[owner->current_gun]; + + if(game->bullets_left < g->projectiles_per_shot + || !g->ready || !g->bullets || owner->reloading) + return; + + for(i = 0; i < g->projectiles_per_shot; i ++) + { + b = malloc( sizeof(struct Bullet) ); + b->owner = owner; + b->entity.on_collision = bullet_on_collision; + b->entity.collidable = false; + b->entity.use_src_rect = false; + b->entity.texture = game->bullet_texture; + b->entity.x = owner->entity.x; + b->entity.y = owner->entity.y + 10; + b->entity.w = BULLET_WIDTH; + b->entity.h = BULLET_WIDTH; + b->damage = g->damage; + b->entity.name = "bullet"; + angle = g->angle * ((float)rand() / (float)RAND_MAX) - g->angle / 2.0f; + speed = (owner->entity.mirror ? -BULLET_SPEED : BULLET_SPEED); + + b->entity.dx = speed; + b->entity.dy = SDL_sinf(angle) * 100.0f; + + for(j = 0; j < MAX_BULLETS; j ++) + { + if(!game->bullets[j]) + { + game->bullets[j] = b; + b->index = j; + break; + } + } + + game->bullets_left --; + } + + g->bullets --; + g->ready = false; + g->cooldown_elapsed = 0.0f; + + Mix_PlayChannel(-1, g->sound_effect, 0); +} + +void update_bullets(void) +{ + int i; + struct Bullet *b; + + for(i = 0; i < MAX_BULLETS; i ++) + { + b = game->bullets[i]; + + if(b) + { + b->entity.x += b->entity.dx * game->frame_time; + b->entity.y += b->entity.dy * game->frame_time; + + if(b->entity.x > (float)game->width || b->entity.x < 0.0f + || b->entity.y > (float)game->height || b->entity.y < 0.0f) + { + destroy_bullet(b->index); + continue; + } + + game->entities[game->num_entities ++] = &b->entity; + } + } +} diff --git a/collisions.c b/collisions.c new file mode 100644 index 0000000..e9f0c13 --- /dev/null +++ b/collisions.c @@ -0,0 +1,130 @@ +#include "game.h" + +struct FRect +{ + float x, y, w, h; +}; + +static inline bool colliding2D(const struct FRect *a, const struct FRect *b) +{ + return ((a->x + a->w > b->x && a->x < b->x + b->w) + && (a->y + a->h > b->y && a->y < b->y + b->h)); +} + +bool frect_intersect(const struct FRect *a, const struct FRect *b, struct FRect *res) +{ + res->x = fmaxf(a->x, b->x); + res->w = fminf(a->x + a->w, b->x + b->w) - fmaxf(a->x, b->x); + res->y = fmaxf(a->y, b->y); + res->h = fminf(a->y + a->h, b->y + b->h) - fmaxf(a->y, b->y); + + return colliding2D(a, b); +} + +bool colliding_vertically(const struct Entity *a, const struct Entity *b, const struct FRect *res) +{ + //return a->y + a->h < b->y + b->h / 2.0f || b->y + b->h < a->y + a->h / 2.0f; + return res->w > res->h; +} + +void collide_with_fixed(struct Entity *e, struct Entity *fixed, const struct FRect *res) +{ + // Check if either of the boxes + if( colliding_vertically(e, fixed, res) ) + { + e->y += (e->y < fixed->y) ? -res->h : res->h; + e->dy = 0.0f; + } + else + { + e->x += (e->x < fixed->x) ? -res->w : res->w; + e->dx = 0.0f; + } +} + +void collide(struct Entity *a, struct Entity *b, const struct FRect *res) +{ + float a_disp, b_disp; + + float vx_sum, vy_sum; + vx_sum = fabsf(a->dx) + fabsf(b->dx); + vy_sum = fabsf(a->dy) + fabsf(b->dy); + + // Check if either of the boxes + if( colliding_vertically(a, b, res) ) + { + a_disp = vy_sum == 0.0f ? res->h / 2.0f : res->h * (fabsf(a->dy) / vy_sum); + b_disp = vy_sum == 0.0f ? res->h / 2.0f : res->h * (fabsf(b->dy) / vy_sum); + //a_disp = res->h / 2.0f; + //b_disp = res->h / 2.0f; + + a->y += (a->y < b->y) ? -a_disp : a_disp; + b->y -= (a->y < b->y) ? -b_disp : b_disp; + + a->dy = 0.0f; + b->dy = 0.0f; + } + else + { + a_disp = vx_sum == 0.0f ? res->w / 2.0f : res->w * (fabsf(a->dx) / vx_sum); + b_disp = vx_sum == 0.0f ? res->w / 2.0f : res->w * (fabsf(b->dx) / vx_sum); + //a_disp = res->w / 2.0f; + //b_disp = res->w / 2.0f; + + a->x += (a->x < b->x) ? -a_disp : a_disp; + b->x -= (a->x < b->x) ? -b_disp : b_disp; + + a->dx = 0.0f; + b->dx = 0.0f; + } +} + +void check_collisions(void) +{ + int i, j; + struct Entity *a, *b; + + for(i = 0; i < game->num_entities; i ++) + { + a = game->entities[i]; + + if(!a) + continue; + + for(j = i + 1; j < game->num_entities; j ++) + { + b = game->entities[j]; + + if(!b) + continue; + + struct FRect res; + struct FRect r1 = {a->x, a->y, a->w, a->h}; + struct FRect r2 = {b->x, b->y, b->w, b->h}; + + if( frect_intersect(&r1, &r2, &res) ) + { + if(a->collidable && b->collidable) + { + if(!a->fixed && !b->fixed) + { + collide(a, b, &res); + } + else if(a->fixed && !b->fixed) + { + collide_with_fixed(b, a, &res); + } + else if(!a->fixed && b->fixed) + { + collide_with_fixed(a, b, &res); + } + } + + if(a->on_collision) + a->on_collision(a, b); + if(b->on_collision) + b->on_collision(b, a); + } + } + } +} diff --git a/config b/config new file mode 100644 index 0000000..e3a4db4 --- /dev/null +++ b/config @@ -0,0 +1,6 @@ +# x y w h use_src_rect mirror texture +#100 500 32 32 0 0 resources/block.png +#300 500 32 32 0 0 resources/block.png +300 590 32 32 0 0 resources/block.png +500 550 32 32 0 0 resources/block.png +400 500 32 32 0 0 resources/block.png diff --git a/file.c b/file.c new file mode 100644 index 0000000..64ee1c8 --- /dev/null +++ b/file.c @@ -0,0 +1,42 @@ +#include "game.h" +#include + +void load_config(const char *config) +{ + FILE *file = fopen(config, "r"); + + if(!file) + die("Config \'%s\' could not be found\n", config); + + char buffer[64]; + + struct Entity *e; + int use_src_rect, mirror; + char texture[16]; + + while( !feof(file) && game->num_tiles < MAX_TILES ) + { + if( !fgets(buffer, 64,file) ) + break; + + if(buffer[0] == '#') + continue; + + // Tile + e = &game->tiles[game->num_tiles ++]; + + sscanf(buffer, "%f %f %d %d %d %d %s\n", &e->x, &e->y, + &e->w, &e->h, &use_src_rect, &mirror, texture); + + e->use_src_rect = use_src_rect ? true : false; + e->mirror = mirror ? true : false; + e->collidable = true; + e->fixed = true; + e->texture = load_texture(texture); + + e->name = malloc(32); + sprintf(e->name, "brick %d", game->num_tiles); + } + + fclose(file); +} diff --git a/game.h b/game.h new file mode 100644 index 0000000..b20c271 --- /dev/null +++ b/game.h @@ -0,0 +1,272 @@ +#ifndef __GAME_H__ +#define __GAME_H__ + +#include +#include +#include +#include + +#include + +#define MAX_AUDIOS 10 +#define MAX_TEXTURES 100 +#define MAX_WIDGETS_PER_LAYER 10 +#define MAX_ENTITIES 128 +#define MAX_TILES 64 + +#define MAX_BULLETS 32 +#define BULLET_WIDTH 8 + +#define MENU_LAYER 0 +#define GAME_LAYER 1 + +#define MOUSE_BUTTON 0 +#define MOUSE_MOTION 1 + +#define POSITION_CENTERED -1 + +#define WINDOW_WIDTH 800 +#define WINDOW_HEIGHT 600 + +enum GameStates + { + QUIT = 0, + RUNNING = 1, + MENU = 2 + }; + +struct Entity +{ + float x, y; + float dx, dy; // Velocity + float d2x, d2y; // Acceleration + + int w, h; + + bool collidable; + bool mirror; + bool fixed; + bool use_src_rect; + int i, j; + SDL_Rect src_rect; + + SDL_Texture *texture; + char *name; + void (*on_collision)(struct Entity *this, struct Entity *other); +}; + +struct Camera +{ + SDL_Rect rect; +}; + +struct Color +{ + unsigned char r, g, b; +}; + +struct Widget +{ + bool use_src_rect; + SDL_Rect rect, src_rect; + SDL_Texture *texture; + + struct Color color; + struct Color on_hover_color; + struct Color default_color; + + bool is_hover; + bool is_focus; + bool is_blended; + int initial_x; + int initial_y; + + void (*callback)(struct Widget *); +}; + +enum GunType + { + REVOLVER = 0, + RIFLE = 1, + SHOTGUN = 2 + }; + +struct Gun +{ + unsigned char type; + float angle; + unsigned char projectiles_per_shot; + bool ready; + float cooldown; + float cooldown_elapsed; + + unsigned char damage; + unsigned char bullets; + unsigned char max_bullets; + + Mix_Chunk *sound_effect; +}; + +struct PlayerKeys +{ + unsigned char jump; + unsigned char left, right; + unsigned char fire, reload; + unsigned char weaponds[3]; +}; + +struct Player +{ + struct Entity entity; + + unsigned char current_gun; + struct Gun guns[3]; + + bool facing_left; + bool on_ground; + bool reloading; + + char life; + char score; + float reload_time; + + char name[16]; + struct PlayerKeys keys; + + struct Widget *hearts[3]; + struct Widget *gun; + int counter; +}; + +struct Bullet +{ + struct Entity entity; + struct Player *owner; + + struct Bullet *next; + + unsigned char index; + unsigned char damage; +}; + +struct Layer +{ + //int num_entities; + //struct Entity *entities[MAX_ENTITIES_PER_LAYER]; + + int num_widgets; + struct Widget widgets[MAX_WIDGETS_PER_LAYER]; +}; + +struct Menu +{ + struct Widget *play_button; + struct Widget *quit_button; +}; + +struct TextureContainer +{ + char name[16]; + SDL_Texture *texture; +}; + +struct Resources +{ + int num_textures; + SDL_Texture *textures[MAX_TEXTURES]; + + int num_stored_textures; + struct TextureContainer contained_textures[MAX_TEXTURES]; + + int num_audios; + Mix_Chunk *audios[MAX_AUDIOS]; + + TTF_Font *font; +}; + +struct MainGame +{ + SDL_Window *window; + SDL_Renderer *renderer; + int width, height; + + unsigned char state; + int gravity; + + int num_players; + struct Player players[2]; + + // 0 -> game, 1 -> menu + struct Layer layers[2]; + unsigned char current_layer; + + struct Resources resources; + + struct Menu menu; + const Uint8 *keys; + + unsigned int bullets_left; + SDL_Texture *bullet_texture; + struct Bullet *bullets[MAX_BULLETS]; + + int num_entities; + struct Entity *entities[MAX_ENTITIES]; + + int num_tiles; + struct Entity tiles[MAX_TILES]; + + float frame_time; + SDL_Texture *guns[3]; + Mix_Chunk *revolver_sound, *rifle_sound; + + struct Widget *score; + + struct Camera camera; +}; + +extern struct MainGame *game; + +/* Util */ +SDL_Texture *load_texture(const char *name); +Mix_Chunk *load_sound(const char *name); +void die(const char *error, ...) __attribute__ ((noreturn)); + +/* GUI */ +struct Widget *add_button(unsigned char layer, + const char *label, + int x, int y, + void (*callback)(struct Widget *)); + +struct Widget *add_image(unsigned char layer, SDL_Texture *image, + int x, int y, int w, int h); + +void process_widget_events(unsigned char type, int x, int y); +void set_color(struct Widget *w, struct Color color); +void set_text(struct Widget *w, const char *text); +struct Widget *add_label(unsigned char layer, + const char *text, + struct Color color, + int x, int y); + +/* Renderer */ +void render_begin(void); +void render_entities(void); +void render_flush(void); + +/* Bullets */ +void init_bullets(void); +void shoot_bullet(struct Player *owner); +void update_bullets(void); + +/* Players */ +void init_player(struct Player *p, struct PlayerKeys *keys, + float x, float y, const char *name); +void update_player(struct Player *p); +void update_score(void); + +/* Collisions */ +void check_collisions(void); + +void load_config(const char *config); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..6cfb1d7 --- /dev/null +++ b/main.c @@ -0,0 +1,265 @@ +#include +#include +#include + +#include "game.h" + +struct MainGame *game; +static const char *game_name = "Highnoon"; + +void process_events(void) +{ + SDL_Event e; + int x, y; + + while( SDL_PollEvent(&e) ) + { + switch(e.type) + { + case SDL_QUIT: + game->state = QUIT; + break; + + case SDL_MOUSEBUTTONDOWN: + SDL_GetMouseState(&x, &y); + process_widget_events(MOUSE_BUTTON, x, y); + break; + + case SDL_MOUSEMOTION: + process_widget_events(MOUSE_MOTION, e.motion.x, e.motion.y); + break; + } + } + + if(game->state == RUNNING && game->keys[SDL_SCANCODE_ESCAPE]) + { + game->state = MENU; + game->current_layer = MENU_LAYER; + } +} + +void unload_game(void) +{ + int i; + struct Resources *res = &game->resources; + + for(i = 0; i < res->num_textures; i ++) + SDL_DestroyTexture(res->textures[i]); + + for(i = 0; i < res->num_audios; i ++) + Mix_FreeChunk(res->audios[i]); + + TTF_CloseFont(res->font); +} + +static void play_button_callback(struct Widget *w) +{ + game->state = RUNNING; + game->current_layer = GAME_LAYER; +} + +static void quit_button_callback(struct Widget *w) +{ + game->state = QUIT; +} + +void load_menu(void) +{ + struct Color c = {255, 255, 255}; + struct Color on_hover = {255, 255, 0}; + + game->menu.play_button = add_button(MENU_LAYER, "Play", POSITION_CENTERED, + 150, play_button_callback); + set_color(game->menu.play_button, c); + game->menu.play_button->on_hover_color = on_hover; + + game->menu.quit_button = add_button(MENU_LAYER, "Quit", POSITION_CENTERED, + 190, quit_button_callback); + set_color(game->menu.quit_button, c); + game->menu.quit_button->on_hover_color = on_hover; + + add_label(MENU_LAYER, game_name, (struct Color){255, 255, 255}, POSITION_CENTERED, 50); +} + +void load_game(void) +{ + int i; + struct PlayerKeys keys[2] = + { + { + .jump = SDL_SCANCODE_W, + .left = SDL_SCANCODE_A, + .right = SDL_SCANCODE_D, + .fire = SDL_SCANCODE_F, + .reload = SDL_SCANCODE_R, + .weaponds = {SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_3} + }, + { + .jump = SDL_SCANCODE_UP, + .left = SDL_SCANCODE_LEFT, + .right = SDL_SCANCODE_RIGHT, + .fire = SDL_SCANCODE_K, + .reload = SDL_SCANCODE_L, + .weaponds = {SDL_SCANCODE_B, SDL_SCANCODE_N, SDL_SCANCODE_M} + } + }; + + game->guns[0] = load_texture("resources/revolver.png"); + game->guns[1] = load_texture("resources/rifle.png"); + game->guns[2] = load_texture("resources/shotgun.png"); + + game->revolver_sound = load_sound("resources/revolver.ogg"); + game->rifle_sound = load_sound("resources/rifle.ogg"); + + game->num_players = 2; + game->bullets_left = MAX_BULLETS; + game->keys = SDL_GetKeyboardState(NULL); + + init_player(&game->players[0], &keys[0], 200.0f, 200.0f, "Player1"); + init_player(&game->players[1], &keys[1], 500.0f, 200.0f, "Player2"); + + game->bullet_texture = load_texture("resources/bullet.png"); + srand(time(NULL)); + + game->score = add_label(GAME_LAYER, "", + (struct Color){255, 255, 255}, POSITION_CENTERED, 0); + update_score(); + + for(i = 0; i < 3; i ++) + { + game->players[0].hearts[i] = add_image(GAME_LAYER, load_texture("resources/heart.png"), + 20 + i * 80, 100, 64, 64); + game->players[0].hearts[i]->use_src_rect = true; + game->players[0].hearts[i]->src_rect = (SDL_Rect){0, 0, 16, 16}; + } + + int w, h; + SDL_QueryTexture(game->guns[0], NULL, NULL, &w, &h); + game->players[0].gun = add_image(GAME_LAYER, game->guns[0], 0, 0, w * 2, h * 2); + game->players[1].gun = add_image(GAME_LAYER, game->guns[0], 400, 0, w * 2, h * 2); + + for(i = 0; i < 3; i ++) + { + game->players[1].hearts[i] = add_image(GAME_LAYER, load_texture("resources/heart.png"), + (game->width - 250) + i * 80, 100, 64, 64); + game->players[1].hearts[i]->use_src_rect = true; + game->players[1].hearts[i]->src_rect = (SDL_Rect){0, 0, 16, 16}; + } + + load_config("config"); + + game->camera.rect.x = WINDOW_WIDTH / 2; + game->camera.rect.y = WINDOW_HEIGHT / 2; + game->camera.rect.w = WINDOW_WIDTH; + game->camera.rect.h = WINDOW_HEIGHT; +} + +static const unsigned int ticks_per_frame = 1000 / 75; + +/* + To make things easier with collision we can have moveable and unmoveable + entities. If a entity is unmoveable then under collisions it should stay in + place and we only have to go back with the moveable +*/ + +int main(int argc, char **argv) +{ + int i; + unsigned int start_ticks, frame_ticks; + + game = calloc( 1, sizeof(struct MainGame) ); + + SDL_Init(SDL_INIT_EVERYTHING); + Mix_Init(MIX_INIT_OGG); + if( TTF_Init() != 0) + die("Error while initializing SDL2_ttf\n", TTF_GetError()); + + game->window = SDL_CreateWindow(game_name, SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, 0); + game->renderer = SDL_CreateRenderer(game->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + + if(game->window == NULL || game->renderer == NULL) + { + die("Error while creating the window and/or renderer:\n%s\n", + SDL_GetError()); + } + + SDL_SetRenderDrawColor(game->renderer, 92, 51, 23, 255); + + if(Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 4096) == -1) + die("Audio could not be opened: %s", Mix_GetError()); + + game->width = WINDOW_WIDTH; + game->height = WINDOW_HEIGHT; + game->gravity = 1200.0f; + game->state = MENU; + game->current_layer = MENU_LAYER; + + game->resources.font = TTF_OpenFont("resources/font.ttf", 40); + + load_menu(); + load_game(); + + struct Widget w = {}; + + /* game->players[0].guns[2].max_bullets = 100; */ + /* game->players[0].guns[2].projectiles_per_shot = 10; */ + /* game->players[0].guns[2].cooldown = .5f; */ + + while(game->state != QUIT) + { + //game->camera.rect.x = game->players[0].entity.x; + //game->camera.rect.y = game->players[0].entity.y; + game->num_entities = 0; + start_ticks = SDL_GetTicks(); + + process_events(); + + if(game->current_layer == GAME_LAYER) + { + update_player(&game->players[0]); + update_player(&game->players[1]); + update_bullets(); + + for(i = 0; i < game->num_tiles; i ++) + game->entities[game->num_entities ++] = &game->tiles[i]; + + check_collisions(); + } + + render_begin(); + + if(game->state == RUNNING) + render_entities(); + + char text[50]; + sprintf(text, "%hhd -- %hhd", + game->players[0].guns[game->players[0].current_gun].bullets, + game->players[1].guns[game->players[1].current_gun].bullets); + set_text(&w, text); + if (w.texture) { + SDL_Rect rect = {0, 200, 100, 50}; + SDL_RenderCopyEx(game->renderer, w.texture, NULL, &rect, 0, + NULL, SDL_FLIP_NONE); + } + render_flush(); + + frame_ticks = SDL_GetTicks() - start_ticks; + if( frame_ticks < ticks_per_frame ) + SDL_Delay(ticks_per_frame - frame_ticks); + + // Frame time in seconds + game->frame_time = (float)frame_ticks / 1000.0f; + } + + SDL_DestroyRenderer(game->renderer); + SDL_DestroyWindow(game->window); + + unload_game(); + free(game); + + SDL_Quit(); + TTF_Quit(); + Mix_Quit(); + return 0; +} diff --git a/manifest.scm b/manifest.scm new file mode 100644 index 0000000..d16e1a7 --- /dev/null +++ b/manifest.scm @@ -0,0 +1,6 @@ +(specifications->manifest + (list "gcc-toolchain" + "SDL2" + "SDL2-image" + "SDL2-mixer" + "SDL2-ttf")) diff --git a/menu.c b/menu.c new file mode 100644 index 0000000..4aae11f --- /dev/null +++ b/menu.c @@ -0,0 +1,179 @@ +#include "game.h" + +struct Widget * +add_button(unsigned char layer, const char *label, int x, int y, + void (*callback)(struct Widget *)) +{ + SDL_Surface *surface; + SDL_Color c = {255, 255, 255, 255}; + + struct Resources *res = &game->resources; + struct Layer *l = &game->layers[layer]; + struct Widget *w; + + w = &l->widgets[l->num_widgets ++]; + + w->callback = callback; + w->is_blended = true; + + surface = TTF_RenderText_Solid(res->font, label, c); + TTF_SizeText(res->font, label, &w->rect.w, &w->rect.h); + w->texture = SDL_CreateTextureFromSurface(game->renderer, surface); + res->textures[res->num_textures ++] = w->texture; + SDL_FreeSurface(surface); + + if(x == POSITION_CENTERED) + w->rect.x = (game->width / 2) - (w->rect.w / 2); + else + w->rect.x = x; + + if(y == POSITION_CENTERED) + w->rect.y = (game->height / 2) - (w->rect.h / 2); + else + w->rect.y = y; + + return w; +} + +void set_color(struct Widget *w, struct Color color) +{ + w->color = color; + w->on_hover_color = color; + w->default_color = color; +} + +struct Widget *add_image(unsigned char layer, SDL_Texture *image, + int x, int y, int w, int h) +{ + struct Layer *l = &game->layers[layer]; + struct Widget *widget; + + widget = &l->widgets[l->num_widgets ++]; + + widget->callback = NULL; + widget->texture = image; + + widget->is_blended = false; + + widget->rect.w = w; + widget->rect.h = h; + + widget->color = (struct Color){255, 255, 255}; + widget->default_color = widget->color; + + if(x == POSITION_CENTERED) + widget->rect.x = (game->width / 2) - (widget->rect.w / 2); + else + widget->rect.x = x; + + if(y == POSITION_CENTERED) + widget->rect.y = (game->height / 2) - (widget->rect.h / 2); + else + widget->rect.y = y; + + return widget; +} + +void process_widget_events(unsigned char type, int x, int y) +{ + int i; + struct Layer *layer = &game->layers[game->current_layer]; + struct Widget *widget; + + for(i = 0; i < layer->num_widgets; i ++) + { + widget = &layer->widgets[i]; + + if(x > widget->rect.x && x < widget->rect.x + widget->rect.w + && y > widget->rect.y && y < widget->rect.y + widget->rect.h) + { + widget->is_hover = true; + widget->color = widget->on_hover_color; + + if(widget->callback != NULL && type == MOUSE_BUTTON) + widget->callback(widget); + } + else + { + widget->is_hover = false; + widget->color = widget->default_color; + } + } +} + +struct Widget *add_label(unsigned char layer, + const char *text, + struct Color color, + int x, int y) +{ + SDL_Surface *surface; + SDL_Color c = (SDL_Color){color.r, color.g, color.b, 255}; + + struct Resources *res = &game->resources; + struct Layer *l = &game->layers[layer]; + struct Widget *w; + + w = &l->widgets[l->num_widgets ++]; + + w->callback = NULL; + w->is_blended = true; + + w->initial_x = x; + w->initial_y = y; + w->default_color = color; + w->color = color; + w->on_hover_color = color; + + surface = TTF_RenderText_Solid(res->font, text, c); + TTF_SizeText(res->font, text, &w->rect.w, &w->rect.h); + + w->texture = SDL_CreateTextureFromSurface(game->renderer, surface); + res->textures[res->num_textures ++] = w->texture; + + SDL_FreeSurface(surface); + + if(x == POSITION_CENTERED) + w->rect.x = (game->width / 2) - (w->rect.w / 2); + else + w->rect.x = x; + + if(y == POSITION_CENTERED) + w->rect.y = (game->height / 2) - (w->rect.h / 2); + else + w->rect.y = y; + + return w; +} + +void set_text(struct Widget *w, const char *text) +{ + int i; + SDL_Surface *surface; + SDL_Texture *texture; + SDL_Color c = (SDL_Color){w->default_color.r, + w->default_color.g, + w->default_color.b, + 255}; + + surface = TTF_RenderText_Solid(game->resources.font, text, c); + TTF_SizeText(game->resources.font, text, &w->rect.w, &w->rect.h); + texture = SDL_CreateTextureFromSurface(game->renderer, surface); + SDL_FreeSurface(surface); + + for(i = 0; i < game->resources.num_textures; i ++) + { + if(game->resources.textures[i] == w->texture) + { + SDL_DestroyTexture(w->texture); + game->resources.textures[i] = texture; + } + } + + w->texture = texture; + + if(w->initial_x == POSITION_CENTERED) + w->rect.x = (game->width / 2) - (w->rect.w / 2); + + if(w->initial_y == POSITION_CENTERED) + w->rect.y = (game->height / 2) - (w->rect.h / 2); +} diff --git a/player.c b/player.c new file mode 100644 index 0000000..e05933d --- /dev/null +++ b/player.c @@ -0,0 +1,298 @@ +#include "game.h" +#include + +void update_score(void) +{ + char buffer[48]; + struct Player *p1 = &game->players[0], *p2 = &game->players[1]; + + sprintf(buffer, "%s: %d - %s: %d", p1->name, p1->score, p2->name, p2->score); + set_text(game->score, buffer); + + p1->life = p2->life = 12; +} + +static const float reload_time = 2.0f; + +static void player_on_collision(struct Entity *this, struct Entity *other) +{ + struct Player *p = (struct Player *)this - offsetof(struct Player, entity); + + if(other->collidable && this->y <= other->y) + { + p->entity.i = 0; + p->on_ground = true; + this->d2y = 0.0f; + } + + if(!other->collidable) + { + struct Bullet *b = (struct Bullet *)other; + + if(b->owner == p) + return; + + p->life -= b->damage; + + int i; + + if(p->life > 8) + { + i = p->life - 8; + p->hearts[2]->src_rect.x = 16 * (4 - i); + p->hearts[1]->src_rect.x = 0; + p->hearts[0]->src_rect.x = 0; + + } + else if(p->life > 4) + { + i = p->life - 4; + p->hearts[2]->src_rect.x = 64; + p->hearts[1]->src_rect.x = 16 * (4 - i); + p->hearts[0]->src_rect.x = 0; + } + else + { + i = p->life; + p->hearts[2]->src_rect.x = 64; + p->hearts[1]->src_rect.x = 64; + p->hearts[0]->src_rect.x = 16 * (4 - i); + } + + if(p->life <= 0) + { + b->owner->score ++; + + p->hearts[2]->src_rect.x = 0; + p->hearts[1]->src_rect.x = 0; + p->hearts[0]->src_rect.x = 0; + + update_score(); + } + } +} + +void init_player(struct Player *p, struct PlayerKeys *keys, + float x, float y, const char *name) +{ + struct Entity *e; + p->keys = *keys; + e = &p->entity; + + e->texture = load_texture("resources/cowboy.png"); + e->use_src_rect = true; + e->src_rect.x = 0; + e->src_rect.y = 0; + e->src_rect.w = 43; + e->src_rect.h = 51; + + e->i = 0; + + e->x = x; + e->y = y; + + e->collidable = true; + e->w = 43; + e->h = 55; + e->on_collision = player_on_collision; + + p->reloading = false; + p->on_ground = false; + p->current_gun = REVOLVER; + p->life = 12; + p->counter = 0; + + p->guns[0] = (struct Gun) + { + .type = REVOLVER, + .angle = 0.0f, + .projectiles_per_shot = 1, + .cooldown = 0.5f, + .ready = true, + .sound_effect = game->revolver_sound, + .bullets = 6, + .max_bullets = 6, + .damage = 2, + }; + + p->guns[1] = (struct Gun) + { + .type = RIFLE, + .angle = 0.0f, + .projectiles_per_shot = 1, + .cooldown = 1.0f, + .ready = true, + .sound_effect = game->rifle_sound, + .bullets = 5, + .max_bullets = 5, + .damage = 3, + }; + + p->guns[2] = (struct Gun) + { + .type = SHOTGUN, + .angle = 5.0f, + .projectiles_per_shot = 5, + .cooldown = 2.0f, + .ready = true, + .sound_effect = game->rifle_sound, + .bullets = 2, + .max_bullets = 2, + .damage = 1, + }; + + if(strlen(name) > 16) + die("Player's name is too long: %s", name); + + strcpy(p->name, name); +} + +void update_player(struct Player *p) +{ + struct PlayerKeys *k = &p->keys; + struct Gun *g = &p->guns[p->current_gun]; + float dt = game->frame_time; + + // Movement related code + if(game->keys[k->left]) + { + p->entity.dx = -300.0f; + p->entity.mirror = true; + + p->counter ++; + + if(p->counter % 5 == 0 && p->on_ground) + { + p->entity.i ++; + p->entity.i %= 5; + } + } + else if(game->keys[k->right]) + { + p->entity.dx = 300.0f; + p->entity.mirror = false; + + p->counter ++; + + if(p->counter % 10 == 0 && p->on_ground) + { + p->entity.i ++; + p->entity.i %= 5; + } + } + else + { + p->counter = 0; + p->entity.d2x = -18.0f * p->entity.dx; + + if(p->on_ground) + { + p->entity.i = 0; + } + } + + if(p->entity.y < game->height - p->entity.h) + { + p->entity.d2y = game->gravity; + } + else + { + p->on_ground = true; + p->entity.d2y = 0; + p->entity.dy = 0; + p->entity.y = (game->height - p->entity.h); + + if(p->entity.i == 5) + p->entity.i = 0; + } + if(game->keys[k->jump] + && p->on_ground) + { + p->entity.dy = -700.0f; + p->on_ground = false; + p->entity.i = 5; + } + + p->entity.x += p->entity.dx * dt + (0.5f * p->entity.d2x * dt * dt); + p->entity.y += p->entity.dy * dt + (0.5f * p->entity.d2y * dt * dt); + + p->entity.dx += p->entity.d2x * dt; + p->entity.dy += p->entity.d2y * dt; + + if(p->entity.x + p->entity.w > game->width) + p->entity.x = game->width - p->entity.w; + else if(p->entity.x < 0) + p->entity.x = 0; + + if(p->entity.y < 0) + p->entity.y = 0; + + // Guns related code + if(game->keys[k->weaponds[0]]) + { + p->current_gun = REVOLVER; + p->reloading = false; + p->reload_time = 0.0f; + + p->gun->texture = game->guns[0]; + SDL_QueryTexture(game->guns[0], NULL, NULL, &p->gun->rect.w, &p->gun->rect.h); + p->gun->rect.w *= 2; + p->gun->rect.h *= 2; + } + else if(game->keys[k->weaponds[1]]) + { + p->current_gun = RIFLE; + p->reloading = false; + p->reload_time = 0.0f; + + p->gun->texture = game->guns[1]; + SDL_QueryTexture(game->guns[1], NULL, NULL, &p->gun->rect.w, &p->gun->rect.h); + p->gun->rect.w *= 2; + p->gun->rect.h *= 2; + } + else if(game->keys[k->weaponds[2]]) + { + p->current_gun = SHOTGUN; + p->reloading = false; + p->reload_time = 0.0f; + + p->gun->texture = game->guns[2]; + SDL_QueryTexture(game->guns[2], NULL, NULL, &p->gun->rect.w, &p->gun->rect.h); + p->gun->rect.w *= 2; + p->gun->rect.h *= 2; + } + + // Check & Update gun's cooldown + if(!g->ready) + { + g->cooldown_elapsed += game->frame_time; + + if(g->cooldown_elapsed >= g->cooldown) + g->ready = true; + } + + if(p->reload_time > 0) + p->reload_time -= game->frame_time; + if(p->reload_time < 0) + p->reload_time = 0.0f; + + if(p->reloading && p->reload_time == 0.0f) + { + g->bullets = g->max_bullets; + p->reloading = false; + } + + if(game->keys[k->fire]) + shoot_bullet(p); + + if(game->keys[k->reload]) + { + p->reloading = true; + p->reload_time = reload_time; + } + + p->entity.src_rect.x = p->entity.i * 43; + + // Add to game's entity list + game->entities[game->num_entities ++] = &p->entity; +} diff --git a/render.c b/render.c new file mode 100644 index 0000000..d6f40ba --- /dev/null +++ b/render.c @@ -0,0 +1,64 @@ +#include "game.h" + +void render_begin(void) +{ + SDL_RenderClear(game->renderer); + if(game->state == RUNNING) + SDL_SetRenderDrawColor(game->renderer, 0, 255, 255, 255); + else + SDL_SetRenderDrawColor(game->renderer, 92, 51, 23, 255); +} + +void render_entities(void) +{ + int i; + SDL_Rect *src_rect; + SDL_Rect dst_rect; + struct Entity *entity; + + for(i = 0; i < game->num_entities; i ++) + { + entity = game->entities[i]; + + if(!entity) + continue; + + dst_rect.x = (int)roundf(entity->x) - game->camera.rect.x + WINDOW_WIDTH / 2; + dst_rect.y = (int)roundf(entity->y) - game->camera.rect.y + WINDOW_HEIGHT / 2; + + dst_rect.w = entity->w; + dst_rect.h = entity->h; + + // Render entities + src_rect = entity->use_src_rect ? &entity->src_rect : NULL; + + SDL_RenderCopyEx(game->renderer,entity->texture, src_rect, &dst_rect, 0, + NULL, entity->mirror ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE); + } +} + +void render_flush(void) +{ + int i; + + SDL_Rect *src_rect; + struct Layer *c_layer = &game->layers[game->current_layer]; + struct Widget *c_widget; + + // Render widgets + for(i = 0; i < c_layer->num_widgets; i ++) + { + c_widget = &c_layer->widgets[i]; + src_rect = c_widget->use_src_rect ? &c_widget->src_rect : NULL; + + SDL_SetTextureColorMod(c_widget->texture, c_widget->color.r, + c_widget->color.g, c_widget->color.b); + + SDL_RenderCopy(game->renderer, c_widget->texture, src_rect, &c_widget->rect); + } + + SDL_RenderPresent(game->renderer); + + // Empty vector + //c_layer->num_entities = 0; +} diff --git a/resources/block.png b/resources/block.png new file mode 100644 index 0000000..bd4a0c2 Binary files /dev/null and b/resources/block.png differ diff --git a/resources/blue.png b/resources/blue.png new file mode 100644 index 0000000..b7a014e Binary files /dev/null and b/resources/blue.png differ diff --git a/resources/bullet.png b/resources/bullet.png new file mode 100644 index 0000000..4b64035 Binary files /dev/null and b/resources/bullet.png differ diff --git a/resources/cowboy.png b/resources/cowboy.png new file mode 100644 index 0000000..7cbf9c0 Binary files /dev/null and b/resources/cowboy.png differ diff --git a/resources/font.ttf b/resources/font.ttf new file mode 100644 index 0000000..58cd6b5 Binary files /dev/null and b/resources/font.ttf differ diff --git a/resources/heart.png b/resources/heart.png new file mode 100644 index 0000000..710da11 Binary files /dev/null and b/resources/heart.png differ diff --git a/resources/heart.xcf b/resources/heart.xcf new file mode 100644 index 0000000..fff370a Binary files /dev/null and b/resources/heart.xcf differ diff --git a/resources/revolver.ogg b/resources/revolver.ogg new file mode 100644 index 0000000..2375d7e Binary files /dev/null and b/resources/revolver.ogg differ diff --git a/resources/revolver.png b/resources/revolver.png new file mode 100644 index 0000000..4efdbc1 Binary files /dev/null and b/resources/revolver.png differ diff --git a/resources/rifle.ogg b/resources/rifle.ogg new file mode 100644 index 0000000..e6ece77 Binary files /dev/null and b/resources/rifle.ogg differ diff --git a/resources/rifle.png b/resources/rifle.png new file mode 100644 index 0000000..a9379c0 Binary files /dev/null and b/resources/rifle.png differ diff --git a/resources/shotgun.png b/resources/shotgun.png new file mode 100644 index 0000000..5369841 Binary files /dev/null and b/resources/shotgun.png differ diff --git a/util.c b/util.c new file mode 100644 index 0000000..d91aff7 --- /dev/null +++ b/util.c @@ -0,0 +1,61 @@ +#include "game.h" +#include +#include + +SDL_Texture *load_texture(const char *name) +{ + int i; + SDL_Surface *surface; + SDL_Texture *texture; + + struct TextureContainer *container; + struct Resources *res = &game->resources; + + if(game->resources.num_textures >= MAX_TEXTURES) + return NULL; + + for(i = 0; i < res->num_stored_textures; i ++) + { + if(!strcmp(res->contained_textures[i].name, name)) + return res->contained_textures[i].texture; + } + + surface = IMG_Load(name); + texture = SDL_CreateTextureFromSurface(game->renderer, surface); + SDL_FreeSurface(surface); + + container = &res->contained_textures[res->num_stored_textures ++]; + strcpy(container->name, name); + container->texture = texture; + + res->textures[game->resources.num_textures ++] = texture; + + return texture; +} + +Mix_Chunk *load_sound(const char *name) +{ + Mix_Chunk *c; + struct Resources *res = &game->resources; + c = Mix_LoadWAV(name); + + if(!c) + die("%s could not be opened: %s", name, Mix_GetError()); + + res->audios[res->num_audios ++] = c; + + return c; +} + +void die(const char *error, ...) +{ + va_list args; + + va_start(args, error); + vfprintf(stderr, error, args); + va_end(args); + + free(game); + SDL_Quit(); + exit(1); +} -- cgit v1.2.3