Controls: WASD to move, Q to quit, Space to shoot.
-To build the loader and the plugins in the plugins/ folder, simply type `make` or `make all`. There are separate targets for the loader (`dungeon`) and the plugins. All Makefiles that are placed in plugin directories, so you might want to include a makefile in your plugin. The plugins target simply depends on ${PLUGINS}, so just add things to this in your plugin Makefile to add them to the plugins target (usually your plugin.so)
+To build the loader and the plugins in the plugins/ folder, simply type `make` or `make all`. There are separate targets for the loader (`dungeon`) and the plugins. All Makefiles that are placed in plugin directories, so you might want to include a makefile in your plugin. The plugins target simply depends on ${PLUGINS}, so just add things to this in your plugin Makefile to add them to the plugins target (usually your plugin.so)
To run the loader, type `./dungeon`. It will load all plugins including the game itself dynamically and run the game.
-Plugins are loaded in alphabethical order, with the exception of the game plugin that is loaded first. If you want to make a plugin that depends on another plugin, make sure the other plugin is loaded first by setting the name of your plugin accordingly. A cleaner solution to this is coming soon.
+If you want to make a plugin that depends on another plugin (including the game itself), make sure to depend on that plugin. To add dependencies to a plugin, create a file named dependencies.txt in the plugin folder. Put the names of all plugins your plugin depends on into that file. You can use spaces or newlines as seperators.
#include <dirent.h>
#include <string.h>
+struct plugin_list
+{
+ char *name;
+ void *handle;
+ struct plugin_list *next;
+};
+
+struct plugin_list *plugins = NULL;
+struct plugin_list **next = &plugins;
+
static void *load_plugin(const char *name)
{
+ for (struct plugin_list *ptr = plugins; ptr != NULL; ptr = ptr->next) {
+ if (strcmp(ptr->name, name) == 0)
+ return ptr->handle;
+ }
+
size_t len = strlen(name);
- char filename[1 + 1 + 7 + 1 + len + 1 + len + 1 + 2 + 1];
- sprintf(filename, "./plugins/%s/%s.so", name, name);
- void *plugin_handle = dlmopen(LM_ID_BASE, filename, RTLD_NOW | RTLD_GLOBAL);
+ char dependency_file_name[1 + 1 + 7 + 1 + len + 1 + 12 + 1 + 3 + 1];
+ sprintf(dependency_file_name, "./plugins/%s/dependencies.txt", name);
+
+ FILE *dependency_file = fopen(dependency_file_name, "r");
+
+ if (dependency_file) {
+ char dependency[BUFSIZ];
+
+ while (fscanf(dependency_file, "%s", dependency) != EOF)
+ load_plugin(dependency);
- if (! plugin_handle) {
+ fclose(dependency_file);
+ }
+
+ char library_name[1 + 1 + 7 + 1 + len + 1 + len + 1 + 2 + 1];
+ sprintf(library_name, "./plugins/%s/%s.so", name, name);
+
+ void *handle = dlmopen(LM_ID_BASE, library_name, RTLD_NOW | RTLD_GLOBAL);
+
+ if (! handle) {
printf("%s\n", dlerror());
exit(EXIT_FAILURE);
}
- return plugin_handle;
+ char *namebuf = malloc(len + 1);
+ strcpy(namebuf, name);
+
+ *next = malloc(sizeof(struct plugin_list));
+ **next = (struct plugin_list) {
+ .name = namebuf,
+ .handle = handle,
+ .next = NULL,
+ };
+ next = &(*next)->next;
+
+ printf("Loaded %s\n", name);
+
+ return handle;
}
int main()
struct dirent *dp;
- while (dp = readdir(dir)) {
- if (dp->d_name[0] != '.' && strcmp(dp->d_name, "game") != 0) {
+ while (dp = readdir(dir))
+ if (dp->d_name[0] != '.')
load_plugin(dp->d_name);
- }
- }
closedir(dir);
-plugins/apple/apple.so: plugins/apple/apple.c plugins/game/game.h
+plugins/apple/apple.so: plugins/apple/apple.c plugins/game/game.h plugins/score/score.h
cc -g -shared -fpic -o plugins/apple/apple.so plugins/apple/apple.c
PLUGINS := ${PLUGINS} plugins/apple/apple.so
#include <stddef.h>
#include <stdlib.h>
#include "../game/game.h"
-
-static struct entity apple;
+#include "../score/score.h"
static void apple_step(struct entity *self, struct entity_step_data stepdata)
{
}
}
+static struct entity apple_entity = {
+ .name = "apple",
+ .x = 0,
+ .y = 0,
+ .color = {0},
+ .use_color = false,
+ .texture = "🍎",
+ .remove = false,
+ .meta = NULL,
+ .health = 1,
+ .max_health = 1,
+ .collide_with_entities = false,
+
+ .on_step = &apple_step,
+ .on_collide = NULL,
+ .on_collide_with_entity = NULL,
+ .on_spawn = NULL,
+ .on_remove = NULL,
+ .on_death = NULL,
+ .on_damage = NULL,
+};
+
static void spawn_apple(int x, int y)
{
- spawn(apple, x, y, NULL);
+ spawn(apple_entity, x, y, NULL);
}
__attribute__((constructor)) static void init()
{
- apple = (struct entity) {
- .name = "apple",
- .x = 0,
- .y = 0,
- .color = get_color("#FF2A53"),
- .texture = "🍎",
- .remove = false,
- .meta = NULL,
- .health = 1,
- .max_health = 1,
- .collide_with_entities = false,
-
- .on_step = &apple_step,
- .on_collide = NULL,
- .on_collide_with_entity = NULL,
- .on_spawn = NULL,
- .on_remove = NULL,
- .on_death = NULL,
- .on_damage = NULL,
- };
-
register_air_function((struct generator_function) {
.chance = 25,
.callback = &spawn_apple,
--- /dev/null
+game
+score
--- /dev/null
+plugins/cherry/cherry.so: plugins/cherry/cherry.c plugins/game/game.h plugins/score/score.h plugins/inventory/inventory.h
+ cc -g -shared -fpic -o plugins/cherry/cherry.so plugins/cherry/cherry.c
+
+PLUGINS := ${PLUGINS} plugins/cherry/cherry.so
--- /dev/null
+#include <stddef.h>
+#include <stdlib.h>
+#include "../game/game.h"
+#include "../score/score.h"
+#include "../inventory/inventory.h"
+
+static bool use_cherry(struct itemstack *stack)
+{
+ (void) stack;
+
+ add_health(&player, 2);
+ return true;
+}
+
+static struct item cherry_item = {
+ .name = "Cherry",
+ .stackable = true,
+
+ .on_use = &use_cherry,
+ .on_destroy = NULL,
+ .on_create = NULL,
+};
+
+static void cherry_step(struct entity *self, struct entity_step_data stepdata)
+{
+ if (stepdata.dx == 0 && stepdata.dy == 0) {
+ add_score(2);
+ inventory_add(&player_inventory, (struct itemstack) {
+ .item = &cherry_item,
+ .count = 1,
+ .meta = NULL,
+ });
+ self->remove = true;
+ }
+}
+
+static struct entity cherry_entity = {
+ .name = "cherry",
+ .x = 0,
+ .y = 0,
+ .color = {0},
+ .use_color = false,
+ .texture = "🍒",
+ .remove = false,
+ .meta = NULL,
+ .health = 1,
+ .max_health = 1,
+ .collide_with_entities = false,
+
+ .on_step = &cherry_step,
+ .on_collide = NULL,
+ .on_collide_with_entity = NULL,
+ .on_spawn = NULL,
+ .on_remove = NULL,
+ .on_death = NULL,
+ .on_damage = NULL,
+};
+
+static void spawn_cherry(int x, int y)
+{
+ spawn(cherry_entity, x, y, NULL);
+}
+
+__attribute__((constructor)) static void init()
+{
+ register_air_function((struct generator_function) {
+ .chance = 100,
+ .callback = &spawn_cherry,
+ });
+}
+
--- /dev/null
+game
+score
+inventory
-plugins/fireball/fireball.so: plugins/fireball/fireball.c plugins/game/game.h
+plugins/fireball/fireball.so: plugins/fireball/fireball.c plugins/game/game.h plugins/movement/movement.h plugins/inventory/inventory.h
cc -g -shared -fpic -o plugins/fireball/fireball.so plugins/fireball/fireball.c
PLUGINS := ${PLUGINS} plugins/fireball/fireball.so
--- /dev/null
+game
+movement
+inventory
#include <stdlib.h>
#include <stddef.h>
#include "../game/game.h"
-
-static struct entity fireball;
+#include "../movement/movement.h"
+#include "../inventory/inventory.h"
struct fireball_data
{
static void fireball_collide_with_entity(struct entity *self, struct entity *other)
{
- add_health(other, -(1 + rand() % 3));
+ add_health(other, -(3 + rand() % 3));
self->remove = true;
}
-static bool try_shoot(int x, int y, int vx, int vy)
+static struct entity fireball_entity = {
+ .name = "fireball",
+ .x = 0,
+ .y = 0,
+ .color = {0},
+ .use_color = true,
+ .texture = "⬤ ",
+ .remove = false,
+ .meta = NULL,
+ .health = 1,
+ .max_health = 1,
+ .collide_with_entities = true,
+
+ .on_step = &fireball_step,
+ .on_collide = &fireball_collide,
+ .on_collide_with_entity = &fireball_collide_with_entity,
+ .on_spawn = &fireball_spawn,
+ .on_remove = NULL,
+ .on_death = NULL,
+ .on_damage = NULL,
+};
+
+static void shoot_fireball()
{
- x += vx;
- y += vy;
+ int vx, vy;
+ vx = vy = 0;
- return spawn(fireball, x, y, & (struct fireball_data) {
+ dir_to_xy(last_player_move, &vx, &vy);
+
+ spawn(fireball_entity, player.x + vx, player.y + vy, & (struct fireball_data) {
.timer = 0.1,
.vx = vx,
.vy = vy,
});
}
-static void shoot_fireball()
+static bool shoot_fireball_item(struct itemstack *stack)
{
- int x, y;
+ (void) stack;
- x = player.x;
- y = player.y;
-
- for (int tries = 10; tries > 0; tries--) {
- int vx, vy;
+ shoot_fireball();
+ return true;
+}
- vx = vy = 0;
+static struct item fireball_item = {
+ .name = "Fireball",
+ .stackable = true,
- dir_to_xy(rand() % 4, &vx, &vy);
+ .on_use = &shoot_fireball_item,
+ .on_destroy = NULL,
+ .on_create = NULL,
+};
- if (try_shoot(x, y, vx, vy))
- return;
- }
+static void shoot_if_has_fireball()
+{
+ if (inventory_remove(&player_inventory, &fireball_item))
+ shoot_fireball();
}
__attribute__((constructor)) static void init()
{
- fireball = (struct entity) {
- .name = "fireball",
- .x = 0,
- .y = 0,
- .color = get_color("#FF6611"),
- .texture = "⬤ ",
- .remove = false,
- .meta = NULL,
- .health = 1,
- .max_health = 1,
- .collide_with_entities = true,
-
- .on_step = &fireball_step,
- .on_collide = &fireball_collide,
- .on_collide_with_entity = &fireball_collide_with_entity,
- .on_spawn = &fireball_spawn,
- .on_remove = NULL,
- .on_death = NULL,
- .on_damage = NULL,
- };
+ fireball_entity.color = get_color("#FF6611");
register_input_handler(' ', (struct input_handler) {
.run_if_dead = false,
- .callback = &shoot_fireball,
+ .callback = &shoot_if_has_fireball,
+ });
+
+ inventory_add(&player_inventory, (struct itemstack) {
+ .item = &fireball_item,
+ .count = 7,
+ .meta = NULL,
});
}
#include <stdio.h>
-#include <stdbool.h>
#include <stdlib.h>
-#include <stddef.h>
#include <unistd.h>
#include <assert.h>
#include <ctype.h>
-#include <time.h>
#include <signal.h>
#include <termios.h>
-#include <sys/ioctl.h>
#include <math.h>
#include <pthread.h>
+#include <string.h>
#include "game.h"
-bool running = true;
-double damage_overlay = 0.0;
+/* Shared variables */
-int score = 0;
-
-struct color black = {0, 0, 0};
+struct color black = {0};
struct material wall;
struct material air;
struct node map[MAP_WIDTH][MAP_HEIGHT];
-struct entity player;
struct list *entities = & (struct list) {
.element = &player,
.next = NULL,
};
+/* Private variables */
+
struct entity *entity_collision_map[MAP_WIDTH][MAP_HEIGHT] = {{NULL}};
-struct list *air_functions = NULL;
+static bool running = true;
+static double damage_overlay = 0.0;
+static struct color damage_overlay_color;
-struct input_handler *input_handlers[256] = {NULL};
+static struct list *air_functions = NULL;
+static struct input_handler *input_handlers[256] = {NULL};
+static struct entity *render_entities[LIGHT * 2 + 1][LIGHT * 2 + 1];
+static struct list *render_components = NULL;
+static struct list *globalsteps = NULL;
-void quit()
-{
- running = false;
-}
+/* Helper functions */
struct color get_color(const char *str)
{
return (struct color) {r, g, b};
}
-bool is_outside(int x, int y)
+void set_color(struct color color, bool bg)
{
- return x >= MAP_WIDTH || x < 0 || y >= MAP_HEIGHT || y < 0;
+ printf("\e[%u;2;%u;%u;%um", bg ? 48 : 38, color.r, color.g, color.b);
}
-struct node get_node(int x, int y)
+void light_color(struct color *color, double light)
{
- return is_outside(x, y) ? (struct node) {&outside} : map[x][y];
+ color->r *= light;
+ color->g *= light;
+ color->b *= light;
}
-bool is_solid(int x, int y)
+void mix_color(struct color *color, struct color other, double ratio)
{
- return get_node(x, y).material->solid;
+ double ratio_total = ratio + 1;
+
+ color->r = (color->r + other.r * ratio) / ratio_total;
+ color->g = (color->g + other.g * ratio) / ratio_total;
+ color->b = (color->b + other.b * ratio) / ratio_total;
}
-bool move(struct entity *entity, int xoff, int yoff)
+void dir_to_xy(enum direction dir, int *x, int *y)
{
- int x, y;
+ switch (dir) {
+ case UP:
+ (*y)--;
+ break;
+ case LEFT:
+ (*x)--;
+ break;
+ case DOWN:
+ (*y)++;
+ break;
+ case RIGHT:
+ (*x)++;
+ break;
+ }
+}
- x = entity->x + xoff;
- y = entity->y + yoff;
+struct list *add_element(struct list *list, void *element)
+{
+ struct list **ptr;
- if (is_solid(x, y)) {
- if (entity->on_collide)
- entity->on_collide(entity, x, y);
- return false;
- } else if (entity->collide_with_entities && entity_collision_map[x][y]) {
- if (entity->on_collide_with_entity)
- entity->on_collide_with_entity(entity, entity_collision_map[x][y]);
- return false;
- } else {
- entity_collision_map[entity->x][entity->y] = NULL;
- entity->x = x;
- entity->y = y;
- entity_collision_map[entity->x][entity->y] = entity;
- return true;
- }
+ for (ptr = &list; *ptr != NULL; ptr = &(*ptr)->next)
+ ;
+
+ *ptr = malloc(sizeof(struct list));
+ (*ptr)->element = element;
+ (*ptr)->next = NULL;
+
+ return list;
+}
+
+int clamp(int v, int min, int max)
+{
+ return v < min ? min : v > max ? max : v;
+}
+
+int max(int a, int b)
+{
+ return a > b ? a : b;
+}
+
+int min(int a, int b)
+{
+ return a < b ? a : b;
+}
+
+void *make_buffer(void *ptr, size_t size)
+{
+ void *buf = malloc(size);
+ memcpy(buf, ptr, size);
+
+ return buf;
+}
+
+double calculate_dtime(struct timespec from, struct timespec to)
+{
+ return (double) (to.tv_sec - from.tv_sec) + (double) (to.tv_nsec - from.tv_nsec) / 1000000000.0;
}
+/* Game-related utility functions */
+
+void quit()
+{
+ running = false;
+}
+
+bool player_dead()
+{
+ return player.health <= 0;
+}
+
+/* Map functions */
+
+struct node get_node(int x, int y)
+{
+ return is_outside(x, y) ? (struct node) {&outside} : map[x][y];
+}
+
+bool is_outside(int x, int y)
+{
+ return x >= MAP_WIDTH || x < 0 || y >= MAP_HEIGHT || y < 0;
+}
+
+bool is_solid(int x, int y)
+{
+ return get_node(x, y).material->solid;
+}
+
+/* Entity functions */
+
bool spawn(struct entity def, int x, int y, void *data)
{
if (is_solid(x, y))
return true;
}
+bool move(struct entity *entity, int xoff, int yoff)
+{
+ int x, y;
+
+ x = entity->x + xoff;
+ y = entity->y + yoff;
+
+ if (is_solid(x, y)) {
+ if (entity->on_collide)
+ entity->on_collide(entity, x, y);
+ return false;
+ } else if (entity->collide_with_entities && entity_collision_map[x][y]) {
+ if (entity->on_collide_with_entity)
+ entity->on_collide_with_entity(entity, entity_collision_map[x][y]);
+ return false;
+ } else {
+ entity_collision_map[entity->x][entity->y] = NULL;
+ entity->x = x;
+ entity->y = y;
+ entity_collision_map[entity->x][entity->y] = entity;
+ return true;
+ }
+}
+
void add_health(struct entity *entity, int health)
{
bool was_alive = entity->health > 0;
entity->on_death(entity);
}
-void add_score(int s)
-{
- score += s;
-}
-
-bool player_dead()
-{
- return player.health <= 0;
-}
-
-struct list *add_element(struct list *list, void *element)
-{
- struct list **ptr;
-
- for (ptr = &list; *ptr != NULL; ptr = &(*ptr)->next)
- ;
-
- *ptr = malloc(sizeof(struct list));
- (*ptr)->element = element;
- (*ptr)->next = NULL;
-
- return list;
-}
+/* Register callback functions */
void register_air_function(struct generator_function func)
{
- struct generator_function *buf = malloc(sizeof(struct generator_function));
- *buf = func;
-
- air_functions = add_element(air_functions, buf);
+ air_functions = add_element(air_functions, make_buffer(&func, sizeof(struct generator_function)));
}
void register_input_handler(unsigned char c, struct input_handler handler)
if (input_handlers[c])
return;
- struct input_handler *buf = malloc(sizeof(struct input_handler));
- *buf = handler;
-
- input_handlers[c] = buf;
+ input_handlers[c] = make_buffer(&handler, sizeof(struct input_handler));
}
-void dir_to_xy(int dir, int *x, int *y)
+void register_render_component(void (*callback)(struct winsize ws))
{
- switch (dir) {
- case 0:
- (*x)++;
- break;
- case 1:
- (*y)++;
- break;
- case 2:
- (*x)--;
- break;
- case 3:
- (*y)--;
- break;
- }
-}
+ render_components = add_element(render_components, callback);
+};
-int clamp(int v, int min, int max)
+void register_globalstep(struct globalstep step)
{
- return v < min ? min : v > max ? max : v;
+ globalsteps = add_element(globalsteps, make_buffer(&step, sizeof(struct globalstep)));
}
/* Player */
damage_overlay += (double) damage * 0.5;
}
+struct entity player = {
+ .name = "player",
+ .x = MAP_WIDTH / 2,
+ .y = MAP_HEIGHT / 2,
+ .color = {0},
+ .use_color = false,
+ .texture = "🙂",
+ .remove = false,
+ .meta = NULL,
+ .health = 10,
+ .max_health = 10,
+ .collide_with_entities = true,
+
+ .on_step = NULL,
+ .on_collide = NULL,
+ .on_collide_with_entity = NULL,
+ .on_spawn = NULL,
+ .on_remove = NULL,
+ .on_death = &player_death,
+ .on_damage = &player_damage,
+};
+
/* Mapgen */
-static bool check_direction(int x, int y, int dir)
+static void mapgen_set_air(int x, int y)
+{
+ if (is_outside(x, y))
+ return;
+
+ if (map[x][y].material == &air)
+ return;
+
+ map[x][y] = (struct node) {&air};
+
+ for (struct list *ptr = air_functions; ptr != NULL; ptr = ptr->next) {
+ struct generator_function *func = ptr->element;
+
+ if (rand() % func->chance == 0)
+ func->callback(x, y);
+ }
+}
+
+static void generate_room(int origin_x, int origin_y)
+{
+ int left = 5 + rand() % 10;
+ int right = 5 + rand() % 10;
+
+ int up = 0;
+ int down = 0;
+
+ for (int x = -left; x <= right; x++) {
+ if (x < 0) {
+ up += rand() % 2;
+ down += rand() % 2;
+ } else {
+ up -= rand() % 2;
+ down -= rand() % 2;
+ }
+
+ for (int y = -up; y <= down; y++)
+ mapgen_set_air(origin_x + x, origin_y + y);
+ }
+}
+
+static bool check_direction(int x, int y, enum direction dir)
{
if (dir % 2 == 0)
- return is_solid(x, y + 1) && is_solid(x, y - 1) && (is_solid(x + 1, y) || rand() % 3 > 1) && (is_solid(x - 1, y) || rand() % 3 > 1);
- else
return is_solid(x + 1, y) && is_solid(x - 1, y) && (is_solid(x, y + 1) || rand() % 3 > 1) && (is_solid(x, y - 1) || rand() % 3 > 1);
+ else
+ return is_solid(x, y + 1) && is_solid(x, y - 1) && (is_solid(x + 1, y) || rand() % 3 > 1) && (is_solid(x - 1, y) || rand() % 3 > 1);
}
-static void generate_corridor(int lx, int ly, int ldir, bool off)
+static void generate_corridor(int lx, int ly, enum direction ldir)
{
if (is_outside(lx, ly))
return;
- /*
- if (off && rand() % 100 == 0)
+ if (rand() % 200 == 0) {
+ generate_room(lx, ly);
return;
- */
+ }
- map[lx][ly] = (struct node) {&air};
+ mapgen_set_air(lx, ly);
- for (struct list *ptr = air_functions; ptr != NULL; ptr = ptr->next) {
- struct generator_function *func = ptr->element;
+ int x, y;
- if (! func->chance || rand() % func->chance == 0) {
- func->callback(lx, ly);
- }
- }
+ enum direction dir;
+ enum direction ret = (ldir + 2) % 4;
- int x, y, dir;
- int ret = (ldir + 2) % 4;
int limit = 50;
do {
} while (dir == ret || (! check_direction(x, y, dir) && --limit));
if (limit)
- generate_corridor(x, y, dir, off);
+ generate_corridor(x, y, dir);
if (rand() % 20 == 0)
- generate_corridor(lx, ly, ldir, true);
+ generate_corridor(lx, ly, ldir);
}
static void generate_corridor_random(int x, int y)
{
- int dir = rand() % 4;
+ enum direction dir = rand() % 4;
- generate_corridor(x, y, dir, false);
- generate_corridor(x, y, (dir + 2) % 4, false);
+ generate_corridor(x, y, dir);
+ generate_corridor(x, y, (dir + 2) % 4);
}
/* Rendering */
-void set_color(struct color color, bool bg)
-{
- printf("\e[%u;2;%u;%u;%um", bg ? 48 : 38, color.r, color.g, color.b);
-}
-
-void light_color(struct color *color, double light)
-{
- color->r *= light;
- color->g *= light;
- color->b *= light;
-}
-
-void mix_color(struct color *color, struct color other, double ratio)
-{
- double ratio_total = ratio + 1;
-
- color->r = (color->r + other.r * ratio) / ratio_total;
- color->g = (color->g + other.g * ratio) / ratio_total;
- color->b = (color->b + other.b * ratio) / ratio_total;
-}
-
-static bool render_color(struct color color, double light, bool bg)
+static bool render_color(struct color color, double light, bool bg, bool use_color)
{
if (light <= 0.0) {
set_color(black, bg);
return false;
- } else {
+ } else if (use_color) {
if (damage_overlay > 0.0)
- mix_color(&color, get_color("#F20000"), damage_overlay * 2.0);
+ mix_color(&color, damage_overlay_color, damage_overlay * 2.0);
light_color(&color, light);
set_color(color, bg);
return true;
+ } else {
+ return true;
}
}
-static void render(render_entity_list entity_list)
+static void render_map(struct winsize ws)
{
- printf("\e[2J\e[0;0H");
-
- struct winsize ws;
- ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);
-
int cols = ws.ws_col / 2 - LIGHT * 2;
int rows = ws.ws_row / 2 - LIGHT;
int cols_left = ws.ws_col - cols - (LIGHT * 2 + 1) * 2;
int rows_left = ws.ws_row - rows - (LIGHT * 2 + 1);
- set_color(black, true);
-
for (int i = 0; i < rows; i++)
for (int i = 0; i < ws.ws_col; i++)
printf(" ");
double dist = sqrt(x * x + y * y);
double light = 1.0 - (double) dist / (double) LIGHT;
- render_color(node.material->color, light, true);
+ render_color(node.material->color, light, true, true);
- struct entity *entity = entity_list[x + LIGHT][y + LIGHT];
+ struct entity *entity = render_entities[x + LIGHT][y + LIGHT];
+ render_entities[x + LIGHT][y + LIGHT] = NULL;
- if (entity && render_color(entity->color, light, false))
+ if (entity && render_color(entity->color, light, false, entity->use_color))
printf("%s", entity->texture);
else
printf(" ");
printf(" ");
}
- for (int i = 0; i < rows_left + 1; i++)
+ for (int i = 0; i < rows_left; i++)
for (int i = 0; i < ws.ws_col; i++)
printf(" ");
+}
- printf("\e[0;0H\e[39m");
-
- printf("\e[32m\e[3mScore:\e[23m %d\e[39m", score);
-
- printf("\e[0;0");
-
- for (int i = 0; i < rows; i++)
- printf("\n");
-
- printf("\t\e[1mInventory\e[22m\n\n");
- printf("\t0x\t\e[3mNothing\e[23m\n");
-
- printf("\e[0;0H");
-
- for (int i = 0; i < ws.ws_row - 2; i++)
- printf("\n");
-
- int hearts_cols = ws.ws_col / 2 - player.max_health;
+static void render()
+{
+ printf("\e[2J");
- for (int i = 0; i < hearts_cols; i++)
- printf(" ");
+ struct winsize ws;
+ ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);
- set_color((struct color) {255, 0, 0}, false);
+ for (struct list *ptr = render_components; ptr != NULL; ptr = ptr->next) {
+ printf("\e[0m\e[0;0H");
+ set_color(black, true);
- for (int i = 0; i < player.max_health; i++) {
- if (i >= player.health)
- set_color(get_color("#5A5A5A"), false);
- printf("\u2665 ");
+ ((void (*)(struct winsize ws)) ptr->element)(ws);
}
- printf("\e[39m\n");
+ fflush(stdout);
}
/* Input */
-static void handle_input(unsigned char c)
+static void handle_interrupt(int signal)
{
- struct input_handler *handler = input_handlers[c];
+ (void) signal;
- if (handler && (handler->run_if_dead || ! player_dead()))
- handler->callback();
+ quit();
}
-/* Multithreading */
-
-static void handle_interrupt(int signal)
+static void handle_input(unsigned char c)
{
- (void) signal;
+ struct input_handler *handler = input_handlers[c];
- running = false;
+ if (handler && (handler->run_if_dead || ! player_dead()))
+ handler->callback();
}
static void *input_thread(void *unused)
/* Main Game */
-__attribute__ ((constructor)) static void init()
-{
- wall = (struct material) {
- .solid = true,
- .color = get_color("#5B2F00"),
- };
-
- air = (struct material) {
- .solid = false,
- .color = get_color("#FFE027"),
- };
-
- outside = (struct material) {
- .solid = true,
- .color = black,
- };
-
- player = (struct entity) {
- .name = "player",
- .x = MAP_WIDTH / 2,
- .y = MAP_HEIGHT / 2,
- .color = get_color("#00FFFF"),
- .texture = "🙂",
- .remove = false,
- .meta = NULL,
- .health = 10,
- .max_health = 10,
- .collide_with_entities = true,
-
- .on_step = NULL,
- .on_collide = NULL,
- .on_collide_with_entity = NULL,
- .on_spawn = NULL,
- .on_remove = NULL,
- .on_death = &player_death,
- .on_damage = &player_damage,
- };
-
- entity_collision_map[player.x][player.y] = &player;
-
- for (int x = 0; x < MAP_WIDTH; x++)
- for (int y = 0; y < MAP_HEIGHT; y++)
- map[x][y] = (struct node) {&wall};
-
- register_input_handler('q', (struct input_handler) {
- .run_if_dead = true,
- .callback = &quit,
- });
-}
-
void game()
{
- srand(time(0));
-
struct sigaction sa;
sa.sa_handler = &handle_interrupt;
sigaction(SIGINT, &sa, NULL);
while (running) {
clock_gettime(CLOCK_REALTIME, &ts);
- double dtime = (double) (ts.tv_sec - ts_old.tv_sec) + (double) (ts.tv_nsec - ts_old.tv_nsec) / 1000000000.0;
+ double dtime = calculate_dtime(ts_old, ts);
ts_old = ts;
bool dead = player_dead();
+ for (struct list *ptr = globalsteps; ptr != NULL; ptr = ptr->next) {
+ struct globalstep *step = ptr->element;
+
+ if (step->run_if_dead || ! dead)
+ step->callback(dtime);
+ }
+
if (! dead && damage_overlay > 0.0) {
damage_overlay -= dtime;
damage_overlay = 0.0;
}
- render_entity_list render_list = {{NULL}};
-
for (struct list **ptr = &entities; *ptr != NULL; ) {
struct entity *entity = (*ptr)->element;
bool visible = abs(dx) <= LIGHT && abs(dy) <= LIGHT;
if (visible)
- render_list[dx + LIGHT][dy + LIGHT] = entity;
+ render_entities[dx + LIGHT][dy + LIGHT] = entity;
if (! dead && entity->on_step)
entity->on_step(entity, (struct entity_step_data) {
ptr = &(*ptr)->next;
}
- render(render_list);
+ render();
// there is no such thing as glfwSwapBuffers, so we just wait 1 / 60 seconds to prevent artifacts
usleep(1000000 / 60);
tcsetattr(STDIN_FILENO, TCSANOW, &oldtio);
}
+/* Initializer function */
+
+__attribute__ ((constructor)) static void init()
+{
+ srand(time(0));
+
+ wall = (struct material) {
+ .solid = true,
+ .color = get_color("#5B2F00"),
+ };
+
+ air = (struct material) {
+ .solid = false,
+ .color = get_color("#FFE027"),
+ };
+
+ outside = (struct material) {
+ .solid = true,
+ .color = black,
+ };
+
+ entity_collision_map[player.x][player.y] = &player;
+
+ for (int x = 0; x < MAP_WIDTH; x++)
+ for (int y = 0; y < MAP_HEIGHT; y++)
+ map[x][y] = (struct node) {&wall};
+
+ register_input_handler('q', (struct input_handler) {
+ .run_if_dead = true,
+ .callback = &quit,
+ });
+
+ register_render_component(&render_map);
+
+ damage_overlay_color = get_color("#F20000");
+}
+
/* Use later */
/*
#define _GAME_H_
#include <stdbool.h>
+#include <sys/ioctl.h>
+#include <stddef.h>
+#include <time.h>
#define MAP_HEIGHT 1000
#define MAP_WIDTH 1000
#define LIGHT 10
struct entity
{
- const char *name;
+ char *name;
int x, y;
struct color color;
- const char *texture;
+ bool use_color;
+ char *texture;
bool remove;
void *meta;
int health;
struct list *next;
};
-typedef struct entity *render_entity_list[LIGHT * 2 + 1][LIGHT * 2 + 1];
-
struct generator_function
{
int chance;
void (*callback)();
};
-extern int score;
+struct globalstep
+{
+ bool run_if_dead;
+ void (*callback)(double dtime);
+};
+
+enum direction
+{
+ UP,
+ LEFT,
+ DOWN,
+ RIGHT,
+};
extern struct color black;
extern struct entity *entity_collision_map[MAP_WIDTH][MAP_HEIGHT];
-extern struct list *air_functions;
-
-extern struct input_handler *input_handlers[256];
+struct color get_color(const char *str);
+void set_color(struct color color, bool bg);
+void light_color(struct color *color, double light);
+void mix_color(struct color *color, struct color other, double ratio);
+void dir_to_xy(enum direction dir, int *x, int *y);
+struct list *add_element(struct list *list, void *element);
+int clamp(int v, int max, int min);
+int max(int a, int b);
+int min(int a, int b);
+void *make_buffer(void *ptr, size_t size);
+double calculate_dtime(struct timespec from, struct timespec to);
void quit();
-struct color get_color(const char *str);
-bool is_outside(int x, int y);
+bool player_dead();
+
struct node get_node(int x, int y);
+bool is_outside(int x, int y);
bool is_solid(int x, int y);
-bool move(struct entity *entity, int xoff, int yoff);
+
bool spawn(struct entity def, int x, int y, void *data);
+bool move(struct entity *entity, int xoff, int yoff);
void add_health(struct entity *entity, int health);
-void add_score(int s);
-bool player_dead();
-void set_color(struct color color, bool bg);
-void light_color(struct color *color, double light);
-void mix_color(struct color *color, struct color other, double ratio);
+
void register_air_function(struct generator_function func);
void register_input_handler(unsigned char c, struct input_handler handler);
-void dir_to_xy(int dir, int *x, int *y);
-int clamp(int v, int max, int min);
-struct list *add_element(struct list *list, void *element);
+void register_render_component(void (*callback)(struct winsize ws));
+void register_globalstep(struct globalstep step);
#endif
--- /dev/null
+plugins/healthbar/healthbar.so: plugins/healthbar/healthbar.c plugins/game/game.h
+ cc -g -shared -fpic -o plugins/healthbar/healthbar.so plugins/healthbar/healthbar.c
+
+PLUGINS := ${PLUGINS} plugins/healthbar/healthbar.so
--- /dev/null
+#include <stdio.h>
+#include "../game/game.h"
+
+static struct color red = {255, 0, 0};
+static struct color gray;
+
+static void render_healthbar(struct winsize ws)
+{
+ int y = max(ws.ws_row - 2, 0);
+ int x = max(ws.ws_col / 2 - player.max_health / 2, 0);
+
+ printf("\e[%u;%uH", y, x);
+
+ set_color(red, false);
+
+ int health = max(player.health, 0);
+
+ for (int i = 0; i < player.max_health; i++) {
+ if (i == health)
+ set_color(gray, false);
+ printf("♥ ");
+ }
+}
+
+__attribute__ ((constructor)) static void init()
+{
+ gray = get_color("#5A5A5A");
+
+ register_render_component(&render_healthbar);
+}
--- /dev/null
+plugins/inventory/inventory.so: plugins/inventory/inventory.c plugins/inventory/inventory.h plugins/game/game.h
+ cc -g -shared -fpic -o plugins/inventory/inventory.so plugins/inventory/inventory.c
+
+PLUGINS := ${PLUGINS} plugins/inventory/inventory.so
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include "../inventory/inventory.h"
+
+static struct color gray;
+static struct color darkgray;
+
+static bool use_item(struct itemstack *stack)
+{
+ (void) stack;
+ return true;
+}
+
+struct inventory player_inventory = {NULL};
+
+static int selected_index = 0;
+
+void inventory_add(struct inventory *self, struct itemstack stack)
+{
+ struct list **ptr = &self->stacks;
+
+ if (stack.item->stackable) {
+ for (; *ptr != NULL; ptr = &(*ptr)->next) {
+ struct itemstack *other = (*ptr)->element;
+ if (other->item == stack.item) {
+ other->count += stack.count;
+ return;
+ }
+ }
+ }
+
+ struct itemstack *buf = make_buffer(&stack, sizeof(struct itemstack));
+ *ptr = add_element(*ptr, buf);
+
+ if (buf->item->on_create)
+ buf->item->on_create(buf);
+}
+
+/*
+
+bool inventory_remove(struct inventory *self, struct itemstack *stack)
+{
+ stack.count = -stack.count;
+
+ for (struct list **ptr = &self->stacks; *ptr != NULL; ) {
+ struct itemstack *other = (*ptr)->element;
+
+ if (other->item == stack.item) {
+ stack.count += other->count;
+
+ if (stack.count > 0) {
+ other->count = stack.count;
+ return true;
+ } else {
+ struct list *next = ptr->next;
+
+ other->count = 0;
+
+ if (other->item->on_destroy)
+ other->item->on_destroy(other);
+
+ free(other);
+ free(*ptr);
+
+ *ptr = next;
+ continue;
+ }
+ }
+
+ ptr = &(*ptr)->next;
+ }
+
+ return false;
+}
+
+*/
+
+static void decrease_item_count(struct list **ptr, struct itemstack *stack)
+{
+ stack->count--;
+
+ if (stack->count == 0) {
+ struct list *next = (*ptr)->next;
+
+ if (stack->item->on_destroy)
+ stack->item->on_destroy(stack);
+
+ if (stack->meta)
+ free(stack->meta);
+
+ free(stack);
+ free(*ptr);
+
+ *ptr = next;
+ }
+}
+
+bool inventory_remove(struct inventory *self, struct item *item)
+{
+ for (struct list **ptr = &self->stacks; *ptr != NULL; ptr = &(*ptr)->next) {
+ struct itemstack *stack = (*ptr)->element;
+
+ if (stack->item == item) {
+ decrease_item_count(ptr, stack);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void handle_enter()
+{
+ int i = 0;
+
+ for (struct list **ptr = &player_inventory.stacks; *ptr != NULL; ptr = &(*ptr)->next, i++) {
+ if (i == selected_index) {
+ struct itemstack *stack = (*ptr)->element;
+
+ if (stack->item->on_use && stack->item->on_use(stack))
+ decrease_item_count(ptr, stack);
+
+ return;
+ }
+ }
+}
+
+static void handle_arrow()
+{
+ char c = fgetc(stdin);
+ if (c == '[') {
+ char dir = fgetc(stdin);
+
+ int count = 0;
+
+ for (struct list *ptr = player_inventory.stacks; ptr != NULL; ptr = ptr->next)
+ count++;
+
+ if (count == 0)
+ return;
+
+ switch (dir) {
+ case 'A':
+ selected_index--;
+
+ if (selected_index < 0)
+ selected_index = count - 1;
+ break;
+ case 'B':
+ selected_index++;
+
+ if (selected_index >= count)
+ selected_index = 0;
+
+ break;
+ }
+ } else {
+ ungetc(c, stdin);
+ }
+}
+
+static void render_inventory(struct winsize ws)
+{
+ printf("\e[3;0H");
+
+ printf(" \e[1mInventory\e[21m\n");
+
+ set_color(gray, false);
+
+ int i = 0;
+ for (struct list *ptr = player_inventory.stacks; ptr != NULL; ptr = ptr->next, i++) {
+ struct itemstack *stack = ptr->element;
+
+ if (i == selected_index) {
+ printf(" \e[39m→ ");
+ set_color(gray, false);
+ } else {
+ printf(" ");
+ }
+
+ printf("%s", stack->item->name);
+
+ if (stack->count > 1) {
+ set_color(darkgray, false);
+ printf(" (x%u)", stack->count);
+ set_color(gray, false);
+ }
+
+ printf("\n");
+ }
+}
+
+__attribute__ ((constructor)) static void init()
+{
+ gray = get_color("#9E9E9E");
+ darkgray = get_color("#555555");
+
+ register_render_component(&render_inventory);
+
+ register_input_handler('\033', (struct input_handler) {
+ .run_if_dead = false,
+ .callback = &handle_arrow,
+ });
+
+ register_input_handler('\n', (struct input_handler) {
+ .run_if_dead = false,
+ .callback = &handle_enter,
+ });
+}
--- /dev/null
+#ifndef _INVENTORY_H_
+#define _INVENTORY_H_
+#include "../game/game.h"
+
+struct itemstack
+{
+ struct item *item;
+ int count;
+ void *meta;
+};
+
+struct item
+{
+ char *name;
+ bool stackable;
+
+ bool (*on_use)(struct itemstack *stack);
+ void (*on_destroy)(struct itemstack *stack);
+ void (*on_create)(struct itemstack *stack);
+};
+
+struct inventory
+{
+ struct list *stacks;
+};
+
+void inventory_add(struct inventory *self, struct itemstack stack);
+bool inventory_remove(struct inventory *self, struct item *item);
+
+extern struct inventory player_inventory;
+#endif
-plugins/monster/monster.so: plugins/monster/monster.c plugins/game/game.h
+plugins/monster/monster.so: plugins/monster/monster.c plugins/game/game.h plugins/score/score.h
cc -g -shared -fpic -o plugins/monster/monster.so plugins/monster/monster.c
PLUGINS := ${PLUGINS} plugins/monster/monster.so
--- /dev/null
+game
+score
#include <stdlib.h>
#include <stddef.h>
#include "../game/game.h"
-
-static struct entity monster;
+#include "../score/score.h"
struct monster_data
{
self->remove = true;
}
+static struct entity monster_entity = {
+ .name = "monster",
+ .x = 0,
+ .y = 0,
+ .color = {0},
+ .use_color = false,
+ .texture = "👾",
+ .remove = false,
+ .meta = NULL,
+ .health = 5,
+ .max_health = 5,
+ .collide_with_entities = true,
+
+ .on_step = &monster_step,
+ .on_collide = NULL,
+ .on_collide_with_entity = &monster_collide_with_entity,
+ .on_spawn = &monster_spawn,
+ .on_remove = NULL,
+ .on_death = &monster_death,
+ .on_damage = NULL,
+};
+
+
static void spawn_monster(int x, int y)
{
- spawn(monster, x, y, NULL);
+ spawn(monster_entity, x, y, NULL);
}
__attribute__((constructor)) static void init()
{
- monster = (struct entity) {
- .name = "monster",
- .x = 0,
- .y = 0,
- .color = get_color("#FF00F6"),
- .texture = "👾",
- .remove = false,
- .meta = NULL,
- .health = 5,
- .max_health = 5,
- .collide_with_entities = true,
-
- .on_step = &monster_step,
- .on_collide = NULL,
- .on_collide_with_entity = &monster_collide_with_entity,
- .on_spawn = &monster_spawn,
- .on_remove = NULL,
- .on_death = &monster_death,
- .on_damage = NULL,
- };
-
register_air_function((struct generator_function) {
.chance = 50,
.callback = &spawn_monster,
-plugins/movement/movement.so: plugins/movement/movement.c plugins/game/game.h
+plugins/movement/movement.so: plugins/movement/movement.c plugins/movement/movement.h plugins/game/game.h
cc -g -shared -fpic -o plugins/movement/movement.so plugins/movement/movement.c
PLUGINS := ${PLUGINS} plugins/movement/movement.so
#include "../game/game.h"
+enum direction last_player_move;
+
+void move_player(enum direction dir)
+{
+ int x, y;
+ x = y = 0;
+
+ dir_to_xy(dir, &x, &y);
+ last_player_move = dir;
+
+ move(&player, x, y);
+}
+
static void move_up()
{
- move(&player, 0, -1);
+ move_player(UP);
}
static void move_left()
{
- move(&player, -1, 0);
+ move_player(LEFT);
}
static void move_down()
{
- move(&player, 0, 1);
+ move_player(DOWN);
}
static void move_right()
{
- move(&player, 1, 0);
+ move_player(RIGHT);
}
__attribute__((constructor)) static void init()
--- /dev/null
+#ifndef _MOVEMENT_H_
+#define _MOVEMENT_H_
+#include "../game/game.h"
+
+extern enum direction last_player_move;
+void move_player(enum direction dir);
+
+#endif
--- /dev/null
+plugins/score/score.so: plugins/score/score.c plugins/score/score.h plugins/game/game.h
+ cc -g -shared -fpic -o plugins/score/score.so plugins/score/score.c
+
+PLUGINS := ${PLUGINS} plugins/score/score.so
--- /dev/null
+#include <stdio.h>
+#include "../game/game.h"
+
+static int score = 0;
+
+void add_score(int s)
+{
+ score += s;
+}
+
+int get_score()
+{
+ return score;
+}
+
+static void render_score(struct winsize ws)
+{
+ (void) ws;
+
+ printf("\e[32m\e[3mScore:\e[23m %d", score);
+}
+
+__attribute__ ((constructor)) static void init()
+{
+ register_render_component(&render_score);
+}
--- /dev/null
+#ifndef _SCORE_H_
+#define _SCORE_H_
+
+void add_score(int s);
+int get_score();
+
+#endif
--- /dev/null
+plugins/sword/sword.so: plugins/sword/sword.c plugins/game/game.h plugins/movement/movement.h plugins/inventory/inventory.h
+ cc -g -shared -fpic -o plugins/sword/sword.so plugins/sword/sword.c
+
+PLUGINS := ${PLUGINS} plugins/sword/sword.so
--- /dev/null
+game
+movement
+inventory
--- /dev/null
+#include <stdlib.h>
+#include <stddef.h>
+#include "../game/game.h"
+#include "../movement/movement.h"
+#include "../inventory/inventory.h"
+
+static bool use_broken_sword(struct itemstack *stack)
+{
+ (void) stack;
+
+ return true;
+}
+
+static struct item broken_sword = {
+ .name = "Broken Sword",
+ .stackable = false,
+
+ .on_use = &use_broken_sword,
+ .on_destroy = NULL,
+ .on_create = NULL,
+};
+
+static bool use_sword(struct itemstack *stack)
+{
+ int x, y;
+ x = player.x;
+ y = player.y;
+
+ dir_to_xy(last_player_move, &x, &y);
+
+ struct entity *entity = entity_collision_map[x][y];
+
+ if (entity) {
+ add_health(entity, -1);
+
+ if (rand() % 100 == 0)
+ stack->item = &broken_sword;
+ }
+
+ return false;
+}
+
+static struct item sword = {
+ .name = "Sword",
+ .stackable = false,
+
+ .on_use = &use_sword,
+ .on_destroy = NULL,
+ .on_create = NULL,
+};
+
+__attribute__((constructor)) static void init()
+{
+ inventory_add(&player_inventory, (struct itemstack) {
+ .item = &sword,
+ .count = 1,
+ .meta = NULL,
+ });
+}