aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Guillermo Albers Raviola <thomas@thomaslabs.org>2026-01-16 23:26:01 +0100
committerThomas Guillermo Albers Raviola <thomas@thomaslabs.org>2026-01-16 23:26:01 +0100
commitc15c603b35e86fd42e6db94a7382ba7a1ddadd6e (patch)
treeab56040ce4768564bef5fbe9e65fe4c025cea6ce
Initial commit
-rw-r--r--Makefile13
-rw-r--r--README.md10
-rw-r--r--bullet.c117
-rw-r--r--collisions.c130
-rw-r--r--config6
-rw-r--r--file.c42
-rw-r--r--game.h272
-rw-r--r--main.c265
-rw-r--r--manifest.scm6
-rw-r--r--menu.c179
-rw-r--r--player.c298
-rw-r--r--render.c64
-rw-r--r--resources/block.pngbin0 -> 343 bytes
-rw-r--r--resources/blue.pngbin0 -> 178 bytes
-rw-r--r--resources/bullet.pngbin0 -> 230 bytes
-rw-r--r--resources/cowboy.pngbin0 -> 1207 bytes
-rw-r--r--resources/font.ttfbin0 -> 65932 bytes
-rw-r--r--resources/heart.pngbin0 -> 389 bytes
-rw-r--r--resources/heart.xcfbin0 -> 1752 bytes
-rw-r--r--resources/revolver.oggbin0 -> 34476 bytes
-rw-r--r--resources/revolver.pngbin0 -> 492 bytes
-rw-r--r--resources/rifle.oggbin0 -> 65753 bytes
-rw-r--r--resources/rifle.pngbin0 -> 467 bytes
-rw-r--r--resources/shotgun.pngbin0 -> 463 bytes
-rw-r--r--util.c61
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);
+ }
+ }
+ }
+}
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 <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);
+}
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 <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
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 <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"))
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 <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
new file mode 100644
index 0000000..bd4a0c2
--- /dev/null
+++ b/resources/block.png
Binary files differ
diff --git a/resources/blue.png b/resources/blue.png
new file mode 100644
index 0000000..b7a014e
--- /dev/null
+++ b/resources/blue.png
Binary files differ
diff --git a/resources/bullet.png b/resources/bullet.png
new file mode 100644
index 0000000..4b64035
--- /dev/null
+++ b/resources/bullet.png
Binary files differ
diff --git a/resources/cowboy.png b/resources/cowboy.png
new file mode 100644
index 0000000..7cbf9c0
--- /dev/null
+++ b/resources/cowboy.png
Binary files differ
diff --git a/resources/font.ttf b/resources/font.ttf
new file mode 100644
index 0000000..58cd6b5
--- /dev/null
+++ b/resources/font.ttf
Binary files differ
diff --git a/resources/heart.png b/resources/heart.png
new file mode 100644
index 0000000..710da11
--- /dev/null
+++ b/resources/heart.png
Binary files differ
diff --git a/resources/heart.xcf b/resources/heart.xcf
new file mode 100644
index 0000000..fff370a
--- /dev/null
+++ b/resources/heart.xcf
Binary files differ
diff --git a/resources/revolver.ogg b/resources/revolver.ogg
new file mode 100644
index 0000000..2375d7e
--- /dev/null
+++ b/resources/revolver.ogg
Binary files differ
diff --git a/resources/revolver.png b/resources/revolver.png
new file mode 100644
index 0000000..4efdbc1
--- /dev/null
+++ b/resources/revolver.png
Binary files differ
diff --git a/resources/rifle.ogg b/resources/rifle.ogg
new file mode 100644
index 0000000..e6ece77
--- /dev/null
+++ b/resources/rifle.ogg
Binary files differ
diff --git a/resources/rifle.png b/resources/rifle.png
new file mode 100644
index 0000000..a9379c0
--- /dev/null
+++ b/resources/rifle.png
Binary files differ
diff --git a/resources/shotgun.png b/resources/shotgun.png
new file mode 100644
index 0000000..5369841
--- /dev/null
+++ b/resources/shotgun.png
Binary files 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 <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);
+}