diff options
| author | Thomas Guillermo Albers Raviola <thomas@thomaslabs.org> | 2026-01-16 23:26:01 +0100 |
|---|---|---|
| committer | Thomas Guillermo Albers Raviola <thomas@thomaslabs.org> | 2026-01-16 23:26:01 +0100 |
| commit | c15c603b35e86fd42e6db94a7382ba7a1ddadd6e (patch) | |
| tree | ab56040ce4768564bef5fbe9e65fe4c025cea6ce | |
Initial commit
| -rw-r--r-- | Makefile | 13 | ||||
| -rw-r--r-- | README.md | 10 | ||||
| -rw-r--r-- | bullet.c | 117 | ||||
| -rw-r--r-- | collisions.c | 130 | ||||
| -rw-r--r-- | config | 6 | ||||
| -rw-r--r-- | file.c | 42 | ||||
| -rw-r--r-- | game.h | 272 | ||||
| -rw-r--r-- | main.c | 265 | ||||
| -rw-r--r-- | manifest.scm | 6 | ||||
| -rw-r--r-- | menu.c | 179 | ||||
| -rw-r--r-- | player.c | 298 | ||||
| -rw-r--r-- | render.c | 64 | ||||
| -rw-r--r-- | resources/block.png | bin | 0 -> 343 bytes | |||
| -rw-r--r-- | resources/blue.png | bin | 0 -> 178 bytes | |||
| -rw-r--r-- | resources/bullet.png | bin | 0 -> 230 bytes | |||
| -rw-r--r-- | resources/cowboy.png | bin | 0 -> 1207 bytes | |||
| -rw-r--r-- | resources/font.ttf | bin | 0 -> 65932 bytes | |||
| -rw-r--r-- | resources/heart.png | bin | 0 -> 389 bytes | |||
| -rw-r--r-- | resources/heart.xcf | bin | 0 -> 1752 bytes | |||
| -rw-r--r-- | resources/revolver.ogg | bin | 0 -> 34476 bytes | |||
| -rw-r--r-- | resources/revolver.png | bin | 0 -> 492 bytes | |||
| -rw-r--r-- | resources/rifle.ogg | bin | 0 -> 65753 bytes | |||
| -rw-r--r-- | resources/rifle.png | bin | 0 -> 467 bytes | |||
| -rw-r--r-- | resources/shotgun.png | bin | 0 -> 463 bytes | |||
| -rw-r--r-- | util.c | 61 |
25 files changed, 1463 insertions, 0 deletions
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 <time.h> + +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); + } + } + } +} @@ -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 @@ -0,0 +1,42 @@ +#include "game.h" +#include <stdio.h> + +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); +} @@ -0,0 +1,272 @@ +#ifndef __GAME_H__ +#define __GAME_H__ + +#include <SDL2/SDL.h> +#include <SDL2/SDL_image.h> +#include <SDL2/SDL_ttf.h> +#include <SDL2/SDL_mixer.h> + +#include <stdbool.h> + +#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 @@ -0,0 +1,265 @@ +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +#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")) @@ -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 <string.h> + +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 Binary files differnew file mode 100644 index 0000000..bd4a0c2 --- /dev/null +++ b/resources/block.png diff --git a/resources/blue.png b/resources/blue.png Binary files differnew file mode 100644 index 0000000..b7a014e --- /dev/null +++ b/resources/blue.png diff --git a/resources/bullet.png b/resources/bullet.png Binary files differnew file mode 100644 index 0000000..4b64035 --- /dev/null +++ b/resources/bullet.png diff --git a/resources/cowboy.png b/resources/cowboy.png Binary files differnew file mode 100644 index 0000000..7cbf9c0 --- /dev/null +++ b/resources/cowboy.png diff --git a/resources/font.ttf b/resources/font.ttf Binary files differnew file mode 100644 index 0000000..58cd6b5 --- /dev/null +++ b/resources/font.ttf diff --git a/resources/heart.png b/resources/heart.png Binary files differnew file mode 100644 index 0000000..710da11 --- /dev/null +++ b/resources/heart.png diff --git a/resources/heart.xcf b/resources/heart.xcf Binary files differnew file mode 100644 index 0000000..fff370a --- /dev/null +++ b/resources/heart.xcf diff --git a/resources/revolver.ogg b/resources/revolver.ogg Binary files differnew file mode 100644 index 0000000..2375d7e --- /dev/null +++ b/resources/revolver.ogg diff --git a/resources/revolver.png b/resources/revolver.png Binary files differnew file mode 100644 index 0000000..4efdbc1 --- /dev/null +++ b/resources/revolver.png diff --git a/resources/rifle.ogg b/resources/rifle.ogg Binary files differnew file mode 100644 index 0000000..e6ece77 --- /dev/null +++ b/resources/rifle.ogg diff --git a/resources/rifle.png b/resources/rifle.png Binary files differnew file mode 100644 index 0000000..a9379c0 --- /dev/null +++ b/resources/rifle.png diff --git a/resources/shotgun.png b/resources/shotgun.png Binary files differnew file mode 100644 index 0000000..5369841 --- /dev/null +++ b/resources/shotgun.png @@ -0,0 +1,61 @@ +#include "game.h" +#include <stdio.h> +#include <stdarg.h> + +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); +} |
