diff options
| author | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2021-07-03 00:49:03 +0200 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2022-03-30 08:20:20 +0200 |
| commit | 6b9f686913efc3725b2690033cd4f398e07076ba (patch) | |
| tree | e9aa91a6b9f617d78123ebe7ad272fc42a60d306 /src | |
| parent | c9e6ae44a9aeb89b3f48f3443d6baa80103f7445 (diff) | |
| download | jancity-6b9f686913efc3725b2690033cd4f398e07076ba.tar.gz | |
Add project source code
Diffstat (limited to 'src')
92 files changed, 5863 insertions, 0 deletions
diff --git a/src/building/CMakeLists.txt b/src/building/CMakeLists.txt new file mode 100644 index 0000000..3eb352e --- /dev/null +++ b/src/building/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(building "src/building.c") +target_include_directories(building PUBLIC "inc") +target_link_libraries(building PUBLIC gfx instance camera) diff --git a/src/building/inc/building.h b/src/building/inc/building.h new file mode 100644 index 0000000..7763b7d --- /dev/null +++ b/src/building/inc/building.h @@ -0,0 +1,41 @@ +#ifndef BUILDING_H +#define BUILDING_H + +#include <building_type.h> +#include <camera.h> +#include <gfx.h> +#include <instance.h> +#include <stdbool.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct building +{ + struct instance instance; + enum building_type type; +}; + +UTIL_STATIC_ASSERT(!offsetof(struct building, instance), "must be at offset zero"); + +struct building_cfg +{ + enum building_type type; + unsigned long x, y; +}; + +void building_create(const struct building_cfg *cfg, struct building *b); +int building_render(const struct building *b, const struct camera *cam, bool sel); +instance_hp building_maxhp(const struct building *b); +const char *building_str(const struct building *b); + +extern struct sprite building_sprites[MAX_BUILDING_TYPES]; + +#ifdef __cplusplus +} +#endif + +#endif /* BUILDING_H */ diff --git a/src/building/inc/building_type.h b/src/building/inc/building_type.h new file mode 100644 index 0000000..eca76b4 --- /dev/null +++ b/src/building/inc/building_type.h @@ -0,0 +1,11 @@ +#ifndef BUILDING_TYPE_H +#define BUILDING_TYPE_H + +enum building_type +{ + BUILDING_TYPE_BARRACKS, + + MAX_BUILDING_TYPES +}; + +#endif /* BUILDING_TYPE_H */ diff --git a/src/building/src/building.c b/src/building/src/building.c new file mode 100644 index 0000000..0b90370 --- /dev/null +++ b/src/building/src/building.c @@ -0,0 +1,80 @@ +#include <building.h> +#include <camera.h> +#include <gfx.h> +#include <stdbool.h> +#include <stddef.h> + +struct sprite building_sprites[MAX_BUILDING_TYPES]; + +instance_hp building_maxhp(const struct building *const b) +{ + static const instance_hp hp[] = + { + [BUILDING_TYPE_BARRACKS] = 100 + }; + + return hp[b->type]; +} + +int building_render(const struct building *const b, + const struct camera *const cam, const bool sel) +{ + if (!b->instance.alive) + return 0; + + struct sprite *const s = sprite_get(); + + if (!s || sprite_clone(&building_sprites[b->type], s)) + return -1; + + const struct instance_render_cfg cfg = + { + .i = &b->instance, + .prim_type = INSTANCE_RENDER_CFG_SPRITE, + .prim = {.s = s}, + .cam = cam, + .sel = sel, + .max_hp = building_maxhp(b) + }; + + return instance_render(&cfg); +} + +static void get_dimensions(const enum building_type type, short *const w, + short *const h) +{ + static const struct dim + { + short w, h; + } dim[] = + { + [BUILDING_TYPE_BARRACKS] = {.w = 68, .h = 66} + }; + + const struct dim *const d = &dim[type]; + *w = d->w; + *h = d->h; +} + +void building_create(const struct building_cfg *const cfg, + struct building *const b) +{ + struct instance *const i = &b->instance; + + get_dimensions(cfg->type, &i->r.w, &i->r.h); + b->type = cfg->type; + i->r.x = cfg->x; + i->r.y = cfg->y; + i->alive = true; + i->hp = building_maxhp(b); +} + +const char *building_str(const struct building *const b) +{ + static const char *const str[] = + { + [BUILDING_TYPE_BARRACKS] = "Barracks" + }; + + return str[b->type]; +} diff --git a/src/camera/CMakeLists.txt b/src/camera/CMakeLists.txt new file mode 100644 index 0000000..fd029b0 --- /dev/null +++ b/src/camera/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(camera "src/camera.c") +target_include_directories(camera PUBLIC "inc") +target_link_libraries(camera PUBLIC container pad util PRIVATE gfx terrain) diff --git a/src/camera/inc/camera.h b/src/camera/inc/camera.h new file mode 100644 index 0000000..1daebd2 --- /dev/null +++ b/src/camera/inc/camera.h @@ -0,0 +1,42 @@ +#ifndef CAMERA_H +#define CAMERA_H + +#include <pad.h> +#include <util.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct camera +{ + int x, y, x_speed, y_speed; + unsigned int xt, yt; + + struct cursor + { + unsigned int x, y, x_init, y_init; + enum + { + CURSOR_STATE_IDLE, + CURSOR_STATE_PRESSED + } state; + } cursor; +}; + +extern struct sprite cursor_sprite; + +void camera_update(struct camera *cam, const struct pad *p); +bool camera_translate(const struct camera *cam, const struct util_rect *dim, short *x, short *y); +void cursor_init(struct cursor *c); +bool cursor_collision(const struct camera *cam, const struct util_rect *d); +void cursor_pos(const struct camera *cam, unsigned long *x, unsigned long *y); +int cursor_render(const struct cursor *c); + +#ifdef __cplusplus +} +#endif + +#endif /* CAMERA_H */ diff --git a/src/camera/src/camera.c b/src/camera/src/camera.c new file mode 100644 index 0000000..4b046e1 --- /dev/null +++ b/src/camera/src/camera.c @@ -0,0 +1,203 @@ +#include <camera.h> +#include <gfx.h> +#include <pad.h> +#include <terrain.h> +#include <util.h> +#include <limits.h> +#include <stdbool.h> + +struct sprite cursor_sprite; + +enum +{ + CURSOR_WIDTH = 20, + CURSOR_HEIGHT = 20 +}; + +bool cursor_collision(const struct camera *const cam, const struct util_rect *const d) +{ + unsigned long x, y; + + cursor_pos(cam, &x, &y); + + const struct util_rect cd = + { + .x = x, + .y = y, + .w = CURSOR_WIDTH / 4, + .h = CURSOR_HEIGHT / 4 + }; + + return util_collision(d, &cd); +} + +void cursor_pos(const struct camera *const cam, unsigned long *const x, + unsigned long *const y) +{ + const struct cursor *const c = &cam->cursor; + + *x = c->x - cam->x; + *y = c->y - cam->y; +} + +static void cursor_update(struct camera *const cam, const struct pad *const p) +{ + struct cursor *const c = &cam->cursor; + enum {STEP = 4}; + + if (pad_pressed(p, PAD_KEY_LEFT) + && (c->x - STEP) + && (!cam->x || c->x != c->x_init)) + c->x -= STEP; + else if (pad_pressed(p, PAD_KEY_RIGHT) + && (c->x + STEP < screen_w) + && (c->x != c->x_init || cam->x <= -MAP_X)) + c->x += STEP; + + if (pad_pressed(p, PAD_KEY_UP) + && (c->y - STEP) + && (c->y != c->y_init || !cam->y)) + c->y -= STEP; + else if (pad_pressed(p, PAD_KEY_DOWN) + && (c->y + STEP < screen_h) + && (c->y != c->y_init || cam->y <= -MAP_Y)) + c->y += STEP; + + c->state = pad_pressed(p, PAD_KEY_A) || pad_pressed(p, PAD_KEY_B) ? + CURSOR_STATE_PRESSED: CURSOR_STATE_IDLE; +} + +int cursor_render(const struct cursor *const c) +{ + struct sprite *const s = sprite_get(); + + if (!s || sprite_clone(&cursor_sprite, s)) + return -1; + + s->x = c->x; + s->y = c->y; + s->w = CURSOR_WIDTH; + s->h = CURSOR_HEIGHT; + + switch (c->state) + { + case CURSOR_STATE_IDLE: + s->u += CURSOR_WIDTH; + break; + + case CURSOR_STATE_PRESSED: + break; + } + + sprite_sort(s); + return 0; +} + +void cursor_init(struct cursor *const c) +{ + c->x = c->x_init = (screen_w / 2) - CURSOR_WIDTH; + c->y = c->y_init = (screen_h / 2) - CURSOR_HEIGHT; +} + +static void update_speed(struct camera *const cam, const struct pad *const p) +{ + enum + { + MAX_SPEED = 10, + STEP = 1, + T_STEP = 3 + }; + + const struct cursor *const c = &cam->cursor; + + if (c->x == c->x_init + && (!cam->x_speed || ++cam->xt >= T_STEP)) + { + if (pad_pressed(p, PAD_KEY_RIGHT)) + { + if (cam->x_speed > 0) + cam->x_speed = -STEP; + else if (cam->x_speed - STEP >= -MAX_SPEED) + cam->x_speed -= STEP; + } + else if (pad_pressed(p, PAD_KEY_LEFT)) + { + if (cam->x_speed < 0) + cam->x_speed = STEP; + else if (cam->x_speed + STEP <= MAX_SPEED) + cam->x_speed += STEP; + } + else + cam->x_speed = 0; + + cam->xt = 0; + } + else if (c->x != c->x_init) + cam->x_speed = 0; + + if (c->y == c->y_init + && (!cam->y_speed || ++cam->yt >= T_STEP)) + { + if (pad_pressed(p, PAD_KEY_DOWN)) + { + if (cam->y_speed > 0) + cam->y_speed = STEP; + else if (cam->y_speed - STEP >= -MAX_SPEED) + cam->y_speed -= STEP; + } + else if (pad_pressed(p, PAD_KEY_UP)) + { + if (cam->y_speed < 0) + cam->y_speed = -STEP; + else if (cam->y_speed + STEP <= MAX_SPEED) + cam->y_speed += STEP; + } + else + cam->y_speed = 0; + + cam->yt = 0; + } + else if (c->y != c->y_init) + cam->y_speed = 0; +} + +static void update_pos(struct camera *const cam) +{ + const int x = cam->x + cam->x_speed; + + cam->x = x; + + if (cam->x > 0) + cam->x = 0; + else if (cam->x < -MAP_X) + cam->x = -MAP_X; + + const int y = cam->y + cam->y_speed; + + cam->y = y; + + if (cam->y > 0) + cam->y = 0; + else if (cam->y < -MAP_Y) + cam->y = -MAP_Y; +} + +void camera_update(struct camera *const cam, const struct pad *const p) +{ + cursor_update(cam, p); + update_speed(cam, p); + update_pos(cam); +} + +bool camera_translate(const struct camera *const cam, const struct util_rect *const dim, + short *const x, short *const y) +{ + const long tx = dim->x + cam->x, ty = dim->y + cam->y; + + if (!gfx_inside_drawenv(tx, ty, dim->w, dim->h)) + return false; + + *x = tx; + *y = ty; + return true; +} diff --git a/src/container/CMakeLists.txt b/src/container/CMakeLists.txt new file mode 100644 index 0000000..740fe37 --- /dev/null +++ b/src/container/CMakeLists.txt @@ -0,0 +1,11 @@ +set(inc "inc") + +if(PS1_BUILD) + set(inc ${inc} "ps1/inc") +elseif(SDL1_2_BUILD) + set(inc ${inc} "sdl-1.2/inc") +endif() + +add_library(container "src/container.c") +target_include_directories(container PUBLIC ${inc}) +target_link_libraries(container PUBLIC gfx sfx) diff --git a/src/container/inc/container.h b/src/container/inc/container.h new file mode 100644 index 0000000..59ab5fd --- /dev/null +++ b/src/container/inc/container.h @@ -0,0 +1,43 @@ +#ifndef CONTAINER_H +#define CONTAINER_H + +#include <container/port.h> +#include <gfx.h> +#include <sfx.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct container +{ + const char *path; + + enum + { + CONTAINER_TYPE_SPRITE, + CONTAINER_TYPE_SOUND + } type; + + union + { + struct sprite *sprite; + struct sound *sound; + } data; + + struct container_rt + { + struct container *c; + } *rt; +}; + +int container_load_ex(const char *path, const struct container *list, size_t n); +void container_free(const struct container *list, size_t n); + +#ifdef __cplusplus +} +#endif + +#endif /* CONTAINER_H */ diff --git a/src/container/ps1/inc/container/port.h b/src/container/ps1/inc/container/port.h new file mode 100644 index 0000000..aaf2f96 --- /dev/null +++ b/src/container/ps1/inc/container/port.h @@ -0,0 +1,6 @@ +#ifndef CONTAINER_PS1_H +#define CONTAINER_PS1_H + +#define container_load(path, list, n) container_load_ex("cdrom:\\"path";1", list, n) + +#endif /* CONTAINER_PS1_H */ diff --git a/src/container/sdl-1.2/inc/container/port.h b/src/container/sdl-1.2/inc/container/port.h new file mode 100644 index 0000000..b58697d --- /dev/null +++ b/src/container/sdl-1.2/inc/container/port.h @@ -0,0 +1,6 @@ +#ifndef CONTAINER_SDL_12_H +#define CONTAINER_SDL_12_H + +#define container_load(path, list, n) container_load_ex(path, list, n) + +#endif /* CONTAINER_SDL_12_H */ diff --git a/src/container/src/container.c b/src/container/src/container.c new file mode 100644 index 0000000..008ca58 --- /dev/null +++ b/src/container/src/container.c @@ -0,0 +1,225 @@ +#include <container.h> +#include <gfx.h> +#include <sfx.h> +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static int readstr(char *const str, const size_t n, FILE *const f) +{ + int ret = -1; + size_t i = 0; + + do + { + if (i >= n) + { + fprintf(stderr, "%s: string too long (> %zu bytes): %*.s\n", + __func__, n, (int)n, str); + goto end; + } + else if (!fread(&str[i], sizeof *str, 1, f)) + { + fprintf(stderr, "%s: expected null-terminated string\n", __func__); + goto end; + } + } while (str[i++]); + + ret = 0; + +end: + return ret; +} + +static int get_file_size(size_t *const sz, FILE *const f) +{ + char szstr[sizeof "2147483647"]; + + if (readstr(szstr, sizeof szstr, f)) + return -1; + + errno = 0; + const unsigned long val = strtoul(szstr, NULL, 10); + + if (errno) + { + fprintf(stderr, "%s: strtoul(3): %s\n", __func__, strerror(errno)); + return -1; + } + + *sz = val; + return 0; +} + +static int read_file_contents(const struct container *const el, FILE *const f, + const long init_off, const size_t sz) +{ + int ret = -1; + + switch (el->type) + { + case CONTAINER_TYPE_SPRITE: + if (sprite_from_fp(el->data.sprite, f)) + goto end; + + break; + + case CONTAINER_TYPE_SOUND: + if (sfx_sound_from_fp(el->data.sound, f)) + goto end; + + break; + } + + const long off = ftell(f); + + if (off < 0) + { + fprintf(stderr, "%s:%d: fseek failed: %s\n", + __func__, __LINE__, strerror(errno)); + goto end; + } + else if (off > init_off + sz) + { + fprintf(stderr, "%s: %s: detected read past file contents\n", + __func__, el->path); + goto end; + } + else if (off != init_off + sz) + { + fprintf(stderr, "only %ld bytes read, %ld expected\n", + off, init_off + sz); + goto end; + } + + ret = 0; + +end: + return ret; +} + +static const struct container *find_element(const struct container *const list, + const size_t n, FILE *const f) +{ + char name[128]; + + if (readstr(name, sizeof name, f)) + return NULL; + + for (size_t i = 0; i < n; i++) + { + const struct container *const el = &list[i]; + + if (!strcmp(el->path, name)) + return el; + } + + fprintf(stderr, "file %s not found on the list\n", name); + return NULL; +} + +static int read_element(const struct container *const list, const size_t n, + FILE *const f, bool *const done) +{ + int ret = -1; + long init_off; + const struct container *el = NULL; + size_t sz; + + if (!(el = find_element(list, n, f))) + goto end; + else if (get_file_size(&sz, f)) + goto end; + else if ((init_off = ftell(f)) < 0) + { + fprintf(stderr, "%s:%d: fseek failed: %s\n", + __func__, __LINE__, strerror(errno)); + goto end; + } + else if (read_file_contents(el, f, init_off, sz)) + goto end; + + done[el - list] = true; + ret = 0; + +end: + return ret; +} + +static int read_all_elements(const struct container *const list, const size_t n, + FILE *const f) +{ + int ret = -1; + /* VLAs are generally frowned upon, but we can safely assume + * 'n' is reasonably low. */ + bool done[n]; + + memset(&done, 0, sizeof done); + + for (size_t i = 0; i < sizeof done / sizeof *done; i++) + if (feof(f) || ferror(f) + || read_element(list, n, f, done)) + goto end; + + for (size_t i = 0; i < sizeof done / sizeof *done; i++) + { + if (!done[i]) + { + fprintf(stderr, "%s: %s not found inside container file\n", + __func__, list[i].path); + goto end; + } + } + + ret = 0; + +end: + return ret; +} + +void container_free(const struct container *const list, const size_t n) +{ + if (!list) + return; + + for (size_t i = 0; i < n; i++) + { + const struct container *const el = &list[i]; + + switch (el->type) + { + case CONTAINER_TYPE_SPRITE: + sprite_free(el->data.sprite); + break; + + case CONTAINER_TYPE_SOUND: + sfx_free(el->data.sound); + break; + } + } +} + +int container_load_ex(const char *const path, const struct container *const list, + const size_t n) +{ + int ret = -1; + FILE *f = NULL; + + if (!(f = fopen(path, "rb"))) + { + fprintf(stderr, "could not open %s: %s\n", path, strerror(errno)); + goto end; + } + else if (read_all_elements(list, n, f)) + goto end; + + ret = 0; + +end: + if (f) + fclose(f); + + return ret; +} diff --git a/src/font/CMakeLists.txt b/src/font/CMakeLists.txt new file mode 100644 index 0000000..46336ef --- /dev/null +++ b/src/font/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(font "src/font.c") +target_include_directories(font PUBLIC "inc") +target_link_libraries(font PUBLIC container PRIVATE gfx) diff --git a/src/font/inc/font.h b/src/font/inc/font.h new file mode 100644 index 0000000..2d8bd2f --- /dev/null +++ b/src/font/inc/font.h @@ -0,0 +1,22 @@ +#ifndef FONT_H +#define FONT_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum font +{ + FONT +}; + +int font_printf(enum font f, short x, short y, const char *fmt, ...); + +extern struct sprite font_sprite; + +#ifdef __cplusplus +} +#endif + +#endif /* FONT_H */ diff --git a/src/font/src/font.c b/src/font/src/font.c new file mode 100644 index 0000000..a6a28d3 --- /dev/null +++ b/src/font/src/font.c @@ -0,0 +1,110 @@ +#include <font.h> +#include <gfx.h> +#include <stdarg.h> +#include <stdio.h> + +struct sprite font_sprite; + +static int renderstr(const enum font f, const short x, short y, const char *str) +{ + static const struct cfg + { + const struct sprite *s; + short fw, fh, fs; + } cfgs[] = + { + [FONT] = + { + .s = &font_sprite, + .fw = 12, + .fh = 14, + .fs = 8 + } + }; + + const struct cfg *const cfg = &cfgs[f]; + char c; + short rx = x; + + while ((c = *str++)) + { + if (c == ' ') + { + rx += cfg->fs; + continue; + } + else if (c == '\n' || c == '\r') + { + rx = x; + y += cfg->fh; + continue; + } + + struct sprite *const s = sprite_get(); + + if (!s || sprite_clone(cfg->s, s)) + return -1; + + s->w = cfg->fw; + s->h = cfg->fh; + + /* Substract non-printable characters (NUL to SP). */ + const char ch = c - '!'; + const short u = (cfg->fw * ch) % cfg->s->w; + const short v = cfg->fh * ((cfg->fw * ch) / cfg->s->w); + + s->u += u; + s->v += v; + s->x = rx; + s->y = y; + sprite_sort(s); + rx += cfg->fs; + } + + return 0; +} + +int font_printf(const enum font f, const short x, const short y, + const char *const fmt, ...) +{ + va_list ap, aq, at; + + va_start(ap, fmt); + va_copy(aq, ap); + va_copy(at, ap); + + /* Short string optimization. */ + char defstr[64]; + int sz = vsnprintf(defstr, sizeof defstr, fmt, ap); + + if (sz < 0) + goto end; + else if (sz >= sizeof defstr) + { + va_list at; + + sz = vsnprintf(NULL, 0, fmt, ap); + + if (sz < 0) + goto end; + + /* VLAs are generally frowned upon, but using the heap + * might not be a good idea for a video game like this. */ + char str[sz + 1]; + + sz = vsnprintf(str, sizeof str, fmt, ap); + va_end(at); + + if (sz < 0) + goto end; + } + + if (renderstr(f, x, y, defstr)) + return -1; + +end: + va_end(at); + va_end(aq); + va_end(ap); + return sz; +} diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt new file mode 100644 index 0000000..0c21b0c --- /dev/null +++ b/src/game/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(game "src/game.c" "src/res.c") +target_include_directories(game PUBLIC "inc" PRIVATE "privinc") +target_link_libraries(game PRIVATE + building + container + font + gfx + gui + instance + pad + player + resource + system + terrain + unit) diff --git a/src/game/inc/game.h b/src/game/inc/game.h new file mode 100644 index 0000000..8275d54 --- /dev/null +++ b/src/game/inc/game.h @@ -0,0 +1,15 @@ +#ifndef GAME_H +#define GAME_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +int game(void); + +#ifdef __cplusplus +} +#endif + +#endif /* GAME_H */ diff --git a/src/game/privinc/game_private.h b/src/game/privinc/game_private.h new file mode 100644 index 0000000..1b9449f --- /dev/null +++ b/src/game/privinc/game_private.h @@ -0,0 +1,16 @@ +#ifndef GAME_PRIVATE_H +#define GAME_PRIVATE_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +int game_resinit(void); +void game_free(void); + +#ifdef __cplusplus +} +#endif + +#endif /* GAME_PRIVATE_H */ diff --git a/src/game/src/game.c b/src/game/src/game.c new file mode 100644 index 0000000..2f13a64 --- /dev/null +++ b/src/game/src/game.c @@ -0,0 +1,101 @@ +#include <game.h> +#include <game_private.h> +#include <gfx.h> +#include <human_player.h> +#include <player.h> +#include <resource.h> +#include <system.h> +#include <terrain.h> +#include <stddef.h> + +int game(void) +{ + int ret = -1; + + if (game_resinit()) + goto end; + + enum {HUMAN_PLAYERS = 1, MAP_RESOURCES = 3}; + struct human_player humans[HUMAN_PLAYERS]; + + for (size_t i = 0; i < sizeof humans / sizeof *humans; i++) + { + const struct human_player_cfg cfg = + { + .padn = i, + .pl = + { + .team = PLAYER_COLOR_BLUE, + .x = 80, + .y = 40 + } + }; + + if (human_player_init(&cfg, &humans[i])) + goto end; + } + + struct terrain_map map; + struct resource res[MAP_RESOURCES] = {0}; + + terrain_init(&map); +#if 1 + if (resource_create(&(const struct resource_cfg) + { + .type = RESOURCE_TYPE_GOLD, + .x = 50, + .y = 200 + }, res, sizeof res / sizeof *res) + || resource_create(&(const struct resource_cfg) + { + .type = RESOURCE_TYPE_WOOD, + .x = 180, + .y = 200 + }, res, sizeof res / sizeof *res) + || resource_create(&(const struct resource_cfg) + { + .type = RESOURCE_TYPE_WOOD, + .x = 240, + .y = 200 + }, res, sizeof res / sizeof *res)) + goto end; +#endif + bool exit = false; + + while (!exit) + { + system_loop(); + + for (size_t i = 0; i < sizeof humans / sizeof *humans; i++) + { + const struct human_player *const h = &humans[i]; + + if (h->pl.alive) + { + struct player_others o = + { + .res = res, + .n_res = sizeof res / sizeof *res + }; + + exit |= human_player_update(&humans[i], &o); + + if (terrain_render(&map, &h->cam) + || human_player_render(h, &o) + || cursor_render(&h->cam.cursor)) + goto end; + + /* TODO: render AI players. */ + } + } + + instance_cyclic(); + gfx_draw(); + } + + ret = 0; + +end: + game_free(); + return ret; +} diff --git a/src/game/src/res.c b/src/game/src/res.c new file mode 100644 index 0000000..39b3b5e --- /dev/null +++ b/src/game/src/res.c @@ -0,0 +1,247 @@ +#include <game_private.h> +#include <building.h> +#include <container.h> +#include <font.h> +#include <gfx.h> +#include <gui.h> +#include <resource.h> +#include <terrain.h> +#include <unit.h> +#include <stdbool.h> + +static const struct container c[] = +{ + { + .path = "barracks", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &building_sprites[BUILDING_TYPE_BARRACKS] + } + }, + + { + .path = "worker_n", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &unit_sprites[UNIT_SPRITE_N] + } + }, + + { + .path = "worker_ne", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &unit_sprites[UNIT_SPRITE_NE] + } + }, + + { + .path = "worker_e", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &unit_sprites[UNIT_SPRITE_E] + } + }, + + { + .path = "worker_se", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &unit_sprites[UNIT_SPRITE_SE] + } + }, + + { + .path = "worker_s", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &unit_sprites[UNIT_SPRITE_S] + } + }, + + { + .path = "grass", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &grass_sprite + } + }, + + { + .path = "cursor", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &cursor_sprite + } + }, + + { + .path = "gui_bar_left", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &gui_sprites[GUI_BAR_LEFT] + } + }, + + { + .path = "gui_bar_mid", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &gui_sprites[GUI_BAR_MID] + } + }, + + { + .path = "gui_bar_right", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &gui_sprites[GUI_BAR_RIGHT] + } + }, + + { + .path = "sel_up_left", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &gui_sprites[GUI_SELECTION_UP_LEFT] + } + }, + + { + .path = "sel_up_right", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &gui_sprites[GUI_SELECTION_UP_RIGHT] + } + }, + + { + .path = "sel_down_left", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &gui_sprites[GUI_SELECTION_DOWN_LEFT] + } + }, + + { + .path = "sel_down_right", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &gui_sprites[GUI_SELECTION_DOWN_RIGHT] + } + }, + + { + .path = "sel_mid", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &gui_sprites[GUI_SELECTION_MID] + } + }, + + { + .path = "sel_mid_v", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &gui_sprites[GUI_SELECTION_MID_VERT] + } + }, + + { + .path = "font", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &font_sprite + } + }, + + { + .path = "gold_mine", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &resource_sprites[RESOURCE_TYPE_GOLD] + } + }, + + { + .path = "tree", + .type = CONTAINER_TYPE_SPRITE, + .data = + { + .sprite = &resource_sprites[RESOURCE_TYPE_WOOD] + } + }, + + { + .path = "acknowledge_01", + .type = CONTAINER_TYPE_SOUND, + .data = + { + .sound = &unit_sounds[UNIT_SOUND_MOVE] + } + }, + + { + .path = "acknowledge_02", + .type = CONTAINER_TYPE_SOUND, + .data = + { + .sound = &unit_sounds[UNIT_SOUND_MOVE_2] + } + }, + + { + .path = "selected_01", + .type = CONTAINER_TYPE_SOUND, + .data = + { + .sound = &unit_sounds[UNIT_SOUND_SELECTED] + } + } +}; + +static bool init; + +void game_free(void) +{ + if (init) + { + container_free(c, sizeof c / sizeof *c); + init = false; + } +} + +int game_resinit(void) +{ + if (!init) + { + if (container_load("rts.cnt", c, sizeof c / sizeof *c)) + { + perror("container_load"); + return -1; + } + + init = true; + } + + return 0; +} diff --git a/src/gfx/CMakeLists.txt b/src/gfx/CMakeLists.txt new file mode 100644 index 0000000..c449be5 --- /dev/null +++ b/src/gfx/CMakeLists.txt @@ -0,0 +1,54 @@ +set(src "src/heap.c") +set(inc "inc") +set(privinc "privinc") +set(deps util) + +if(PS1_BUILD) + set(src ${src} + "ps1/src/4line.c" + "ps1/src/env.c" + "ps1/src/heap.c" + "ps1/src/init.c" + "ps1/src/rect.c" + "ps1/src/sort.c" + "ps1/src/sprite.c" + "ps1/src/quad.c") + set(inc ${inc} "ps1/inc") + set(privinc ${privinc} "ps1/privinc") + set(privdeps ${privdeps} system) +elseif(SDL1_2_BUILD) + set(inc ${inc} "sdl-1.2/inc") + set(privinc ${privinc} "sdl-1.2/privinc") + set(src ${src} + "sdl-1.2/src/env.c" + "sdl-1.2/src/heap.c" + "sdl-1.2/src/line.c" + "sdl-1.2/src/rect.c" + "sdl-1.2/src/sort.c" + "sdl-1.2/src/sprite.c" + "sdl-1.2/src/quad.c") + set(deps ${deps} SDL) +endif() + +add_library(gfx ${src}) +target_include_directories(gfx PUBLIC ${inc}) +target_include_directories(gfx PRIVATE ${privinc}) +target_link_libraries(gfx PUBLIC ${deps} PRIVATE ${privdeps}) + +if(PS1_BUILD) + set(modes VMODE_PAL VMODE_NTSC) + + if(VIDEO_MODE) + if(NOT "${VIDEO_MODE}" IN_LIST modes) + message(FATAL_ERROR "Invalid video mode ${VIDEO_MODE}. Available options:\n" + "${modes}\n" + "Run CMake again using one of the available video modes e.g.: cmake .. -DVIDEO_MODE=VMODE_PAL") + endif() + + target_compile_definitions(gfx PRIVATE VIDEO_MODE=${VIDEO_MODE}) + else() + message(FATAL_ERROR "Please define video mode. Available options:\n" + "${modes}\n" + "Run CMake again using one of the available video modes e.g.: cmake .. -DVIDEO_MODE=VMODE_PAL") + endif() +endif() diff --git a/src/gfx/inc/gfx.h b/src/gfx/inc/gfx.h new file mode 100644 index 0000000..0c30911 --- /dev/null +++ b/src/gfx/inc/gfx.h @@ -0,0 +1,37 @@ +#ifndef GFX_H +#define GFX_H + +#include <gfx/port.h> +#include <stdbool.h> +#include <stdio.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +int gfx_init(void); +void gfx_draw(void); +void gfx_sync(void); +void sprite_sort(struct sprite *s); +int sprite_clone(const struct sprite *src, struct sprite *dst); +void quad_sort(struct quad *q); +void rect_sort(struct rect *r); +void stp_4line_sort(struct stp_4line *l); +int sprite_from_fp(struct sprite *s, FILE *f); +struct sprite *sprite_get(void); +struct quad *quad_get(void); +struct rect *rect_get(bool semitrans); +struct stp_4line *stp_4line_get(void); +int quad_from_sprite(const struct sprite *s, struct quad *q); +bool gfx_inside_drawenv(short x, short y, short w, short h); +void gfx_deinit(void); +void sprite_free(struct sprite *src); + +extern int screen_w, screen_h; + +#ifdef __cplusplus +} +#endif + +#endif /* GFX_H */ diff --git a/src/gfx/privinc/gfx_private.h b/src/gfx/privinc/gfx_private.h new file mode 100644 index 0000000..0553362 --- /dev/null +++ b/src/gfx/privinc/gfx_private.h @@ -0,0 +1,22 @@ +#ifndef GFX_PRIVATE_H +#define GFX_PRIVATE_H + +#include <gfx.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +void rect_init(struct rect *r); +void semitrans_rect_init(struct rect *r); +void stp_4line_init(struct stp_4line *l); +void *gfx_port_heap(size_t *n); +void gfx_reset_heap(void); + +#ifdef __cplusplus +} +#endif + +#endif /* GFX_PRIVATE_H */ diff --git a/src/gfx/ps1/inc/gfx/port.h b/src/gfx/ps1/inc/gfx/port.h new file mode 100644 index 0000000..dec00c3 --- /dev/null +++ b/src/gfx/ps1/inc/gfx/port.h @@ -0,0 +1,142 @@ +#ifndef GFX_PS1_H +#define GFX_PS1_H + +#include <util.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* 0-3 Texture page X Base (N*64) (ie. in 64-halfword steps) ;GPUSTAT.0-3 + 4 Texture page Y Base (N*256) (ie. 0 or 256) ;GPUSTAT.4 + 5-6 Semi Transparency (0=B/2+F/2, 1=B+F, 2=B-F, 3=B+F/4) ;GPUSTAT.5-6 + 7-8 Texture page colors (0=4bit, 1=8bit, 2=15bit, 3=Reserved);GPUSTAT.7-8 + 9 Dither 24bit to 15bit (0=Off/strip LSBs, 1=Dither Enabled) ;GPUSTAT.9 + 10 Drawing to display area (0=Prohibited, 1=Allowed) ;GPUSTAT.10 + 11 Texture Disable (0=Normal, 1=Disable if GP1(09h).Bit0=1) ;GPUSTAT.15 + (Above might be chipselect for (absent) second VRAM chip?) + 12 Textured Rectangle X-Flip (BIOS does set this bit on power-up...?) + 13 Textured Rectangle Y-Flip (BIOS does set it equal to GPUSTAT.13...?) + 14-23 Not used (should be 0) + 24-31 Command (E1h)*/ +union gfx_common +{ + struct + { + uint32_t tpagex :4; + uint32_t tpagey :1; + uint32_t stp :2; + uint32_t bpp :2; + uint32_t dither :1; + uint32_t draw_to_disp :1; + uint32_t disable :1; + uint32_t xflip :1; + uint32_t yflip :1; + uint32_t :10; + uint8_t cmd; + } f; + + uint32_t mask; +}; + +union gfx_sznext +{ + struct + { + uint32_t next :24; + uint32_t sz :8; + } f; + + uint32_t cmd_next; +}; + +struct sprite +{ + union gfx_sznext sznext; + union gfx_common common; + uint8_t r, g, b; + uint8_t cmd; + int16_t x, y; + uint8_t u, v; + uint16_t clutid; + uint16_t w, h; +}; + +struct quad +{ + union gfx_sznext sznext; + uint8_t r, g, b; + uint8_t cmd; + int16_t x0, y0; + uint8_t u0, v0; + uint16_t clutid; + int16_t x1, y1; + uint8_t u1, v1; + + union + { + struct + { + /* 0-8 Same as GP0(E1h).Bit0-8. */ + uint16_t lb :9; + uint16_t :2; + /* 11 Same as GP0(E1h).Bit11. */ + uint16_t hb :1; + uint16_t :4; + } bit; + + uint16_t mask; + } tpage; + + int16_t x2, y2; + uint8_t u2, v2; + uint16_t :16; + int16_t x3, y3; + uint8_t u3, v3; + uint16_t :16; +}; + +struct rect +{ + union gfx_sznext sznext; + union gfx_common common; + uint8_t r, g, b; + uint8_t cmd; + int16_t x, y; + uint16_t w, h; +}; + +struct stp_4line +{ + union gfx_sznext sznext; + union gfx_common common; + uint8_t r, g, b; + uint8_t cmd; + int16_t x, y; + + struct stp_4line_vtx + { + uint32_t r :8; + uint32_t g :8; + uint32_t b :8; + uint32_t :8; + + int16_t x, y; + } vertices[4]; + + uint32_t end; +}; + +enum {DRAW_MODE = 0xE1}; + +int sprite_from_file_ex(const char *path, struct sprite *s); + +#define sprite_from_file(path, s) sprite_from_file_ex("cdrom:\\"path";1", s) + +#ifdef __cplusplus +} +#endif + +#endif /* GFX_PS1_H */ diff --git a/src/gfx/ps1/privinc/ps1/gfx_private.h b/src/gfx/ps1/privinc/ps1/gfx_private.h new file mode 100644 index 0000000..fd5a790 --- /dev/null +++ b/src/gfx/ps1/privinc/ps1/gfx_private.h @@ -0,0 +1,25 @@ +#ifndef GFX_PS1_PRIVATE_H +#define GFX_PS1_PRIVATE_H + +#include <gfx.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum +{ + SCREEN_W = 368, + SCREEN_H = 240 +}; + +void gfx_swapheap(void); +void gfx_initenvs(void); +void gfx_swapbuffers(void); + +#ifdef __cplusplus +} +#endif + +#endif /* GFX_PS1_PRIVATE_H */ diff --git a/src/gfx/ps1/src/4line.c b/src/gfx/ps1/src/4line.c new file mode 100644 index 0000000..955277e --- /dev/null +++ b/src/gfx/ps1/src/4line.c @@ -0,0 +1,14 @@ +#include <gfx.h> +#include <psxgpu.h> +#include <stdint.h> +#include <string.h> + +void stp_4line_init(struct stp_4line *const l) +{ + memset(l, 0, sizeof *l); + l->sznext.f.sz = (sizeof *l - sizeof l->sznext) / sizeof (uint32_t); + l->common.f.cmd = DRAW_MODE; + l->cmd = 0x5A; + enum {TERMINATION_CODE = 0x55555555}; + l->end = TERMINATION_CODE; +} diff --git a/src/gfx/ps1/src/env.c b/src/gfx/ps1/src/env.c new file mode 100644 index 0000000..c12002b --- /dev/null +++ b/src/gfx/ps1/src/env.c @@ -0,0 +1,42 @@ +#include <gfx.h> +#include <gfx_private.h> +#include <ps1/gfx_private.h> +#include <psxgpu.h> +#include <stdbool.h> + +static GsDispEnv dispenv; +enum {ENV_Y = SCREEN_H + 16}; + +static GsDrawEnv drawenv = +{ + .y = ENV_Y, + .w = SCREEN_W, + .h = SCREEN_H +}; + +int screen_w = SCREEN_W, screen_h = SCREEN_H; + +void gfx_swapbuffers(void) +{ + const short y = drawenv.y; + + drawenv.y = dispenv.y; + dispenv.y = y; + GsSetDrawEnv(&drawenv); + GsSetDispEnv(&dispenv); +} + +void gfx_initenvs(void) +{ + GsSetDrawEnv(&drawenv); + GsSetDispEnv(&dispenv); +} + +bool gfx_inside_drawenv(const short x, const short y, const short w, + const short h) +{ + return (x + w >= 0) + && x < drawenv.w + && (y + h >= 0) + && y < drawenv.h; +} diff --git a/src/gfx/ps1/src/heap.c b/src/gfx/ps1/src/heap.c new file mode 100644 index 0000000..b67ee3e --- /dev/null +++ b/src/gfx/ps1/src/heap.c @@ -0,0 +1,19 @@ +#include <gfx.h> +#include <gfx_private.h> +#include <stddef.h> + +static unsigned int sel; + +void gfx_swapheap(void) +{ + sel ^= 1; +} + +void *gfx_port_heap(size_t *const n) +{ + enum {HEAP_SZ = 5120, N_HEAPS = 2}; + static char heaps[N_HEAPS][HEAP_SZ]; + + *n = sizeof *heaps / sizeof **heaps; + return heaps[sel]; +} diff --git a/src/gfx/ps1/src/init.c b/src/gfx/ps1/src/init.c new file mode 100644 index 0000000..15ecd3e --- /dev/null +++ b/src/gfx/ps1/src/init.c @@ -0,0 +1,16 @@ +#include <gfx_private.h> +#include <ps1/gfx_private.h> +#include <psxgpu.h> + +void gfx_deinit(void) +{ +} + +int gfx_init(void) +{ + GsInit(); + GsClearMem(); + GsSetVideoMode(SCREEN_W, SCREEN_H, VIDEO_MODE); + gfx_initenvs(); + return 0; +} diff --git a/src/gfx/ps1/src/quad.c b/src/gfx/ps1/src/quad.c new file mode 100644 index 0000000..649426f --- /dev/null +++ b/src/gfx/ps1/src/quad.c @@ -0,0 +1,23 @@ +#include <gfx.h> +#include <gfx/port.h> +#include <stdint.h> +#include <string.h> + +static void quad_init(struct quad *const q) +{ + memset(q, 0, sizeof *q); + q->sznext.f.sz = (sizeof *q - sizeof q->sznext) / sizeof (uint32_t); + q->cmd = 0x2d; +} + +int quad_from_sprite(const struct sprite *const s, struct quad *const q) +{ + quad_init(q); + q->tpage.mask = s->common.mask; + q->clutid = s->clutid; + q->u0 = q->u2 = s->u; + q->v0 = q->v1 = s->v; + q->u1 = q->u3 = s->u + s->w - 1; + q->v2 = q->v3 = s->v + s->h - 1; + return 0; +} diff --git a/src/gfx/ps1/src/rect.c b/src/gfx/ps1/src/rect.c new file mode 100644 index 0000000..7e593b7 --- /dev/null +++ b/src/gfx/ps1/src/rect.c @@ -0,0 +1,23 @@ +#include <gfx.h> +#include <psxgpu.h> +#include <stdint.h> +#include <string.h> + +static void common_init(struct rect *const r) +{ + memset(r, 0, sizeof *r); + r->sznext.f.sz = (sizeof *r - sizeof r->sznext) / sizeof (uint32_t); + r->common.f.cmd = DRAW_MODE; +} + +void rect_init(struct rect *const r) +{ + common_init(r); + r->cmd = 0x60; +} + +void semitrans_rect_init(struct rect *const r) +{ + common_init(r); + r->cmd = 0x62; +} diff --git a/src/gfx/ps1/src/sort.c b/src/gfx/ps1/src/sort.c new file mode 100644 index 0000000..5cc14aa --- /dev/null +++ b/src/gfx/ps1/src/sort.c @@ -0,0 +1,75 @@ +#include <gfx.h> +#include <gfx_private.h> +#include <ps1/gfx_private.h> +#include <system/port.h> +#include <psxgpu.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +static union gfx_sznext *first, *last; + +static void add_to_list(union gfx_sznext *const p) +{ + if (!first) + first = p; + else if (last) + last->f.next = (uint32_t)p; + + last = p; +} + +void sprite_sort(struct sprite *const s) +{ + add_to_list(&s->sznext); +} + +void quad_sort(struct quad *const q) +{ + add_to_list(&q->sznext); +} + +void rect_sort(struct rect *const r) +{ + add_to_list(&r->sznext); +} + +void stp_4line_sort(struct stp_4line *const l) +{ + add_to_list(&l->sznext); +} + +void gfx_draw(void) +{ + static union gfx_sznext term = {.cmd_next = 0xffffff}; + + add_to_list(&term); + + void gpu_ctrl(unsigned int command, unsigned int param); + + gfx_sync(); + gfx_swapbuffers(); + gfx_swapheap(); + gpu_ctrl(4, 2); + D2_MADR = (uint32_t)first; + D2_BCR = 0; + D2_CHCR = (1 << 0xa) | 1 | (1 << 0x18); + first = NULL; + gfx_reset_heap(); +} + +void gfx_sync(void) +{ + /* Wait for the GPU to finish drawing primitives. */ + while (!(GPU_CONTROL_PORT & (1 << 0x1a))) + ; + + /* Wait for the GPU to be free. */ + while (!(GPU_CONTROL_PORT & (1 << 0x1c))) + ; + + while (!vblank_set) + ; + + vblank_set = false; +} diff --git a/src/gfx/ps1/src/sprite.c b/src/gfx/ps1/src/sprite.c new file mode 100644 index 0000000..3e35a70 --- /dev/null +++ b/src/gfx/ps1/src/sprite.c @@ -0,0 +1,244 @@ +#include <gfx.h> +#include <psxgpu.h> +#include <errno.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> + +void sprite_free(struct sprite *const src) +{ +} + +int sprite_clone(const struct sprite *const src, struct sprite *const dst) +{ + *dst = *src; + return 0; +} + +static void sprite_init(struct sprite *const s) +{ + memset(s, 0, sizeof *s); + s->sznext.f.sz = (sizeof *s - sizeof s->sznext) / sizeof (uint32_t); + s->common.f.cmd = DRAW_MODE; + s->cmd = 0x65; +} + +struct tim_pos +{ + uint16_t x, y, w, h; +}; + +static void transfer_init(const struct tim_pos *const p) +{ + while (!(GPU_CONTROL_PORT & (1 << 0x1c))) + ; + + GPU_CONTROL_PORT = 0x04000000; + GPU_DATA_PORT = 0x01000000; + GPU_DATA_PORT = 0xE6000000; + GPU_DATA_PORT = 0xA0000000; + GPU_DATA_PORT = (p->y << 16) | p->x; + GPU_DATA_PORT = (p->h << 16) | p->w; +} + +static int transfer(const size_t sz, FILE *const f) +{ + const size_t rem = sz % sizeof (uint32_t); + + if (sz >= sizeof (uint32_t)) + { + for (size_t i = 0; i < sz / sizeof (uint32_t); i++) + { + uint32_t pix; + + if (!fread(&pix, sizeof pix, 1, f)) + { + fprintf(stderr, "could not read CLUT word %zu/%zu\n", + i, sz / sizeof pix); + return -1; + } + + GPU_DATA_PORT = pix; + } + } + + if (rem) + { + uint32_t pix = 0; + + if (!fread(&pix, rem, 1, f)) + { + fprintf(stderr, "failed reading remaining %zu bytes\n", rem); + return -1; + } + + GPU_DATA_PORT = pix; + } + + return 0; +} + +struct header +{ + uint32_t sz; + struct tim_pos pos; +}; + +static int upload_clut(struct sprite *const s, FILE *const f) +{ + struct header clut; + + if (!fread(&clut, sizeof clut, 1, f)) + { + fprintf(stderr, "no CLUT pos data found\n"); + return -1; + } + + transfer_init(&clut.pos); + + const size_t sz = clut.sz - sizeof clut; + + if (transfer(sz, f)) + return -1; + + s->clutid = get_clutid(clut.pos.x, clut.pos.y); + return 0; +} + +enum bpp +{ + BPP_4 = 0, + BPP_8 = 1, + BPP_16 = 2, + BPP_24 = 4 +}; + +static int upload_img(struct sprite *const s, const enum bpp bpp, FILE *const f) +{ + struct header img; + + if (!fread(&img, sizeof img, 1, f)) + { + fprintf(stderr, "could not extract image header\n"); + return -1; + } + + transfer_init(&img.pos); + + const size_t sz = img.sz - sizeof img; + + if (transfer(sz, f)) + return -1; + + enum + { + VRAM_X = 1024, + VRAM_Y = 512, + TPAGE_WIDTH = 64 + }; + + s->common.f.tpagex = img.pos.x / TPAGE_WIDTH; + s->common.f.tpagey = img.pos.y / (VRAM_Y / 2); + s->u = img.pos.x % TPAGE_WIDTH; + s->v = img.pos.y % (VRAM_Y / 2); + + switch (bpp) + { + case BPP_4: + s->w = img.pos.w * 4; + s->u <<= 2; + break; + + case BPP_8: + s->w = img.pos.w * 2; + s->u <<= 1; + break; + + case BPP_16: + s->w = img.pos.w; + break; + + case BPP_24: + s->w = img.pos.w + (img.pos.w / 2); + break; + } + + s->h = img.pos.h; + return 0; +} + +int sprite_from_fp(struct sprite *const s, FILE *const f) +{ + int ret = -1; + + sprite_init(s); + + struct + { + uint32_t version; + enum bpp bpp :3; + bool has_clut :1; + uint32_t :28; + } h; + + enum {VERSION_ID = 0x10}; + + if (!fread(&h, sizeof h, 1, f)) + { + fprintf(stderr, "TIM header not found\n"); + goto end; + } + else if (h.version != VERSION_ID) + { + fprintf(stderr, "%s: invalid TIM header %#" PRIx32 "\n", h.version); + goto end; + } + else if (h.bpp == BPP_24) + { + fprintf(stderr, "24-bit mode unsupported\n"); + goto end; + } + else if ((h.has_clut && upload_clut(s, f)) + || upload_img(s, h.bpp, f)) + goto end; + + s->common.f.bpp = h.bpp ? __builtin_ctz(h.bpp) + 1 : 0; + ret = 0; + +end: + return ret; +} + +int sprite_from_file_ex(const char *const path, struct sprite *const s) +{ + int ret = -1; + FILE *f = NULL; + + if (!path) + { + errno = EINVAL; + goto end; + } + + f = fopen(path, "rb"); + + if (!f) + { + fprintf(stderr, "could not open %s: %s\n", path, strerror(errno)); + goto end; + } + else if (sprite_from_fp(s, f)) + { + fprintf(stderr, "%s: sprite_from_fp failed\n", path); + goto end; + } + +end: + + if (f) + fclose(f); + + return ret; +} diff --git a/src/gfx/sdl-1.2/inc/gfx/port.h b/src/gfx/sdl-1.2/inc/gfx/port.h new file mode 100644 index 0000000..ec1093e --- /dev/null +++ b/src/gfx/sdl-1.2/inc/gfx/port.h @@ -0,0 +1,52 @@ +#ifndef GFX_SDL_H +#define GFX_SDL_H + +#include <SDL/SDL.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct sprite +{ + SDL_Surface *s; + short x, y, w, h; + unsigned char u, v; +}; + +struct quad +{ + unsigned char r, g, b; + short x0, x1, x2, x3; + short y0, y1, y2, y3; + unsigned char u0, u1, u2, u3; + unsigned char v0, v1, v2, v3; + SDL_Surface *s; +}; + +struct rect +{ + unsigned char r, g, b; + short x, y, w, h; +}; + +struct stp_4line +{ + short x, y; + unsigned char r, g, b; + + struct stp_4line_vtx + { + unsigned char r, g, b; + short x, y; + } vertices[4]; +}; + +SDL_Surface *gfx_port_screen(void); + +#ifdef __cplusplus +} +#endif + +#endif /* GFX_SDL_H */ diff --git a/src/gfx/sdl-1.2/privinc/sdl-1.2/gfx_private.h b/src/gfx/sdl-1.2/privinc/sdl-1.2/gfx_private.h new file mode 100644 index 0000000..c067af7 --- /dev/null +++ b/src/gfx/sdl-1.2/privinc/sdl-1.2/gfx_private.h @@ -0,0 +1,19 @@ +#ifndef GFX_SDL_12_PRIVATE_H +#define GFX_SDL_12_PRIVATE_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum +{ + SCREEN_W = 320, + SCREEN_H = 240 +}; + +#ifdef __cplusplus +} +#endif + +#endif /* GFX_SDL_12_PRIVATE_H */ diff --git a/src/gfx/sdl-1.2/src/env.c b/src/gfx/sdl-1.2/src/env.c new file mode 100644 index 0000000..6589d84 --- /dev/null +++ b/src/gfx/sdl-1.2/src/env.c @@ -0,0 +1,82 @@ +#include <gfx.h> +#include <sdl-1.2/gfx_private.h> +#include <SDL/SDL.h> +#include <stdbool.h> + +int screen_w = SCREEN_W, screen_h = SCREEN_H; +static bool fullscreen; +static SDL_Surface *screen; + +void gfx_deinit(void) +{ + if (screen) + SDL_FreeSurface(screen); + + SDL_QuitSubSystem(SDL_INIT_VIDEO); +} + +static int resize_screen(const int w, const int h, const bool full_screen) +{ + Uint32 flags = SDL_HWSURFACE | SDL_RESIZABLE | SDL_DOUBLEBUF; + + const SDL_VideoInfo *const info = SDL_GetVideoInfo(); + + if (!info) + { + fprintf(stderr, "SDL_GetVideoInfo: %s\n", SDL_GetError()); + return -1; + } + + if (fullscreen) + flags |= SDL_FULLSCREEN; + +#if 0 + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); +#endif + + const int bpp = info->vfmt->BitsPerPixel; + + if (screen) + SDL_FreeSurface(screen); + + if (!(screen = SDL_SetVideoMode(screen_w, screen_h, bpp, flags))) + { + fprintf(stderr, "SDL_SetVideoMode: %s\n", SDL_GetError()); + return -1; + } + + screen_w = w; + screen_h = h; + fullscreen = full_screen; + return 0; +} + +int gfx_init(void) +{ + if (SDL_InitSubSystem(SDL_INIT_VIDEO)) + { + fprintf(stderr, "%s: SDL_InitSubSystem: %s\n", + __func__, SDL_GetError()); + return -1; + } + + return resize_screen(screen_w, screen_h, fullscreen); +} + +bool gfx_inside_drawenv(const short x, const short y, const short w, + const short h) +{ + return (x + w >= 0) + && x < screen_w + && (y + h >= 0) + && y < screen_h; +} + +SDL_Surface *gfx_port_screen(void) +{ + return screen; +} diff --git a/src/gfx/sdl-1.2/src/heap.c b/src/gfx/sdl-1.2/src/heap.c new file mode 100644 index 0000000..b84760a --- /dev/null +++ b/src/gfx/sdl-1.2/src/heap.c @@ -0,0 +1,11 @@ +#include <gfx.h> +#include <gfx_private.h> +#include <stddef.h> + +void *gfx_port_heap(size_t *const n) +{ + static char heap[5120]; + + *n = sizeof heap / sizeof *heap; + return heap; +} diff --git a/src/gfx/sdl-1.2/src/line.c b/src/gfx/sdl-1.2/src/line.c new file mode 100644 index 0000000..aeeda7b --- /dev/null +++ b/src/gfx/sdl-1.2/src/line.c @@ -0,0 +1,15 @@ +#include <gfx.h> +#include <gfx/port.h> +#include <stddef.h> + +void stp_4line_init(struct stp_4line *const l) +{ +} + +void semitrans_stp_4line_init(struct stp_4line *r) +{ +} + +void stp_4line_sort(struct stp_4line *const r) +{ +} diff --git a/src/gfx/sdl-1.2/src/quad.c b/src/gfx/sdl-1.2/src/quad.c new file mode 100644 index 0000000..fe1b39f --- /dev/null +++ b/src/gfx/sdl-1.2/src/quad.c @@ -0,0 +1,18 @@ +#include <gfx.h> +#include <gfx/port.h> +#include <stddef.h> +#include <stdlib.h> + +int quad_from_sprite(const struct sprite *const s, struct quad *const q) +{ + q->s = s->s; + q->u0 = q->u2 = s->u; + q->v0 = q->v1 = s->v; + q->u1 = q->u3 = s->u + s->w - 1; + q->v2 = q->v3 = s->v + s->h - 1; + return 0; +} + +void quad_sort(struct quad *const q) +{ +} diff --git a/src/gfx/sdl-1.2/src/rect.c b/src/gfx/sdl-1.2/src/rect.c new file mode 100644 index 0000000..ee50378 --- /dev/null +++ b/src/gfx/sdl-1.2/src/rect.c @@ -0,0 +1,33 @@ +#include <gfx.h> +#include <gfx/port.h> +#include <SDL/SDL.h> +#include <stddef.h> +#include <stdlib.h> + +void rect_sort(struct rect *const r) +{ + SDL_Rect rct = + { + .x = r->x, + .y = r->y, + .w = r->w, + .h = r->h + }; + + SDL_Surface *const screen = gfx_port_screen(); + const Uint32 map = SDL_MapRGB(screen->format, r->r, r->g, r->b); + + if (SDL_FillRect(screen, &rct, map)) + { + fprintf(stderr, "SDL_FillRect: %s\n", SDL_GetError()); + return; + } +} + +void semitrans_rect_init(struct rect *const r) +{ +} + +void rect_init(struct rect *const r) +{ +} diff --git a/src/gfx/sdl-1.2/src/sort.c b/src/gfx/sdl-1.2/src/sort.c new file mode 100644 index 0000000..2cc4e75 --- /dev/null +++ b/src/gfx/sdl-1.2/src/sort.c @@ -0,0 +1,22 @@ +#include <gfx.h> +#include <gfx_private.h> +#include <gfx/port.h> +#include <SDL/SDL.h> +#include <stdio.h> + +void gfx_draw(void) +{ + enum {FPS = 50, REFRESH_MS = 1000 / FPS}; + static Uint32 prev; + const Uint32 cur = SDL_GetTicks(); + + if (cur - prev < REFRESH_MS) + SDL_Delay(REFRESH_MS - (cur - prev)); + + prev = SDL_GetTicks(); + + if (SDL_Flip(gfx_port_screen())) + fprintf(stderr, "SDL_Flip: %s\n", SDL_GetError()); + + gfx_reset_heap(); +} diff --git a/src/gfx/sdl-1.2/src/sprite.c b/src/gfx/sdl-1.2/src/sprite.c new file mode 100644 index 0000000..9d65c0d --- /dev/null +++ b/src/gfx/sdl-1.2/src/sprite.c @@ -0,0 +1,75 @@ +#include <gfx.h> +#include <gfx/port.h> +#include <SDL/SDL.h> +#include <stddef.h> +#include <stdlib.h> + +void sprite_free(struct sprite *const s) +{ + if (s && s->s) + SDL_FreeSurface(s->s); +} + +int sprite_clone(const struct sprite *const src, struct sprite *const dst) +{ + *dst = *src; + return 0; +} + +int sprite_from_fp(struct sprite *const s, FILE *const f) +{ + int ret = -1; + SDL_RWops *ops = NULL; + SDL_Surface *ts = NULL; + + if (!(ops = SDL_RWFromFP(f, 0))) + { + fprintf(stderr, "SDL_RWFromFP: %s\n", SDL_GetError()); + goto end; + } + if (!(ts = SDL_LoadBMP_RW(ops, 0))) + { + fprintf(stderr, "SDL_LoadBMP_RW: %s\n", SDL_GetError()); + goto end; + } + /* Magenta as transparent. */ + else if (SDL_SetColorKey(ts, SDL_SRCCOLORKEY, SDL_MapRGB(ts->format, 255, 0, 255))) + { + fprintf(stderr, "SDL_SetColorKey: %s\n", SDL_GetError()); + goto end; + } + else if (!(s->s = SDL_DisplayFormat(ts))) + { + fprintf(stderr, "SDL_DisplayFormat: %s\n", SDL_GetError()); + goto end; + } + + s->w = ts->w; + s->h = ts->h; + ret = 0; + +end: + SDL_FreeRW(ops); + SDL_FreeSurface(ts); + return ret; +} + +void sprite_sort(struct sprite *const s) +{ + SDL_Rect r = + { + .x = s->x, + .y = s->y + }; + + SDL_Rect clip = + { + .x = s->u, + .y = s->v, + .w = s->w, + .h = s->h + }; + + if (SDL_BlitSurface(s->s, &clip, gfx_port_screen(), &r)) + fprintf(stderr, "SDL_BlitSurface: %s\n", SDL_GetError()); +} diff --git a/src/gfx/src/heap.c b/src/gfx/src/heap.c new file mode 100644 index 0000000..40a856c --- /dev/null +++ b/src/gfx/src/heap.c @@ -0,0 +1,56 @@ +#include <gfx.h> +#include <gfx_private.h> +#include <stddef.h> + +static size_t heap_i; + +void gfx_reset_heap(void) +{ + heap_i = 0; +} + +static void *get_element(const size_t sz) +{ + size_t n; + char *ret = gfx_port_heap(&n); + + if (ret && heap_i + sz < n) + { + ret += heap_i; + heap_i += sz; + } + else + return NULL; + + return ret; +} + +struct sprite *sprite_get(void) +{ + return get_element(sizeof (struct sprite)); +} + +struct quad *quad_get(void) +{ + return get_element(sizeof (struct quad)); +} + +struct rect *rect_get(const bool semitrans) +{ + struct rect *const r = get_element(sizeof (struct rect)); + + if (r) + semitrans ? semitrans_rect_init(r) : rect_init(r); + + return r; +} + +struct stp_4line *stp_4line_get(void) +{ + struct stp_4line *const l = get_element(sizeof (struct stp_4line)); + + if (l) + stp_4line_init(l); + + return l; +} diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt new file mode 100644 index 0000000..59ee94d --- /dev/null +++ b/src/gui/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(gui "src/gui.c") +target_include_directories(gui PUBLIC "inc") +target_link_libraries(gui PUBLIC gfx player PRIVATE font unit building) diff --git a/src/gui/inc/gui.h b/src/gui/inc/gui.h new file mode 100644 index 0000000..254fd47 --- /dev/null +++ b/src/gui/inc/gui.h @@ -0,0 +1,36 @@ +#ifndef GUI_H +#define GUI_H + +#include <gfx.h> +#include <human_player.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum +{ + GUI_BAR_LEFT, + GUI_BAR_MID, + GUI_BAR_RIGHT, + GUI_SELECTION_UP_LEFT, + GUI_SELECTION_UP_RIGHT, + GUI_SELECTION_MID_VERT, + GUI_SELECTION_DOWN_LEFT, + GUI_SELECTION_DOWN_RIGHT, + GUI_SELECTION_MID, + + MAX_GUI_SPRITES +}; + +void gui_update(struct human_player *h); +int gui_render(const struct human_player *h); + +extern struct sprite gui_sprites[MAX_GUI_SPRITES]; + +#ifdef __cplusplus +} +#endif + +#endif /* GUI_H */ diff --git a/src/gui/src/gui.c b/src/gui/src/gui.c new file mode 100644 index 0000000..2a5e53b --- /dev/null +++ b/src/gui/src/gui.c @@ -0,0 +1,473 @@ +#include <gui.h> +#include <building.h> +#include <font.h> +#include <gfx.h> +#include <human_player.h> +#include <player.h> +#include <unit.h> +#include <limits.h> +#include <stddef.h> + +struct sprite gui_sprites[MAX_GUI_SPRITES]; + +static int render_topleft(void) +{ + struct sprite *const s = sprite_get(); + + if (!s + || sprite_clone(&gui_sprites[GUI_BAR_LEFT], s)) + return -1; + + sprite_sort(s); + return 0; +} + +static int render_topright(void) +{ + struct sprite *const s = sprite_get(); + + if (!s || sprite_clone(&gui_sprites[GUI_BAR_RIGHT], s)) + return -1; + + s->x = screen_w - s->w; + sprite_sort(s); + return 0; +} + +static int render_topmid(void) +{ + const uint16_t mid_w = gui_sprites[GUI_BAR_MID].w; + const uint16_t lw = gui_sprites[GUI_BAR_LEFT].w; + const size_t w = screen_w - lw - lw; + const size_t rem_mid = w % mid_w; + const size_t whole_mid = w / mid_w; + const size_t n_mid = rem_mid ? whole_mid + 1 : whole_mid; + + for (struct + { + size_t i; + short x; + } a = {.i = 0, .x = lw}; + a.i < n_mid; + a.i++, a.x += mid_w) + { + struct sprite *const m = sprite_get(); + + if (!m + || sprite_clone(&gui_sprites[GUI_BAR_MID], m)) + return -1; + + m->x = a.x; + + if (rem_mid && a.i + 1 == n_mid) + m->w = rem_mid; + else + m->w = mid_w; + + sprite_sort(m); + } + + return 0; +} + +static int render_top(void) +{ + if (render_topleft() + || render_topright() + || render_topmid()) + return -1; + + return 0; +} + +static int render_seltop(struct sprite *const up) +{ + enum {OFFSET = 48}; + + if (sprite_clone(&gui_sprites[GUI_SELECTION_UP_LEFT], up)) + return -1; + + up->y = screen_h - up->h - OFFSET; + up->x = 16; + sprite_sort(up); + + struct sprite *const right = sprite_get(); + + if (!right + || sprite_clone(&gui_sprites[GUI_SELECTION_UP_RIGHT], right)) + return -1; + + right->x = screen_w - right->w - up->x; + right->y = up->y; + sprite_sort(right); + return 0; +} + +static int render_selvert(const struct sprite *const up, const short x) +{ + const uint16_t vert_h = gui_sprites[GUI_SELECTION_MID_VERT].h; + const size_t h = screen_h - up->y - up->h - up->h; + const size_t rem_vert = h % vert_h; + const size_t whole_vert = h / vert_h; + const size_t n_vert = rem_vert ? whole_vert + 1 : whole_vert; + + for (struct + { + size_t i; + short y; + } a = {.i = 0, .y = up->y + up->h}; + a.i < n_vert; + a.i++, a.y += vert_h) + { + struct sprite *const v = sprite_get(); + + if (!v) + return -1; + + *v = gui_sprites[GUI_SELECTION_MID_VERT]; + v->y = a.y; + v->x = x; + + if (rem_vert && a.i + 1 == n_vert) + v->h = rem_vert; + else + v->h = vert_h; + + sprite_sort(v); + } + + return 0; +} + +static int render_selvertleft(const struct sprite *const up) +{ + return render_selvert(up, up->x); +} + +static int render_selvertright(const struct sprite *const up) +{ + return render_selvert(up, screen_w - gui_sprites[GUI_SELECTION_MID_VERT].w - up->x); +} + +static int render_seldown(const struct sprite *const up) +{ + { + struct sprite *const left = sprite_get(); + + if (!left) + return -1; + + *left = gui_sprites[GUI_SELECTION_DOWN_LEFT]; + left->x = up->x; + left->y = screen_h - left->h; + sprite_sort(left); + } + + { + struct sprite *const right = sprite_get(); + + if (!right) + return -1; + + *right = gui_sprites[GUI_SELECTION_DOWN_RIGHT]; + right->x = screen_w - right->w - up->x; + right->y = screen_h - right->h; + sprite_sort(right); + } + + return 0; +} + +static int render_selrect(const struct sprite *const up) +{ + struct rect *const sel = rect_get(true); + + if (!sel) + return -1; + + const struct sprite *const mid = &gui_sprites[GUI_SELECTION_MID], + *const vert = &gui_sprites[GUI_SELECTION_MID_VERT]; + sel->x = up->x + vert->w; + sel->y = up->y + mid->h; + sel->w = screen_w - sel->x - vert->w - up->x; + sel->h = screen_h - sel->y - mid->h; + sel->r = 72; + sel->g = 66; + sel->b = 56; + rect_sort(sel); + return 0; +} + +static int render_selmid(const struct sprite *const up, const short y) +{ + const uint16_t mid_w = gui_sprites[GUI_SELECTION_MID].w; + const size_t w = screen_w - up->x - up->x - up->w - up->w; + const size_t rem_mid = w % mid_w; + const size_t whole_mid = w / mid_w; + const size_t n_mid = rem_mid ? whole_mid + 1 : whole_mid; + + for (struct + { + size_t i; + short x; + } a = {.i = 0, .x = up->x + up->w}; + a.i < n_mid; + a.i++, a.x += mid_w) + { + struct sprite *const m = sprite_get(); + + if (!m) + return -1; + + *m = gui_sprites[GUI_SELECTION_MID]; + m->x = a.x; + m->y = y; + + if (rem_mid && a.i + 1 == n_mid) + m->w = rem_mid; + else + m->w = mid_w; + + sprite_sort(m); + } + + return 0; +} + +static int render_selmidtop(const struct sprite *const up) +{ + return render_selmid(up, up->y); +} + +static int render_selmiddown(const struct sprite *const up) +{ + return render_selmid(up, screen_h - gui_sprites[GUI_SELECTION_MID].h); +} + +static int render_topmenu(const struct human_player *const h) +{ + const struct player *const pl = &h->pl; + + if (render_top() + || (font_printf(FONT, 16, 6, "Wood=%u", + h->gui_res[RESOURCE_TYPE_WOOD]) < 0) + || (font_printf(FONT, 108, 6, "Gold=%u", + h->gui_res[RESOURCE_TYPE_GOLD]) < 0) + || (font_printf(FONT, 212, 6, "Pop.=%u/%zu", + pl->pop, sizeof pl->units / sizeof *pl->units) < 0)) + return -1; + + return 0; +} + +static int draw_hp(const struct instance *const i, const short x, const short y, + const instance_hp max_hp) +{ + enum {OFFSET = 4, WIDTH = 64, HEIGHT = 4}; + const short ry = y - OFFSET - HEIGHT; + + if (ry < 0) + return 0; + + const short gw = (WIDTH * i->hp) / max_hp; + + { + struct rect *const gr = rect_get(true); + + if (!gr) + return -1; + + gr->x = x; + gr->y = ry; + gr->w = gw; + gr->h = HEIGHT; + gr->g = UCHAR_MAX >> 1; + rect_sort(gr); + } + + { + struct rect *const rr = rect_get(true); + + if (!rr) + return -1; + + rr->x = x + gw; + rr->y = ry; + rr->w = WIDTH - gw; + rr->h = HEIGHT; + rr->r = UCHAR_MAX >> 1; + rect_sort(rr); + } + + return 0; +} + +static int draw_miners(const struct resource *const r) +{ + const struct resource_gold *const g = &r->res.gold; + + if (!g->n_miners) + return 0; + + for (size_t i = 0, n = 0; i < sizeof g->miners / sizeof *g->miners; i++) + { + if (g->miners[i]) + { + enum {OFFSET = 112, SZ = 16, GAP = 4}; + struct rect *const r = rect_get(true); + + if (!r) + return -1; + + r->x = OFFSET + n * (SZ + GAP); + r->y = screen_h - 40; + r->r = r->g = r->b = 127; + r->w = r->h = SZ; + rect_sort(r); + + if (++n >= g->n_miners) + break; + } + } + + return 0; +} + +static int draw_carry(const enum font f, const short x, const short y, + const struct unit *const u) +{ + if (unit_can_harvest(u)) + { + const struct unit_harvester *const uh = &u->us.harvester; + + if (uh->carry) + return font_printf(f, x, y, "%d", uh->carry) < 0; + } + + return 0; +} + +static int render_selsingle(const struct sprite *const up, + const struct human_player *const h) +{ + enum {X_OFF = 8, Y_OFF = 8, HP_Y = 32}; + const short x = up->x + X_OFF, y = up->y + Y_OFF; + + for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++) + { + const struct sel_instance *const sel = &h->sel[i]; + + if (sel) + { + switch (sel->type) + { + case INSTANCE_TYPE_BUILDING: + { + const struct building *const b = sel->d.b; + const struct instance *const in = &b->instance; + const instance_hp hp = in->hp, max_hp = building_maxhp(b); + + if ((font_printf(FONT, x, y, "%s", building_str(b)) < 0) + || (font_printf(FONT, x, y + HP_Y, "%u/%u", hp, max_hp) < 0) + || draw_hp(in, x, y + HP_Y - 4, max_hp)) + return -1; + + return 0; + } + break; + + case INSTANCE_TYPE_UNIT: + { + const struct unit *const u = sel->d.u; + const struct instance *const in = &u->instance; + const instance_hp hp = in->hp, max_hp = unit_maxhp(u); + enum {CARRY_X = 96, CARRY_Y = 8}; + + if ((font_printf(FONT, x, y, "%s", unit_str(u)) < 0) + || (font_printf(FONT, x, y + HP_Y, "%u/%u", hp, max_hp) < 0) + || draw_carry(FONT, x + CARRY_X, y, u) + || draw_hp(in, x, y + HP_Y - 4, max_hp)) + return -1; + + return 0; + } + break; + + case INSTANCE_TYPE_RESOURCE: + { + const struct resource *const r = sel->d.r; + const struct instance *const in = &r->instance; + const instance_hp hp = in->hp, max_hp = resource_maxhp(r); + + if ((font_printf(FONT, x, y, "%s", resource_str(r)) < 0) + || (font_printf(FONT, x, y + HP_Y, "%u/%u", hp, max_hp) < 0) + || draw_hp(in, x, y + HP_Y - 4, max_hp) + || (r->type == RESOURCE_TYPE_GOLD && draw_miners(r))) + return -1; + + return 0; + } + break; + } + + return 0; + } + } + + /* Unreachable. */ + return -1; +} + +static int render_selinfo(const struct sprite *const up, + const struct human_player *const h) +{ + enum {X_OFF = 8, Y_OFF = 8}; + + if (h->n_sel == 1) + return render_selsingle(up, h); + else if (font_printf(FONT, up->x + X_OFF, up->y + Y_OFF, + "%u units selected", h->n_sel) < 0) + return -1; + + return 0; +} + +static int render_sel(const struct human_player *const h) +{ + struct sprite *const up = sprite_get(); + + if (!up + || render_seltop(up) + || render_selrect(up) + || render_selmidtop(up) + || render_selmiddown(up) + || render_selvertleft(up) + || render_selvertright(up) + || render_seldown(up) + || render_selinfo(up, h)) + return -1; + + return 0; +} + +void gui_update(struct human_player *const h) +{ + struct player *const pl = &h->pl; + + for (size_t i = 0; i < sizeof pl->resources / sizeof *pl->resources; i++) + { + if (h->gui_res[i] > pl->resources[i]) + h->gui_res[i]--; + else if (h->gui_res[i] < pl->resources[i]) + h->gui_res[i]++; + } +} + +int gui_render(const struct human_player *const h) +{ + if ((h->top_gui && render_topmenu(h)) + || (h->n_sel && render_sel(h))) + return -1; + + return 0; +} diff --git a/src/instance/CMakeLists.txt b/src/instance/CMakeLists.txt new file mode 100644 index 0000000..2e777a9 --- /dev/null +++ b/src/instance/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(instance "src/instance.c") +target_include_directories(instance PUBLIC "inc") +target_link_libraries(instance PUBLIC camera gfx util) diff --git a/src/instance/inc/instance.h b/src/instance/inc/instance.h new file mode 100644 index 0000000..ca8a606 --- /dev/null +++ b/src/instance/inc/instance.h @@ -0,0 +1,68 @@ +#ifndef INSTANCE_H +#define INSTANCE_H + +#include <camera.h> +#include <gfx.h> +#include <util.h> +#include <stdbool.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef unsigned int instance_hp; + +struct instance +{ + bool alive, dying; + unsigned int hp; + struct util_rect r; +}; + +typedef bool (*instance_attacked_cb)(struct instance *i, unsigned int ap); +typedef bool (*instance_sheltered_cb)(struct instance *self, struct instance *other); +typedef void (*instance_done_cb)(struct instance *i, void *op); + +struct instance_render_cfg +{ + const struct instance *i; + const struct camera *cam; + enum + { + INSTANCE_RENDER_CFG_SPRITE, + INSTANCE_RENDER_CFG_QUAD + } prim_type; + + union + { + struct sprite *s; + const struct instance_render_quad + { + struct quad *q; + short w, h; + uint8_t u; + bool xflip; + } *quad; + } prim; + + bool sel; + instance_hp max_hp; + const struct instance_render_off + { + short x, y; + } *off; +}; + +bool instance_attacked(struct instance *self, unsigned int ap); +void instance_clear_pools(void); +int instance_render(const struct instance_render_cfg *cfg); +int instance_render_target(const struct instance *const i, const struct camera *cam); +void instance_cyclic(void); + +#ifdef __cplusplus +} +#endif + +#endif /* INSTANCE_H */ diff --git a/src/instance/src/instance.c b/src/instance/src/instance.c new file mode 100644 index 0000000..03f4872 --- /dev/null +++ b/src/instance/src/instance.c @@ -0,0 +1,150 @@ +#include <instance.h> +#include <gfx.h> +#include <limits.h> +#include <stdbool.h> +#include <stddef.h> +#include <string.h> + +static unsigned char line_g; +static bool line_g_flip; + +bool instance_attacked(struct instance *const self, const instance_hp ap) +{ + if (self->hp > ap) + self->hp -= ap; + else + { + self->hp = 0; + self->alive = false; + } + + return !self->alive; +} + +void instance_cyclic(void) +{ + if (!line_g_flip) + { + if ((line_g += 5) >= UCHAR_MAX) + line_g_flip ^= true; + } + else if (!(line_g -= 5)) + line_g_flip ^= true; +} + +static int draw_sel(const struct instance *const i, const short x, const short y) +{ + struct stp_4line *const l = stp_4line_get(); + + if (!l) + return -1; + + enum {R = 0, G = 255, B = 0}; + + l->x = l->vertices[2].x = l->vertices[3].x = x; + l->y = l->vertices[0].y = l->vertices[3].y = y; + l->vertices[0].x = l->vertices[1].x = x + i->r.w; + l->vertices[1].y = l->vertices[2].y = y + i->r.h; + l->r = R; + l->g = line_g; + l->b = B >> 2; + + for (size_t i = 0; i < sizeof l->vertices / sizeof *l->vertices; i++) + { + struct stp_4line_vtx *const v = &l->vertices[i]; + + v->r = R; + v->b = B; + } + + l->vertices[0].g = l->vertices[2].g = UCHAR_MAX - line_g; + l->vertices[1].g = l->vertices[3].g = line_g; + stp_4line_sort(l); + + struct stp_4line *const ll = stp_4line_get(); + + if (!ll) + return -1; + + *ll = *l; + ll->x = ll->vertices[2].x = ll->vertices[3].x = l->x ? l->x - 1 : 0; + ll->y = ll->vertices[0].y = ll->vertices[3].y = l->y ? l->y - 1 : 0; + ll->vertices[0].x = ll->vertices[1].x = l->vertices[0].x + 1; + ll->vertices[1].y = ll->vertices[2].y = l->vertices[1].y + 1; + stp_4line_sort(ll); + return 0; +} + +static void render_sprite(struct sprite *const s, + const struct instance_render_off *const off, const short x, const short y) +{ + s->x = off ? x + off->x : x; + s->y = off ? y + off->y : y; + sprite_sort(s); +} + +static void render_quad(const struct instance_render_quad *const rq, + const struct instance_render_off *const off, const short x, const short y) +{ + struct quad *const q = rq->q; + const short x0 = x + off->x, x1 = x0 + rq->w - 1; + + if (rq->xflip) + { + q->x0 = q->x2 = x1; + q->x1 = q->x3 = x0; + q->u0 = q->u2 = rq->u; + q->u1 = q->u3 = rq->u + rq->w - 1; + } + else + { + q->x0 = q->x2 = x0; + q->x1 = q->x3 = x1; + q->u0 = q->u2 = rq->u; + q->u1 = q->u3 = rq->u + rq->w - 1; + } + + const short y0 = y + off->y, y1 = y0 + rq->h - 1; + + q->y0 = q->y1 = y0; + q->y2 = q->y3 = y1; + quad_sort(q); +} + +int instance_render(const struct instance_render_cfg *const cfg) +{ + const struct instance *const i = cfg->i; + short x, y; + + if (camera_translate(cfg->cam, &i->r, &x, &y)) + { + const struct instance_render_off *const off = cfg->off; + + if (cfg->sel && draw_sel(i, x, y)) + return -1; + + switch (cfg->prim_type) + { + case INSTANCE_RENDER_CFG_SPRITE: + render_sprite(cfg->prim.s, off, x, y); + break; + + case INSTANCE_RENDER_CFG_QUAD: + render_quad(cfg->prim.quad, off, x, y); + break; + } + } + + return 0; +} + +int instance_render_target(const struct instance *const i, + const struct camera *const cam) +{ + short x, y; + + if (camera_translate(cam, &i->r, &x, &y)) + return draw_sel(i, x, y); + + return -1; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..fd0759f --- /dev/null +++ b/src/main.c @@ -0,0 +1,14 @@ +#include <game.h> +#include <system.h> +#include <stdlib.h> + +int main(void) +{ + int ret = EXIT_SUCCESS; + + if (system_init() || game()) + ret = EXIT_FAILURE; + + system_deinit(); + return ret; +} diff --git a/src/pad/CMakeLists.txt b/src/pad/CMakeLists.txt new file mode 100644 index 0000000..4862c2b --- /dev/null +++ b/src/pad/CMakeLists.txt @@ -0,0 +1,10 @@ +add_library(pad "src/pad.c") +target_include_directories(pad PUBLIC "inc" PRIVATE "privinc") + +if(PS1_BUILD) + target_include_directories(pad PUBLIC "ps1/inc") + target_sources(pad PRIVATE "ps1/src/pad.c") +elseif(SDL1_2_BUILD) + target_include_directories(pad PUBLIC "sdl-1.2/inc") + target_sources(pad PRIVATE "sdl-1.2/src/pad.c") +endif() diff --git a/src/pad/inc/pad.h b/src/pad/inc/pad.h new file mode 100644 index 0000000..2579c84 --- /dev/null +++ b/src/pad/inc/pad.h @@ -0,0 +1,49 @@ +#ifndef PAD_H +#define PAD_H + +#include <pad/port.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define PAD_KEYS \ + X(PAD_KEY_LEFT) \ + X(PAD_KEY_RIGHT) \ + X(PAD_KEY_UP) \ + X(PAD_KEY_DOWN) \ + X(PAD_KEY_A) \ + X(PAD_KEY_B) \ + X(PAD_KEY_C) \ + X(PAD_KEY_D) \ + X(PAD_KEY_E) \ + X(PAD_KEY_OPTIONS) + +enum pad_key +{ +#define X(x) x, + PAD_KEYS +#undef X +}; + +struct pad +{ + int player; + int mask, oldmask; + struct pad_port port; +}; + +void pad_init(int player, struct pad *p); +void pad_update(struct pad *p); +bool pad_pressed(const struct pad *p, enum pad_key k); +bool pad_justpressed(const struct pad *p, enum pad_key k); +bool pad_released(const struct pad *p, enum pad_key k); +const char *pad_str(enum pad_key k); + +#ifdef __cplusplus +} +#endif + +#endif /* PAD_H */ diff --git a/src/pad/privinc/pad_private.h b/src/pad/privinc/pad_private.h new file mode 100644 index 0000000..095c37e --- /dev/null +++ b/src/pad/privinc/pad_private.h @@ -0,0 +1,17 @@ +#ifndef PAD_PRIVATE_H +#define PAD_PRIVATE_H + +#include <pad.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +void pad_port_update(struct pad *p); + +#ifdef __cplusplus +} +#endif + +#endif /* PAD_PRIVATE_H */ diff --git a/src/pad/ps1/inc/pad/port.h b/src/pad/ps1/inc/pad/port.h new file mode 100644 index 0000000..14dc784 --- /dev/null +++ b/src/pad/ps1/inc/pad/port.h @@ -0,0 +1,20 @@ +#ifndef PAD_PS1_H +#define PAD_PS1_H + +#include <psx.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct pad_port +{ + psx_pad_state pps; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* PAD_PS1_H */ diff --git a/src/pad/ps1/src/pad.c b/src/pad/ps1/src/pad.c new file mode 100644 index 0000000..eea56ed --- /dev/null +++ b/src/pad/ps1/src/pad.c @@ -0,0 +1,41 @@ +#include <pad.h> +#include <pad_private.h> +#include <pad/port.h> +#include <stddef.h> +#include <string.h> + +void pad_init(const int player, struct pad *const p) +{ + memset(p, 0, sizeof *p); + p->player = player; +} + +void pad_port_update(struct pad *const p) +{ + static const int psx_keys[] = + { + [PAD_KEY_LEFT] = PAD_LEFT, + [PAD_KEY_RIGHT] = PAD_RIGHT, + [PAD_KEY_UP] = PAD_UP, + [PAD_KEY_DOWN] = PAD_DOWN, + [PAD_KEY_A] = PAD_CROSS, + [PAD_KEY_B] = PAD_CIRCLE, + [PAD_KEY_C] = PAD_TRIANGLE, + [PAD_KEY_D] = PAD_SQUARE, + [PAD_KEY_E] = PAD_L1, + [PAD_KEY_OPTIONS] = PAD_START + }; + + p->mask = 0; + PSX_PollPad(p->player, &p->port.pps); + + for (size_t i = 0; i < sizeof psx_keys / sizeof *psx_keys; i++) + { + const int mask = 1 << i; + + if (p->port.pps.buttons & psx_keys[i]) + p->mask |= mask; + else + p->mask &= ~mask; + } +} diff --git a/src/pad/sdl-1.2/inc/pad/port.h b/src/pad/sdl-1.2/inc/pad/port.h new file mode 100644 index 0000000..b6ae85a --- /dev/null +++ b/src/pad/sdl-1.2/inc/pad/port.h @@ -0,0 +1,20 @@ +#ifndef PAD_SDL_1_2_H +#define PAD_SDL_1_2_H + +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct pad_port +{ + bool exit; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* PAD_SDL_1_2_H */ diff --git a/src/pad/sdl-1.2/src/pad.c b/src/pad/sdl-1.2/src/pad.c new file mode 100644 index 0000000..a062758 --- /dev/null +++ b/src/pad/sdl-1.2/src/pad.c @@ -0,0 +1,80 @@ +#include <pad.h> +#include <SDL/SDL.h> +#include <stdio.h> + +static void key_event(const SDL_KeyboardEvent *const key, struct pad *const p) +{ + static const int keys[] = + { + [PAD_KEY_LEFT] = SDLK_LEFT, + [PAD_KEY_RIGHT] = SDLK_RIGHT, + [PAD_KEY_UP] = SDLK_UP, + [PAD_KEY_DOWN] = SDLK_DOWN, + [PAD_KEY_A] = SDLK_x, + [PAD_KEY_B] = SDLK_d, + [PAD_KEY_C] = SDLK_w, + [PAD_KEY_D] = SDLK_a, + [PAD_KEY_E] = SDLK_q, + [PAD_KEY_OPTIONS] = SDLK_ESCAPE + }; + + for (size_t i = 0; i < sizeof keys / sizeof *keys; i++) + { + const SDLKey k = key->keysym.sym; + + if (k == keys[i]) + { + const int mask = 1 << i; + + if (key->type == SDL_KEYDOWN) + { + printf("%s pressed\n", pad_str(i)); + p->mask |= mask; + } + else + { + printf("%s released\n", pad_str(i)); + p->mask &= ~mask; + } + } + } +} + +void pad_port_update(struct pad *const p) +{ + SDL_Event ev; + static const int masks[] = {SDL_KEYEVENTMASK}; + int n; + + while ((n = SDL_PeepEvents(&ev, 1, SDL_GETEVENT, + masks[p->player] | SDL_QUITMASK)) > 0) + { + switch (ev.type) + { + case SDL_KEYDOWN: + /* Fall through. */ + case SDL_KEYUP: + key_event(&ev.key, p); + break; + + case SDL_QUIT: + p->port.exit = true; + break; + + default: + fprintf(stderr, "%s: unexpected SDL_Event %d\n", + __func__, ev.type); + break; + } + } + + if (n < 0) + { + fprintf(stderr, "%s: SDL_PeepEvents: %s\n", __func__, SDL_GetError()); + return; + } +} + +void pad_init(const int player, struct pad *const p) +{ +} diff --git a/src/pad/src/pad.c b/src/pad/src/pad.c new file mode 100644 index 0000000..b95a43e --- /dev/null +++ b/src/pad/src/pad.c @@ -0,0 +1,37 @@ +#include <pad.h> +#include <pad_private.h> +#include <pad/port.h> +#include <stdbool.h> + +bool pad_pressed(const struct pad *const p, const enum pad_key k) +{ + return p->mask & (1 << k); +} + +bool pad_justpressed(const struct pad *const p, const enum pad_key k) +{ + return p->mask & (1 << k) && !(p->oldmask & (1 << k)); +} + +bool pad_released(const struct pad *const p, const enum pad_key k) +{ + return !(p->mask & (1 << k)) && p->oldmask & (1 << k); +} + +void pad_update(struct pad *const p) +{ + p->oldmask = p->mask; + pad_port_update(p); +} + +const char *pad_str(const enum pad_key k) +{ + static const char *const s[] = + { +#define X(x) [x] = #x, + PAD_KEYS +#undef X + }; + + return s[k]; +} diff --git a/src/player/CMakeLists.txt b/src/player/CMakeLists.txt new file mode 100644 index 0000000..d7a275d --- /dev/null +++ b/src/player/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(player "src/player.c" "src/human_player.c") +target_include_directories(player PUBLIC "inc") +target_link_libraries(player + PUBLIC + building + camera + gfx + instance + pad + resource + tech + unit + util + PRIVATE + pad + gui) diff --git a/src/player/inc/human_player.h b/src/player/inc/human_player.h new file mode 100644 index 0000000..6d97cf7 --- /dev/null +++ b/src/player/inc/human_player.h @@ -0,0 +1,69 @@ +#ifndef HUMAN_PLAYER_H +#define HUMAN_PLAYER_H + +#include <camera.h> +#include <instance.h> +#include <pad.h> +#include <player.h> +#include <stdbool.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum {MAX_SELECTED_INSTANCES = 4}; + +struct human_player +{ + struct player pl; + struct camera cam; + struct pad pad; + + struct sel_instance + { + enum sel_type + { + INSTANCE_TYPE_UNIT, + INSTANCE_TYPE_BUILDING, + INSTANCE_TYPE_RESOURCE + } type; + + union sel_data + { + const struct unit *u; + const struct building *b; + const struct resource *r; + const struct instance *i; + struct unit *rw_u; + } d; + } sel[MAX_SELECTED_INSTANCES]; + + struct + { + const struct instance *ins; + unsigned char t, n; + bool render; + } target; + + size_t n_sel; + bool top_gui; + unsigned long gui_res[MAX_RESOURCE_TYPES]; +}; + +struct human_player_cfg +{ + struct player_cfg pl; + int padn; +}; + +int human_player_init(const struct human_player_cfg *cfg, struct human_player *h); +bool human_player_update(struct human_player *h, struct player_others *o); +int human_player_render(const struct human_player *h, const struct player_others *o); + +#ifdef __cplusplus +} +#endif + +#endif /* HUMAN_PLAYER_H */ diff --git a/src/player/inc/player.h b/src/player/inc/player.h new file mode 100644 index 0000000..25f4d81 --- /dev/null +++ b/src/player/inc/player.h @@ -0,0 +1,71 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#include <building.h> +#include <camera.h> +#include <instance.h> +#include <pad.h> +#include <resource.h> +#include <tech.h> +#include <unit.h> +#include <stdbool.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef unsigned int player_team; + +enum +{ + PLAYER_MAX_UNITS = 5, + PLAYER_MAX_BUILDINGS = 5 +}; + +struct player +{ + enum player_color + { + PLAYER_COLOR_BLUE, + PLAYER_COLOR_RED + } color; + + player_team team; + bool alive; + struct unit units[PLAYER_MAX_UNITS]; + struct building buildings[PLAYER_MAX_BUILDINGS]; + unsigned long resources[MAX_RESOURCE_TYPES]; + size_t pop, bpop; + + struct + { + struct unit_tech u; + } tree; +}; + +struct player_cfg +{ + enum player_color color; + player_team team; + unsigned long x, y; +}; + +struct player_others +{ + struct player *pl; + struct resource *res; + size_t n_pl, n_res; +}; + +int player_init(const struct player_cfg *cfg, struct player *pl); +int player_create_unit(const struct unit_cfg *cfg, struct player *pl); +int player_create_building(const struct building_cfg *cfg, struct player *pl); +void player_update(struct player *p); + +#ifdef __cplusplus +} +#endif + +#endif /* PLAYER_H */ diff --git a/src/player/src/human_player.c b/src/player/src/human_player.c new file mode 100644 index 0000000..7fd558a --- /dev/null +++ b/src/player/src/human_player.c @@ -0,0 +1,570 @@ +#include <human_player.h> +#include <player.h> +#include <building.h> +#include <camera.h> +#include <gui.h> +#include <instance.h> +#include <pad.h> +#include <resource.h> +#include <unit.h> +#include <util.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +static bool instance_selected(const struct human_player *const h, + const union sel_data *const sel) +{ + if (h->n_sel) + for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++) + { + const struct sel_instance *const si = &h->sel[i]; + + if (si && sel->u == si->d.u) + return true; + } + + return false; +} + +static bool select_units(struct human_player *const h, const short x, + const short y) +{ + struct player *const pl = &h->pl; + + for (size_t i = 0; i < sizeof pl->units / sizeof *pl->units; i++) + { + const struct unit *const u = &pl->units[i]; + const struct instance *const in = &u->instance; + + if (in->alive) + { + switch (u->state) + { + default: + { + const union sel_data d = {.u = u}; + + if (!instance_selected(h, &d) + && cursor_collision(&h->cam, &in->r) + && h->n_sel < sizeof h->sel / sizeof *h->sel) + { + for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++) + { + struct sel_instance *const sel = &h->sel[i]; + + if (!sel->d.u) + { + sel->type = INSTANCE_TYPE_UNIT; + sel->d.u = u; + h->n_sel++; + sfx_play(&unit_sounds[UNIT_SOUND_SELECTED]); + return true; + } + } + } + } + break; + + case UNIT_STATE_SHELTERED: + /* Fall through. */ + case UNIT_STATE_HARVESTING_GOLD: + break; + } + } + } + + return false; +} + +static bool select_buildings(struct human_player *const h, const short x, + const short y) +{ + struct player *const pl = &h->pl; + + for (size_t i = 0; i < sizeof pl->buildings / sizeof *pl->buildings; i++) + { + const struct building *const b = &pl->buildings[i]; + const struct instance *const in = &b->instance; + + if (in->alive) + { + const union sel_data d = {.b = b}; + + if (!instance_selected(h, &d) + && cursor_collision(&h->cam, &in->r) + && h->n_sel < sizeof h->sel / sizeof *h->sel) + { + for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++) + { + struct sel_instance *const sel = &h->sel[i]; + + if (!sel->d.b) + { + sel->type = INSTANCE_TYPE_BUILDING; + sel->d.b = b; + h->n_sel++; + return true; + } + } + } + } + } + + return false; +} + +static bool select_resources(struct human_player *const h, const short x, + const short y, const struct player_others *const o) +{ + for (size_t i = 0; i < o->n_res; i++) + { + const struct resource *const r = &o->res[i]; + const struct instance *const in = &r->instance; + + if (in->alive) + { + const union sel_data d = {.r = r}; + + if (!instance_selected(h, &d) + && cursor_collision(&h->cam, &in->r) + && h->n_sel < sizeof h->sel / sizeof *h->sel) + { + for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++) + { + struct sel_instance *const sel = &h->sel[i]; + + if (!sel->d.r) + { + sel->type = INSTANCE_TYPE_RESOURCE; + sel->d.r = r; + h->n_sel++; + return true; + } + } + } + } + } + + return false; +} + +static void select_instances(struct human_player *const h, + const struct player_others *const o) +{ + unsigned long x, y; + + cursor_pos(&h->cam, &x, &y); + + if (!select_buildings(h, x, y) + && !select_units(h, x, y)) + select_resources(h, x, y, o); +} + +static bool instance_collision(const struct camera *const cam, + const struct instance *const in) +{ + return in->alive && cursor_collision(cam, &in->r); +} + +static void resources_stored(struct instance *const i, void *const op) +{ + struct player *const p = op; + struct unit *const u = (struct unit *)i; + struct unit_harvester *const uh = &u->us.harvester; + + p->resources[uh->type] += uh->carry; + uh->carry = 0; + unit_set_target(u, &uh->prev_target); +} + +static void harvest_done(struct instance *const ins, void *const op) +{ + struct player *const p = op; + struct unit *const u = (struct unit *)ins; + struct resource *const res = (struct resource *)u->target.ins; + + if (res->type == RESOURCE_TYPE_GOLD) + { + struct resource_gold *const g = &res->res.gold; + + for (size_t i = 0; i < sizeof g->miners / sizeof *g->miners; i++) + { + struct instance **const pi = &g->miners[i]; + + if (*pi == ins) + { + *pi = NULL; + g->n_miners--; + break; + } + } + } + + for (size_t i = 0; i < sizeof p->buildings / sizeof *p->buildings; i++) + { + struct building *const b = &p->buildings[i]; + + if (b->instance.alive && b->type == BUILDING_TYPE_BARRACKS) + { + const struct unit_target t = + { + .ins = &b->instance, + .state = UNIT_STATE_CARRYING, + .done = resources_stored, + .op = p + /* TODO .state = UNIT_STATE_SHELTERED */ + }; + + u->us.harvester.prev_target = u->target; + unit_set_target(u, &t); + break; + } + } +} + +static bool target_from_own(struct player *const p, + const struct camera *const cam, struct unit_target *const t) +{ + for (size_t i = 0; i < sizeof p->buildings / sizeof *p->buildings; i++) + { + struct building *const b = &p->buildings[i]; + struct instance *const in = &b->instance; + + if (instance_collision(cam, in)) + { + *t = (const struct unit_target) + { + .ins = in, + /* TODO .state = UNIT_STATE_SHELTERED */ + }; + + return true; + } + } + + return false; +} + +static bool target_from_others(const struct player *const p, + const struct player_others *const o, + const struct camera *const cam, struct unit_target *const t) +{ + for (size_t i = 0; i < o->n_pl; i++) + { + struct player *const pl = &o->pl[i]; + + if (p->team != pl->team) + { + for (size_t i = 0; i < sizeof pl->units / sizeof *pl->units; i++) + { + struct unit *const u = &pl->units[i]; + struct instance *const in = &u->instance; + + if (instance_collision(cam, in)) + { + *t = (const struct unit_target) + { + .ins = in, + .attack = unit_attacked, + .state = UNIT_STATE_ATTACKING + }; + + return true; + } + } + + for (size_t i = 0; i < sizeof pl->buildings / sizeof *pl->buildings; i++) + { + struct building *const b = &pl->buildings[i]; + struct instance *const in = &b->instance; + + if (instance_collision(cam, in)) + { + *t = (const struct unit_target) + { + .ins = in, + .state = UNIT_STATE_ATTACKING + }; + + return true; + } + } + } + } + + return false; +} + +static enum unit_state target_state_from_res(const struct resource *const res) +{ + switch (res->type) + { + case RESOURCE_TYPE_GOLD: + return UNIT_STATE_HARVESTING_GOLD; + + case RESOURCE_TYPE_WOOD: + return UNIT_STATE_HARVESTING_WOOD; + + default: + break; + } + + return UNIT_STATE_IDLE_MOVING; +} + +static bool target_from_res(struct player *const p, + const struct player_others *const o, + const struct camera *const cam, struct unit_target *const t) +{ + for (size_t i = 0; i < o->n_res; i++) + { + struct resource *const res = &o->res[i]; + struct instance *const in = &res->instance; + + if (instance_collision(cam, in)) + { + *t = (const struct unit_target) + { + .ins = in, + .attack = resource_harvested, + .shelter = resource_shelter(res), + .state = target_state_from_res(res), + .done = harvest_done, + .op = p + }; + + return true; + } + } + + return false; +} + +static void set_target(struct human_player *const h, struct unit *const u, + const struct unit_target *const t) +{ + unit_set_target(u, t); + memset(&h->target, 0, sizeof h->target); + h->target.ins = t->ins; + h->target.render = true; +} + +static void target_from_pos(struct player *const p, + const struct player_others *const o, + const struct camera *const cam, struct unit_target *const t) +{ + if (!target_from_own(p, cam, t) + && !target_from_others(p, o, cam, t) + && !target_from_res(p, o, cam, t)) + t->ins = NULL; +} + +static void move_units(struct human_player *const h, + struct player_others *const o) +{ + if (!h->n_sel) + return; + + unsigned long x, y; + struct player *const p = &h->pl; + struct unit_target t; + + target_from_pos(p, o, &h->cam, &t); + cursor_pos(&h->cam, &x, &y); + + bool unit_move = false; + + for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++) + { + struct sel_instance *const si = &h->sel[i]; + + if (si->d.u && si->type == INSTANCE_TYPE_UNIT) + { + struct unit *const u = si->d.rw_u; + + if (!t.ins || !unit_target_valid(u, &t)) + unit_move_to(u, x, y); + else + set_target(h, u, &t); + + unit_move = true; + } + } + + if (unit_move) + { + const enum unit_sound s = rand() & 1 ? + UNIT_SOUND_MOVE : UNIT_SOUND_MOVE_2; + sfx_play(&unit_sounds[s]); + } +} + +static void deselect_instances(struct human_player *const h) +{ + memset(h->sel, 0, sizeof h->sel); + h->n_sel = 0; +} + +static void update_selected(struct human_player *const h) +{ + if (h->n_sel) + { + for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++) + { + struct sel_instance *const si = &h->sel[i]; + + if (si->d.i && (!si->d.i->alive || si->d.i->dying + || (si->type == INSTANCE_TYPE_UNIT + && (si->d.u->state == UNIT_STATE_HARVESTING_GOLD + || si->d.u->state == UNIT_STATE_SHELTERED)))) + { + si->d.i = NULL; + h->n_sel--; + } + } + } +} + +static void update_target(struct human_player *const h) +{ + const struct instance *const i = h->target.ins; + + if (i) + { + enum {TOGGLE = 10}; + + if (!i->alive) + memset(&h->target, 0, sizeof h->target); + else if (++h->target.t >= TOGGLE) + { + enum {TIMES = 5}; + + h->target.render ^= true; + h->target.t = 0; + + if (++h->target.n >= TIMES) + memset(&h->target, 0, sizeof h->target); + } + } +} + +bool human_player_update(struct human_player *const h, + struct player_others *const o) +{ + bool ret = false; + struct player *const p = &h->pl; + + if (p->alive) + { + gui_update(h); + pad_update(&h->pad); + update_selected(h); + update_target(h); + + if (pad_justpressed(&h->pad, PAD_KEY_OPTIONS)) + ret = true; + else if (pad_justpressed(&h->pad, PAD_KEY_A)) + select_instances(h, o); + else if (pad_justpressed(&h->pad, PAD_KEY_B)) + move_units(h, o); + else if (pad_justpressed(&h->pad, PAD_KEY_C)) + deselect_instances(h); + else if (pad_justpressed(&h->pad, PAD_KEY_E)) + h->top_gui ^= true; + + camera_update(&h->cam, &h->pad); + player_update(p); + } + + return ret; +} + +static int render_target(const struct human_player *const h) +{ + const struct instance *const i = h->target.ins; + + if (!i || !i->alive || !h->target.render) + return 0; + + return instance_render_target(i, &h->cam); +} + +static int render_own_units(const struct human_player *const h) +{ + const struct player *const p = &h->pl; + + for (size_t i = 0; i < sizeof p->units / sizeof *p->units; i++) + { + const struct unit *const u = &p->units[i]; + const union sel_data d = {.u = u}; + const bool sel = instance_selected(h, &d); + + if (unit_render(u, &h->cam, sel)) + return -1; + } + + return 0; +} + +static int render_own_buildings(const struct human_player *const h) +{ + const struct player *const p = &h->pl; + + for (size_t i = 0; i < sizeof p->buildings / sizeof *p->buildings; i++) + { + const struct building *const b = &p->buildings[i]; + const union sel_data d = {.b = b}; + const bool sel = instance_selected(h, &d); + + if (building_render(b, &h->cam, sel)) + return -1; + } + + return 0; +} + +static int render_resources(const struct human_player *const h, + const struct resource *const res, const size_t n) +{ + for (size_t i = 0; i < n; i++) + { + const struct resource *const r = &res[i]; + const union sel_data d = {.r = r}; + const bool sel = instance_selected(h, &d); + + if (resource_render(r, &h->cam, sel)) + return -1; + } + + return 0; +} + +int human_player_render(const struct human_player *const h, + const struct player_others *const o) +{ + if (render_target(h) + || render_own_units(h) + || render_own_buildings(h) + || render_resources(h, o->res, o->n_res) + || gui_render(h)) + return -1; + + return 0; +} + +int human_player_init(const struct human_player_cfg *const cfg, + struct human_player *const h) +{ + memset(h, 0, sizeof *h); + + if (player_init(&cfg->pl, &h->pl)) + return -1; + + pad_init(cfg->padn, &h->pad); + cursor_init(&h->cam.cursor); + h->top_gui = true; + memmove(h->gui_res, h->pl.resources, sizeof h->gui_res); + return 0; +} diff --git a/src/player/src/player.c b/src/player/src/player.c new file mode 100644 index 0000000..a638cb8 --- /dev/null +++ b/src/player/src/player.c @@ -0,0 +1,112 @@ +#include <player.h> +#include <building.h> +#include <unit.h> +#include <stddef.h> + +void player_update(struct player *const p) +{ + for (size_t i = 0; i < sizeof p->units / sizeof *p->units; i++) + { + struct unit *const u = &p->units[i]; + + unit_update(&p->tree.u, u); + } +} + +int player_create_unit(const struct unit_cfg *const cfg, struct player *const pl) +{ + enum {N = sizeof pl->units / sizeof *pl->units}; + + if (pl->pop < N) + { + for (size_t i = 0; i < N; i++) + { + struct unit *const u = &pl->units[i]; + struct instance *const i = &u->instance; + + if (!i->alive) + { + unit_create(cfg, u); + pl->pop++; + return 0; + } + } + } + + return -1; +} + +int player_create_building(const struct building_cfg *const cfg, struct player *const pl) +{ + enum {N = sizeof pl-> buildings / sizeof *pl->buildings}; + + if (pl->bpop < N) + { + for (size_t i = 0; i < N; i++) + { + struct building *const b = &pl->buildings[i]; + struct instance *const i = &b->instance; + + if (!i->alive) + { + building_create(cfg, b); + pl->bpop++; + return 0; + } + } + } + + return -1; +} + +int player_init(const struct player_cfg *const cfg, struct player *const pl) +{ + const unsigned long x = cfg->x, y = cfg->y; + + const struct building_cfg bcfg = + { + .type = BUILDING_TYPE_BARRACKS, + .x = x, + .y = y + }; + + const struct unit_cfg cfgs[] = + { + { + .type = UNIT_TYPE_PEASANT, + .x = x + 80, + .y = y + }, + + { + .type = UNIT_TYPE_PEASANT, + .x = x + 80, + .y = y + 40 + }, + + { + .type = UNIT_TYPE_PEASANT, + .x = x + 100, + .y = y + 20 + } + }; + + if (player_create_building(&bcfg, pl)) + return -1; + + for (size_t i = 0; i < sizeof cfgs / sizeof *cfgs; i++) + if (player_create_unit(&cfgs[i], pl)) + return -1; + + for (size_t i = 0; i < sizeof pl->resources / sizeof *pl->resources; i++) + { + enum {DEFAULT_RES = 120}; + + pl->resources[i] = DEFAULT_RES; + } + + pl->alive = true; + pl->color = cfg->color; + pl->team = cfg->team; + return 0; +} diff --git a/src/resource/CMakeLists.txt b/src/resource/CMakeLists.txt new file mode 100644 index 0000000..1591e58 --- /dev/null +++ b/src/resource/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(resource "src/resource.c") +target_include_directories(resource PUBLIC "inc") +target_link_libraries(resource PUBLIC camera gfx instance util) diff --git a/src/resource/inc/resource.h b/src/resource/inc/resource.h new file mode 100644 index 0000000..56da45e --- /dev/null +++ b/src/resource/inc/resource.h @@ -0,0 +1,57 @@ +#ifndef RESOURCE_H +#define RESOURCE_H + +#include <camera.h> +#include <container.h> +#include <gfx.h> +#include <instance.h> +#include <resource_type.h> +#include <util.h> +#include <stdbool.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum {RESOURCE_GOLD_CAPACITY = 2}; + +struct resource +{ + struct instance instance; + enum resource_type type; + + union + { + struct resource_gold + { + struct instance *miners[RESOURCE_GOLD_CAPACITY]; + size_t n_miners; + } gold; + } res; +}; + +UTIL_STATIC_ASSERT(!offsetof(struct resource, instance), "must be at offset zero"); + +struct resource_cfg +{ + enum resource_type type; + unsigned long x, y; +}; + +const struct container_list *resource_res(void); +int resource_create(const struct resource_cfg *cfg, struct resource *list, size_t n); +int resource_render(const struct resource *res, const struct camera *cam, bool sel); +instance_sheltered_cb resource_shelter(const struct resource *res); +bool resource_harvested(struct instance *i, instance_hp ap); +instance_hp resource_maxhp(const struct resource *res); +const char *resource_str(const struct resource *res); + +extern struct sprite resource_sprites[MAX_RESOURCE_TYPES]; + +#ifdef __cplusplus +} +#endif + +#endif /* RESOURCE_H */ diff --git a/src/resource/inc/resource_type.h b/src/resource/inc/resource_type.h new file mode 100644 index 0000000..8a41148 --- /dev/null +++ b/src/resource/inc/resource_type.h @@ -0,0 +1,21 @@ +#ifndef RESOURCE_TYPE_H +#define RESOURCE_TYPE_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum resource_type +{ + RESOURCE_TYPE_WOOD, + RESOURCE_TYPE_GOLD, + + MAX_RESOURCE_TYPES +}; + +#ifdef __cplusplus +} +#endif + +#endif /* RESOURCE_TYPE_H */ diff --git a/src/resource/src/resource.c b/src/resource/src/resource.c new file mode 100644 index 0000000..dd59bac --- /dev/null +++ b/src/resource/src/resource.c @@ -0,0 +1,139 @@ +#include <resource.h> +#include <gfx.h> +#include <instance.h> +#include <stdbool.h> +#include <stddef.h> + +struct sprite resource_sprites[MAX_RESOURCE_TYPES]; + +static bool gold_shelter(struct instance *const self, + struct instance *const other) +{ + struct resource *const res = (struct resource *)self; + struct resource_gold *const g = &res->res.gold; + + for (size_t i = 0; i < sizeof g->miners / sizeof *g->miners; i++) + { + struct instance **const ins = &g->miners[i]; + + if (!*ins) + { + *ins = other; + g->n_miners++; + return true; + } + } + + return false; +} + +instance_sheltered_cb resource_shelter(const struct resource *res) +{ + static const instance_sheltered_cb s[] = + { + [RESOURCE_TYPE_GOLD] = gold_shelter + }; + + return s[res->type]; +} + +bool resource_harvested(struct instance *const self, const instance_hp ap) +{ + return instance_attacked(self, ap); +} + +instance_hp resource_maxhp(const struct resource *const res) +{ + static const instance_hp hp[] = + { + [RESOURCE_TYPE_GOLD] = 1000, + [RESOURCE_TYPE_WOOD] = 45, + }; + + return hp[res->type]; +} + +int resource_render(const struct resource *const res, + const struct camera *const cam, const bool sel) +{ + const struct instance *const in = &res->instance; + + if (!in->alive) + return 0; + + struct sprite *const s = sprite_get(); + + if (!s || sprite_clone(&resource_sprites[res->type], s)) + return -1; + + if (res->type == RESOURCE_TYPE_GOLD) + { + s->h = in->r.h; + + if (res->res.gold.n_miners) + s->v += s->h; + } + + const struct instance_render_cfg cfg = + { + .i = &res->instance, + .prim_type = INSTANCE_RENDER_CFG_SPRITE, + .prim = {.s = s}, + .cam = cam, + .sel = sel, + .max_hp = resource_maxhp(res) + }; + + return instance_render(&cfg); +} + +static void get_dimensions(const enum resource_type type, short *const w, + short *const h) +{ + static const struct dim + { + short w, h; + } dim[] = + { + [RESOURCE_TYPE_GOLD] = {.w = 96, .h = 96}, + [RESOURCE_TYPE_WOOD] = {.w = 32, .h = 46} + }; + + const struct dim *const d = &dim[type]; + *w = d->w; + *h = d->h; +} + +int resource_create(const struct resource_cfg *const cfg, struct resource *const list, + const size_t n) +{ + for (size_t i = 0; i < n; i++) + { + struct resource *const r = &list[i]; + struct instance *const in = &r->instance; + + if (!in->alive) + { + get_dimensions(cfg->type, &in->r.w, &in->r.h); + r->type = cfg->type; + in->r.x = cfg->x; + in->r.y = cfg->y; + in->alive = true; + in->hp = resource_maxhp(r); + return 0; + } + } + + return -1; +} + +const char *resource_str(const struct resource *const res) +{ + static const char *const str[] = + { + [RESOURCE_TYPE_GOLD] = "Gold mine", + [RESOURCE_TYPE_WOOD] = "Pine tree" + }; + + return str[res->type]; +} diff --git a/src/sfx/CMakeLists.txt b/src/sfx/CMakeLists.txt new file mode 100644 index 0000000..3453d2e --- /dev/null +++ b/src/sfx/CMakeLists.txt @@ -0,0 +1,37 @@ +set(inc "inc") + +if(PS1_BUILD) + set(src + "ps1/src/sound.c") + set(inc ${inc} "ps1/inc") + set(privdeps system) +elseif(SDL1_2_BUILD) + set(src + "sdl-1.2/src/sound.c") + set(inc ${inc} "sdl-1.2/inc") + set(deps ${deps} SDL_mixer) + set(privdeps ${privdeps} SDL) +endif() + +add_library(sfx ${src}) +target_include_directories(sfx PUBLIC ${inc}) +target_include_directories(sfx PRIVATE ${privinc}) +target_link_libraries(sfx PUBLIC ${deps} PRIVATE ${privdeps}) + +if(PS1_BUILD) + set(modes VMODE_PAL VMODE_NTSC) + + if(VIDEO_MODE) + if(NOT "${VIDEO_MODE}" IN_LIST modes) + message(FATAL_ERROR "Invalid video mode ${VIDEO_MODE}. Available options:\n" + "${modes}\n" + "Run CMake again using one of the available video modes e.g.: cmake .. -DVIDEO_MODE=VMODE_PAL") + endif() + + target_compile_definitions(sfx PRIVATE VIDEO_MODE=${VIDEO_MODE}) + else() + message(FATAL_ERROR "Please define video mode. Available options:\n" + "${modes}\n" + "Run CMake again using one of the available video modes e.g.: cmake .. -DVIDEO_MODE=VMODE_PAL") + endif() +endif() diff --git a/src/sfx/inc/sfx.h b/src/sfx/inc/sfx.h new file mode 100644 index 0000000..b8fe44f --- /dev/null +++ b/src/sfx/inc/sfx.h @@ -0,0 +1,22 @@ +#ifndef SFX_H +#define SFX_H + +#include <sfx/port.h> +#include <stdio.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +int sfx_init(void); +int sfx_sound_from_fp(struct sound *s, FILE *f); +int sfx_play(const struct sound *s); +void sfx_free(struct sound *s); +void sfx_deinit(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SFX_H */ diff --git a/src/sfx/ps1/inc/sfx/port.h b/src/sfx/ps1/inc/sfx/port.h new file mode 100644 index 0000000..80c97e8 --- /dev/null +++ b/src/sfx/ps1/inc/sfx/port.h @@ -0,0 +1,17 @@ +#ifndef SFX_PS1_H +#define SFX_PS1_H + +#include <stddef.h> +#include <stdint.h> + +typedef uint32_t sfx_spu_addr; +typedef uint8_t sfx_spu_voice; + +struct sound +{ + sfx_spu_addr addr; + uint16_t sample_rate; + sfx_spu_voice voice; +}; + +#endif /* SFX_PS1_H */ diff --git a/src/sfx/ps1/src/sound.c b/src/sfx/ps1/src/sound.c new file mode 100644 index 0000000..e0377c7 --- /dev/null +++ b/src/sfx/ps1/src/sound.c @@ -0,0 +1,188 @@ +#include <sfx.h> +#include <sfx/port.h> +#include <psxspu.h> +#include <ctype.h> +#include <stddef.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> + +void sfx_free(struct sound *const s) +{ +} + +void sfx_deinit(void) +{ +} + +static int vag_ntohs(int n) +{ + return (n & 0xFF) << 24 + | (n & 0xFF00) << 16 + | (n & 0xFF0000) >> 8 + | (n & 0xFF000000) >> 24; +} + +static const char magic[] = "VAGp"; + +struct vag_header +{ + char magic_bytes[sizeof magic - 1]; + uint32_t version; + uint32_t :32; + uint32_t size; + uint32_t sample_rate; + uint8_t reserved[12]; + char name[16]; +}; + +static int get_header(struct vag_header *const h, FILE *const f) +{ + int ret = -1; + + if (!fread(h, sizeof *h, 1, f)) + { + fprintf(stderr, "VAG header not found\n"); + goto end; + } + else if (strncmp(h->magic_bytes, magic, sizeof h->magic_bytes)) + { + fprintf(stderr, "Expected magic bytes \"%s\", found: ", magic); + enum {N = sizeof h->magic_bytes / sizeof *h->magic_bytes}; + + for (size_t i = 0; i < N; i++) + { + const char c = h->magic_bytes[i]; + + fprintf(stderr, "[%zu] = %#x", i, c); + + if ((i + 1) < N) + fprintf(stderr, ", "); + + if (isalnum(c)) + fprintf(stderr, "(%c)", c); + } + + putchar('\n'); + goto end; + } + + /* File data is stored as big endian for some reason. */ + h->version = vag_ntohs(h->version); + h->sample_rate = vag_ntohs(h->sample_rate); + h->size = vag_ntohs(h->size); + ret = 0; + +end: + return ret; +} + +static sfx_spu_addr cur_addr = SPU_DATA_BASE_ADDR; + +static int upload_vag(const struct vag_header *const h, + struct sound *const s, FILE *const f) +{ + enum {SPU_RAM = 512 << 10}; + const sfx_spu_addr end_addr = cur_addr + h->size; + + if (end_addr >= SPU_RAM) + { + fprintf(stderr, "maximum SPU address exceeded (%#x, max %#x)\n", + end_addr, SPU_RAM); + return -1; + } + + s->addr = cur_addr; + s->sample_rate = h->sample_rate; + + for (size_t i = 0; i < h->size; /* See loop body. */) + { + /* From https://problemkaputt.de/psx-spx.htm#soundprocessingunitspu : + * "Sound RAM Data Transfer Fifo" supports up to 32 samples. */ + uint16_t hwords[32]; + const size_t rem = h->size - i; + const size_t n = rem > sizeof hwords ? sizeof hwords : rem; + + if (!fread(&hwords, n, 1, f)) + { + fprintf(stderr, "%s: failed to read %zu bytes\n", __func__, n); + return -1; + } + + SsUpload(&hwords, n, cur_addr); + cur_addr += n; + i += n; + } + +#if 0 + const unsigned used = cur_addr - SPU_DATA_BASE_ADDR; + printf("SPU RAM: %u bytes (%u%%)\n", used, (used * 100) / (SPU_RAM - SPU_DATA_BASE_ADDR)); +#endif + return 0; +} + +typedef volatile struct +{ + uint16_t l_vol, r_vol; + uint16_t adpcm_sample_rate; + uint16_t adpcm_start_addr; + + union + { + struct + { + uint32_t sustain_lvl :4; + uint32_t decay_shift :4; + uint32_t attack_step :2; + uint32_t attack_shift :5; + uint32_t attack_mode :1; + uint32_t release_shift :5; + uint32_t release_mode :1; + uint32_t sustain_step :2; + uint32_t sustain_shift :5; + uint32_t :1; + uint32_t sustain_dir :1; + uint32_t sustain_mode :1; + } bit; + + uint32_t mask; + } adsr; + + uint16_t adsr_vol; + uint16_t adsr_repeat_addr; +} *spu_voice; + +#define SPU_VOICE ((spu_voice)0x1f801c00) + +int sfx_play(const struct sound *const s) +{ + const sfx_spu_voice voice = s->voice; + + SPU_VOICE[voice].adpcm_sample_rate = SsFreqToPitch(s->sample_rate); + SPU_VOICE[voice].adpcm_start_addr = s->addr >> 3; + SPU_VOICE[voice].l_vol = 0x3FFF; + SPU_VOICE[voice].r_vol = 0x3FFF; + + if (voice > sizeof SPU_KEY_ON1 * 8) + SPU_KEY_ON2 = 1 << (voice - sizeof SPU_KEY_ON1 * 8); + else + SPU_KEY_ON1 = 1 << voice; + + return 0; +} + +int sfx_sound_from_fp(struct sound *const s, FILE *const f) +{ + struct vag_header h; + + if (get_header(&h, f) || upload_vag(&h, s, f)) + return -1; + + return 0; +} + +int sfx_init(void) +{ + SsInit(); + return 0; +} diff --git a/src/sfx/sdl-1.2/inc/sfx/port.h b/src/sfx/sdl-1.2/inc/sfx/port.h new file mode 100644 index 0000000..d8418f7 --- /dev/null +++ b/src/sfx/sdl-1.2/inc/sfx/port.h @@ -0,0 +1,11 @@ +#ifndef SFX_SDL1_2_H +#define SFX_SDL1_2_H + +#include <SDL/SDL_mixer.h> + +struct sound +{ + Mix_Chunk *chunk; +}; + +#endif /* SFX_SDL1_2_H */ diff --git a/src/sfx/sdl-1.2/src/sound.c b/src/sfx/sdl-1.2/src/sound.c new file mode 100644 index 0000000..e843923 --- /dev/null +++ b/src/sfx/sdl-1.2/src/sound.c @@ -0,0 +1,71 @@ +#include <sfx.h> +#include <sfx/port.h> +#include <SDL/SDL.h> +#include <SDL/SDL_mixer.h> +#include <stdio.h> + +void sfx_free(struct sound *const s) +{ + if (s && s->chunk) + Mix_FreeChunk(s->chunk); +} + +int sfx_play(const struct sound *const s) +{ + if (Mix_PlayChannel(-1, s->chunk, 0) < 0) + return -1; + + return 0; +} + +int sfx_sound_from_fp(struct sound *const s, FILE *const f) +{ + int ret = -1; + SDL_RWops *const ops = SDL_RWFromFP(f, 0); + + if (!ops) + { + fprintf(stderr, "SDL_RWFromFP: %s\n", SDL_GetError()); + goto end; + } + else if (!(s->chunk = Mix_LoadWAV_RW(ops, 0))) + { + fprintf(stderr, "Mix_LoadWAV_RW: %s\n", SDL_GetError()); + goto end; + } + + ret = 0; + +end: + SDL_FreeRW(ops); + return ret; +} + +void sfx_deinit(void) +{ + Mix_CloseAudio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); +} + +int sfx_init(void) +{ + enum {CHUNK_SZ = 4096}; + + if (SDL_InitSubSystem(SDL_INIT_AUDIO)) + { + fprintf(stderr, "SDL_InitSubSystem: %s\n", SDL_GetError()); + goto failure; + } + else if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, + MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS, CHUNK_SZ)) + { + fprintf(stderr, "Mix_OpenAudio: %s\n", SDL_GetError()); + goto failure; + } + + return 0; + +failure: + sfx_deinit(); + return -1; +} diff --git a/src/system.cnf b/src/system.cnf new file mode 100644 index 0000000..c1c0208 --- /dev/null +++ b/src/system.cnf @@ -0,0 +1,4 @@ +BOOT = cdrom:\rts.exe;1 +TCB = 4 +EVENT = 8 +STACK = 801FF800 diff --git a/src/system/CMakeLists.txt b/src/system/CMakeLists.txt new file mode 100644 index 0000000..679d5b4 --- /dev/null +++ b/src/system/CMakeLists.txt @@ -0,0 +1,13 @@ +set(inc "inc") + +if(PS1_BUILD) + set(src "ps1/src/init.c") + set(inc ${inc} "ps1/inc") +elseif(SDL1_2_BUILD) + set(src "sdl-1.2/src/system.c") + set(inc ${inc} "sdl-1.2/inc") +endif() + +add_library(system ${src}) +target_include_directories(system PUBLIC ${inc}) +target_link_libraries(system PRIVATE gfx sfx) diff --git a/src/system/inc/system.h b/src/system/inc/system.h new file mode 100644 index 0000000..5338a0b --- /dev/null +++ b/src/system/inc/system.h @@ -0,0 +1,17 @@ +#ifndef SYSTEM_H +#define SYSTEM_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +int system_init(void); +void system_deinit(void); +void system_loop(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SYSTEM_H */ diff --git a/src/system/ps1/inc/system/port.h b/src/system/ps1/inc/system/port.h new file mode 100644 index 0000000..395097e --- /dev/null +++ b/src/system/ps1/inc/system/port.h @@ -0,0 +1,17 @@ +#ifndef INIT_PS1_H +#define INIT_PS1_H + +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern volatile bool vblank_set; + +#ifdef __cplusplus +} +#endif + +#endif /* INIT_PS1_H */ diff --git a/src/system/ps1/src/init.c b/src/system/ps1/src/init.c new file mode 100644 index 0000000..a644e47 --- /dev/null +++ b/src/system/ps1/src/init.c @@ -0,0 +1,32 @@ +#include <gfx.h> +#include <sfx.h> +#include <system.h> +#include <psx.h> +#include <stdbool.h> + +volatile bool vblank_set; + +static void vblank(void *const arg) +{ + vblank_set = true; +} + +void system_deinit(void) +{ + gfx_deinit(); + sfx_deinit(); +} + +int system_init(void) +{ + SetVBlankHandler(vblank); + + if (gfx_init() || sfx_init()) + return -1; + + return 0; +} + +void system_loop(void) +{ +} diff --git a/src/system/sdl-1.2/inc/system/port.h b/src/system/sdl-1.2/inc/system/port.h new file mode 100644 index 0000000..3838026 --- /dev/null +++ b/src/system/sdl-1.2/inc/system/port.h @@ -0,0 +1,13 @@ +#ifndef INIT_SDL_1_2_H +#define INIT_SDL_1_2_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* INIT_SDL_H */ diff --git a/src/system/sdl-1.2/src/system.c b/src/system/sdl-1.2/src/system.c new file mode 100644 index 0000000..a738a75 --- /dev/null +++ b/src/system/sdl-1.2/src/system.c @@ -0,0 +1,36 @@ +#include <gfx.h> +#include <sfx.h> +#include <system.h> +#include <SDL/SDL.h> +#include <stdio.h> +#include <stdlib.h> + +void system_loop(void) +{ + SDL_PumpEvents(); +} + +void system_deinit(void) +{ + gfx_deinit(); + sfx_deinit(); + SDL_Quit(); +} + +int system_init(void) +{ + if (SDL_Init(0)) + { + fprintf(stderr, "SDL_Init: %s\n", SDL_GetError()); + goto failure; + } + else if (gfx_init() || sfx_init()) + goto failure; + + SDL_WM_SetCaption("rts", NULL); + return 0; + +failure: + system_deinit(); + return -1; +} diff --git a/src/tech/CMakeLists.txt b/src/tech/CMakeLists.txt new file mode 100644 index 0000000..1fd6963 --- /dev/null +++ b/src/tech/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(tech INTERFACE) +target_include_directories(tech INTERFACE "inc") diff --git a/src/tech/inc/tech.h b/src/tech/inc/tech.h new file mode 100644 index 0000000..071515f --- /dev/null +++ b/src/tech/inc/tech.h @@ -0,0 +1,20 @@ +#ifndef TECH_H +#define TECH_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum tech_level +{ + TECH_LEVEL_1, + TECH_LEVEL_2, + TECH_LEVEL_3 +}; + +#ifdef __cplusplus +} +#endif + +#endif /* TECH_H */ diff --git a/src/terrain/CMakeLists.txt b/src/terrain/CMakeLists.txt new file mode 100644 index 0000000..c74de94 --- /dev/null +++ b/src/terrain/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(terrain "src/terrain.c") +target_include_directories(terrain PUBLIC "inc") +target_link_libraries(terrain PUBLIC container camera gfx) diff --git a/src/terrain/inc/terrain.h b/src/terrain/inc/terrain.h new file mode 100644 index 0000000..9552555 --- /dev/null +++ b/src/terrain/inc/terrain.h @@ -0,0 +1,41 @@ +#ifndef TERRAIN_H +#define TERRAIN_H + +#include <camera.h> +#include <gfx.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum +{ + MAP_TILES = 64, + TERRAIN_SZ = 32, + MAP_X = MAP_TILES * TERRAIN_SZ, + MAP_Y = MAP_TILES * TERRAIN_SZ +}; + +enum terrain_type +{ + TERRAIN_TYPE_GRASS +}; + +struct terrain_map +{ + enum terrain_type m[MAP_TILES][MAP_TILES]; + int nx, ny; +}; + +void terrain_init(struct terrain_map *map); +int terrain_render(const struct terrain_map *map, const struct camera *cam); + +extern struct sprite grass_sprite; + +#ifdef __cplusplus +} +#endif + +#endif /* TERRAIN_H */ diff --git a/src/terrain/src/terrain.c b/src/terrain/src/terrain.c new file mode 100644 index 0000000..04df6e6 --- /dev/null +++ b/src/terrain/src/terrain.c @@ -0,0 +1,67 @@ +#include <terrain.h> +#include <camera.h> +#include <gfx.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +struct sprite grass_sprite; + +int terrain_render(const struct terrain_map *const map, + const struct camera *const cam) +{ + const int start_x = abs(cam->x / TERRAIN_SZ), + start_y = abs(cam->y / TERRAIN_SZ); + + const int remx = cam->x % TERRAIN_SZ, + remy = cam->y % TERRAIN_SZ; + + int nx = map->nx, ny = map->ny; + + if (abs(remx) >= TERRAIN_SZ / 2) + nx++; + + if (abs(remy) >= TERRAIN_SZ / 2) + ny++; + + struct m + { + size_t i; + int p; + }; + + for (struct m x = {.i = start_x, .p = remx}; + x.i < nx + start_x; x.i++, x.p += TERRAIN_SZ) + for (struct m y = {.i = start_y, .p = remy}; + y.i < ny + start_y; y.i++, y.p += TERRAIN_SZ) + { + struct sprite *const s = sprite_get(); + + if (!s) + return -1; + + switch (map->m[y.i][x.i]) + { + case TERRAIN_TYPE_GRASS: + if (sprite_clone(&grass_sprite, s)) + return -1; + + break; + } + + s->x = x.p; + s->y = y.p; + sprite_sort(s); + } + + return 0; +} + +void terrain_init(struct terrain_map *const map) +{ + const int extra = !!(screen_w % TERRAIN_SZ); + + memset(map, 0, sizeof *map); + map->nx = screen_w / TERRAIN_SZ + extra; + map->ny = screen_h / TERRAIN_SZ + extra; +} diff --git a/src/unit/CMakeLists.txt b/src/unit/CMakeLists.txt new file mode 100644 index 0000000..460cf43 --- /dev/null +++ b/src/unit/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(unit "src/unit.c") +target_include_directories(unit PUBLIC "inc") +target_link_libraries(unit PUBLIC fixmath container camera gfx instance resource sfx tech util) diff --git a/src/unit/inc/unit.h b/src/unit/inc/unit.h new file mode 100644 index 0000000..4a5ace7 --- /dev/null +++ b/src/unit/inc/unit.h @@ -0,0 +1,134 @@ +#ifndef UNIT_H +#define UNIT_H + +#include <camera.h> +#include <gfx.h> +#include <sfx.h> +#include <instance.h> +#include <resource_type.h> +#include <tech.h> +#include <unit_type.h> +#include <util.h> +#include <fixmath.h> +#include <stdbool.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct unit_tech +{ + enum tech_level carry; +}; + +enum unit_state +{ + UNIT_STATE_IDLE_MOVING, + UNIT_STATE_SHELTERED, + UNIT_STATE_HARVESTING_WOOD, + UNIT_STATE_HARVESTING_GOLD, + UNIT_STATE_CARRYING, + UNIT_STATE_ATTACKING +}; + +struct unit_target +{ + struct instance *ins; + enum unit_state state; + instance_sheltered_cb shelter; + instance_attacked_cb attack; + instance_done_cb done; + void *op; +}; + +struct unit +{ + struct instance instance; + enum unit_type type; + + enum unit_dir + { + UNIT_DIR_N, + UNIT_DIR_NE, + UNIT_DIR_E, + UNIT_DIR_SE, + UNIT_DIR_S, + UNIT_DIR_SW, + UNIT_DIR_W, + UNIT_DIR_NW, + + MAX_UNIT_DIRECTIONS + } dir; + + enum unit_state state; + + struct + { + unsigned char t, i; + } frame; + + union + { + struct unit_harvester + { + enum resource_type type; + unsigned char carry, t; + struct unit_target prev_target; + } harvester; + } us; + + fix16_t rx, ry, tx, ty; + + struct unit_target target; +}; + +UTIL_STATIC_ASSERT(!offsetof(struct unit, instance), "must be at offset zero"); + +struct unit_cfg +{ + enum unit_type type; + unsigned long x, y; +}; + +void unit_create(const struct unit_cfg *cfg, struct unit *u); +int unit_render(const struct unit *u, const struct camera *cam, bool sel); +bool unit_can_harvest(const struct unit *u); +bool unit_target_valid(const struct unit *u, const struct unit_target *t); +void unit_set_target(struct unit *u, const struct unit_target *t); +void unit_move_to(struct unit *u, unsigned long x, unsigned long y); +bool unit_attacked(struct instance *, instance_hp ap); +void unit_update(const struct unit_tech *t, struct unit *u); +instance_hp unit_maxhp(const struct unit *u); +const char *unit_str(const struct unit *u); + +enum +{ + UNIT_SPRITE_N, + UNIT_SPRITE_NE, + UNIT_SPRITE_E, + UNIT_SPRITE_SE, + UNIT_SPRITE_S, + + MAX_UNIT_SPRIES +}; + +extern struct sprite unit_sprites[MAX_UNIT_SPRIES]; + +enum unit_sound +{ + UNIT_SOUND_SELECTED, + UNIT_SOUND_MOVE, + UNIT_SOUND_MOVE_2, + + MAX_UNIT_SOUNDS +}; + +extern struct sound unit_sounds[MAX_UNIT_SOUNDS]; + +#ifdef __cplusplus +} +#endif + +#endif /* UNIT_H */ diff --git a/src/unit/inc/unit_type.h b/src/unit/inc/unit_type.h new file mode 100644 index 0000000..fddd5c9 --- /dev/null +++ b/src/unit/inc/unit_type.h @@ -0,0 +1,11 @@ +#ifndef UNIT_TYPE_H +#define UNIT_TYPE_H + +enum unit_type +{ + UNIT_TYPE_PEASANT, + + MAX_UNIT_TYPES +}; + +#endif /* UNIT_TYPE_H */ diff --git a/src/unit/src/unit.c b/src/unit/src/unit.c new file mode 100644 index 0000000..bb8e243 --- /dev/null +++ b/src/unit/src/unit.c @@ -0,0 +1,660 @@ +#include <unit.h> +#include <camera.h> +#include <gfx.h> +#include <sfx.h> +#include <fixmath.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +struct sprite unit_sprites[MAX_UNIT_SPRIES]; +struct sound unit_sounds[MAX_UNIT_SOUNDS]; + +enum {N_FRAMES = 5}; + +static void move_unit(struct unit *const u, const fix16_t sx, const fix16_t sy) +{ + switch (u->dir) + { + case UNIT_DIR_N: + u->ry -= sy; + break; + + case UNIT_DIR_NE: + u->rx += sx; + u->ry -= sy; + break; + + case UNIT_DIR_E: + u->rx += sx; + break; + + case UNIT_DIR_SE: + u->rx += sx; + u->ry += sy; + break; + + case UNIT_DIR_S: + u->ry += sy; + break; + + case UNIT_DIR_SW: + u->rx -= sx; + u->ry += sy; + break; + + case UNIT_DIR_W: + u->rx -= sx; + break; + + case UNIT_DIR_NW: + u->rx -= sx; + u->ry -= sy; + break; + + default: + break; + } +} + +static void get_speed(const struct unit *const u, fix16_t *const x, + fix16_t *const y) +{ + static const struct speed + { + fix16_t x, y; + } speed[] = + { + [UNIT_TYPE_PEASANT] = + { + .x = FIX16_C_FROM_INT(1), + .y = FIX16_C_FROM_INT(1) + } + }; + + const struct speed *const s = &speed[u->type]; + const int dx = abs(u->rx - u->tx); + const int dy = abs(u->ry - u->ty); + + *x = dx < s->x ? dx : s->x; + *y = dy < s->y ? dy : s->y; +} + +static enum unit_dir get_direction(const struct unit *const u) +{ + const fix16_t x = u->rx, y = u->ry, tx = u->tx, ty = u->ty; + enum unit_dir dir = 0; + + if (x != tx && y != ty) + { + if (x > tx && y > ty) + dir = UNIT_DIR_NW; + else if (x < tx && y > ty) + dir = UNIT_DIR_NE; + else if (x < tx && y < ty) + dir = UNIT_DIR_SE; + else + dir = UNIT_DIR_SW; + } + else if (x != tx) + { + if (x > tx) + dir = UNIT_DIR_W; + else + dir = UNIT_DIR_E; + } + else if (y != ty) + { + if (y > ty) + dir = UNIT_DIR_N; + else + dir = UNIT_DIR_S; + } + + return dir; +} + +static instance_hp attack_points(const struct unit *const u) +{ + static instance_hp ap[] = + { + [UNIT_TYPE_PEASANT] = 1 + }; + + return ap[u->type]; +} + +static void unit_stop(struct unit *const u) +{ + u->tx = u->rx; + u->ty = u->ry; +} + +static void target_reset(struct unit *const u) +{ + memset(&u->target, 0, sizeof u->target); + u->state = UNIT_STATE_IDLE_MOVING; + unit_stop(u); +} + +static void unit_chase(const struct instance *const i, struct unit *const u) +{ + u->tx = fix16_from_int(i->r.x + (i->r.w >> 1)); + u->ty = fix16_from_int(i->r.y + (i->r.h >> 1)); + u->state = UNIT_STATE_IDLE_MOVING; +} + +static void target_interact(struct unit *const u) +{ + struct unit_target *const t = &u->target; + const struct instance *const ins = t->ins; + + /* TODO: lose u->ti if not visible. */ + if (ins->alive) + { + if (t->state != u->state) + { + struct instance *const ui = &u->instance; + + if (util_collision(&ins->r, &ui->r)) + { + switch (t->state) + { + case UNIT_STATE_CARRYING: + u->target.done(&u->instance, u->target.op); + break; + + case UNIT_STATE_ATTACKING: + t->attack(t->ins, attack_points(u)); + u->state = t->state; + break; + + case UNIT_STATE_HARVESTING_WOOD: + { + struct unit_harvester *const uh = &u->us.harvester; + + if (uh->type != RESOURCE_TYPE_WOOD) + uh->carry = 0; + + uh->type = RESOURCE_TYPE_WOOD; + u->state = t->state; + unit_stop(u); + } + break; + + case UNIT_STATE_HARVESTING_GOLD: + { + struct unit_harvester *const uh = &u->us.harvester; + + if (uh->type != RESOURCE_TYPE_GOLD) + uh->carry = 0; + + uh->type = RESOURCE_TYPE_GOLD; + + if (t->shelter(t->ins, ui)) + u->state = t->state; + else + unit_stop(u); + } + break; + + case UNIT_STATE_SHELTERED: + u->state = t->state; + break; + + default: + break; + } + } + else + unit_chase(ins, u); + } + } + else + target_reset(u); +} + +static bool must_move(const struct unit *const u) +{ + return u->rx != u->tx || u->ry != u->ty; +} + +static void update_harvest(const struct unit_tech *const t, struct unit *const u) +{ + static const unsigned char carry[] = + { + [TECH_LEVEL_1] = 10, + [TECH_LEVEL_2] = 15, + [TECH_LEVEL_3] = 25 + }; + + static const char inc[] = + { + [TECH_LEVEL_1] = 10, + [TECH_LEVEL_2] = 50, + [TECH_LEVEL_3] = 25 + }; + + struct unit_harvester *const uh = &u->us.harvester; + bool ret = false; + + if (++uh->t >= inc[t->carry]) + { + if (uh->carry < carry[t->carry]) + { + ++uh->carry; + ret = u->target.attack(u->target.ins, 1); + } + + uh->t = 0; + } + + if (ret || uh->carry >= carry[t->carry]) + { + u->state = UNIT_STATE_CARRYING; + u->target.done(&u->instance, u->target.op); + } +} + +void unit_update(const struct unit_tech *const t, struct unit *const u) +{ + const struct instance *const i = &u->instance; + + if (i->alive) + { + if (u->target.ins) + target_interact(u); + + switch (u->state) + { + case UNIT_STATE_SHELTERED: + break; + + case UNIT_STATE_HARVESTING_GOLD: + /* Fall through. */ + case UNIT_STATE_HARVESTING_WOOD: + update_harvest(t, u); + break; + + case UNIT_STATE_ATTACKING: + if (must_move(u)) + u->state = UNIT_STATE_IDLE_MOVING; + + break; + + case UNIT_STATE_CARRYING: + /* Fall through. */ + case UNIT_STATE_IDLE_MOVING: + + if (must_move(u)) + { + fix16_t x_step, y_step; + + u->dir = get_direction(u); + get_speed(u, &x_step, &y_step); + move_unit(u, x_step, y_step); + + enum {FRAME_RATE = 6}; + + if (++u->frame.t >= FRAME_RATE) + { + u->frame.t = 0; + + if (++u->frame.i >= N_FRAMES) + u->frame.i = 0; + } + + u->state = UNIT_STATE_IDLE_MOVING; + } + else + u->frame.i = 0; + + u->instance.r.x = fix16_to_int(u->rx); + u->instance.r.y = fix16_to_int(u->ry); + break; + } + } +} + +bool can_attack(const struct unit *const u) +{ + static const bool a[] = + { + [UNIT_TYPE_PEASANT] = true + }; + + return a[u->type]; +} + +static bool can_shelter(const struct unit *const u) +{ + static const bool s[] = + { + [UNIT_TYPE_PEASANT] = true + }; + + return s[u->type]; +} + +bool unit_can_harvest(const struct unit *const u) +{ + static const bool h[] = + { + [UNIT_TYPE_PEASANT] = true + }; + + return h[u->type]; +} + +bool unit_attacked(struct instance *const i, const instance_hp ap) +{ + return instance_attacked(i, ap); +} + +bool unit_target_valid(const struct unit *const u, + const struct unit_target *const t) +{ + switch (t->state) + { + case UNIT_STATE_HARVESTING_GOLD: + /* Fall through. */ + case UNIT_STATE_HARVESTING_WOOD: + return unit_can_harvest(u); + + case UNIT_STATE_ATTACKING: + return can_attack(u); + + case UNIT_STATE_SHELTERED: + if (t->shelter) + return can_shelter(u); + else + break; + + case UNIT_STATE_IDLE_MOVING: + /* Fall through. */ + case UNIT_STATE_CARRYING: + break; + } + + return false; +} + +void unit_set_target(struct unit *const u, const struct unit_target *const t) +{ + const struct instance *const ti = t->ins; + + u->target = *t; + unit_chase(ti, u); +} + +void unit_move_to(struct unit *const u, const unsigned long x, const unsigned long y) +{ + const struct instance *const i = &u->instance; + const unsigned long x_off = i->r.w >> 1; + const unsigned long y_off = i->r.h >> 1; + + u->target.ins = NULL; + u->state = UNIT_STATE_IDLE_MOVING; + u->tx = x > x_off ? fix16_from_int(x - x_off) : 0; + u->ty = y > y_off ? fix16_from_int(y - y_off) : 0; +} + +instance_hp unit_maxhp(const struct unit *const u) +{ + static const instance_hp hp[] = + { + [UNIT_TYPE_PEASANT] = 25 + }; + + return hp[u->type]; +} + +static int get_ux(const struct unit *const u) +{ + switch (u->dir) + { + case UNIT_DIR_N: + return 0; + + case UNIT_DIR_NE: + /* Fall through. */ + case UNIT_DIR_NW: + return 1; + + case UNIT_DIR_E: + /* Fall through. */ + case UNIT_DIR_W: + return 2; + + case UNIT_DIR_SE: + /* Fall through. */ + case UNIT_DIR_SW: + return 3; + + case UNIT_DIR_S: + return 4; + + default: + break; + } + + return -1; +} + +typedef const struct +{ + const struct sprite *s; + char xo, x[N_FRAMES], w[N_FRAMES]; + short y; + short h; +} anim_dim; + +static anim_dim *peasant_anim(const struct unit *const u) +{ + static anim_dim t[] = + { + { + .s = &unit_sprites[UNIT_SPRITE_N], + .xo = 5, + .x = {0, 1, 1, 2, 1}, + .w = {25, 22, 24, 22, 24}, + .y = 2, + .h = 31 + }, + + { + .s = &unit_sprites[UNIT_SPRITE_NE], + .xo = 11, + .x = {0, -4, -1, -4, -2}, + .w = {18, 26, 22, 23, 20}, + .y = 2, + .h = 31 + }, + + { + .s = &unit_sprites[UNIT_SPRITE_E], + .xo = 10, + .x = {0, -6, -1, -6, -3}, + .w = {14, 26, 17, 24, 19}, + .y = 2, + .h = 33 + }, + + { + .s = &unit_sprites[UNIT_SPRITE_SE], + .xo = 6, + .x = {0, 1, 2, 0, 0}, + .w = {20, 22, 18, 21, 21}, + .y = 2, + .h = 31 + }, + + { + .s = &unit_sprites[UNIT_SPRITE_S], + .xo = 7, + .x = {0, 1, 0, 0, 0}, + .w = {24, 23, 24, 23, 24}, + .y = 2, + .h = 33 + } + }; + + const int ux = get_ux(u); + + if (ux < 0) + return NULL; + + return &t[ux]; +} + +struct render_cfg +{ + struct instance_render_off off; + struct instance_render_quad qcfg; +}; + +static anim_dim *peasant_quad(const struct unit *const u) +{ + return peasant_anim(u); +} + +static void adjust_quad(const struct unit *const u, anim_dim *const dim, + struct render_cfg *const rcfg) +{ + const unsigned char n = u->frame.i; + short u_off = 0; + + for (unsigned char i = 0; i < n; i++) + u_off += dim->w[i]; + + struct instance_render_quad *const qcfg = &rcfg->qcfg; + + qcfg->u = u_off; + qcfg->w = dim->w[n]; + qcfg->h = dim->h; + + struct instance_render_off *const off = &rcfg->off; + + off->x = dim->xo; + off->y = dim->y; + + switch (u->dir) + { + case UNIT_DIR_SW: + /* Fall through. */ + case UNIT_DIR_W: + /* Fall through. */ + case UNIT_DIR_NW: + qcfg->xflip = true; + off->x += dim->x[n]; + break; + + default: + qcfg->xflip = false; + off->x += dim->x[n]; + break; + } +} + +static int unit_quad(const struct unit *const u, struct render_cfg *const rcfg) +{ + struct instance_render_quad *const qcfg = &rcfg->qcfg; + + qcfg->q = quad_get(); + + if (!qcfg->q) + return -1; + + static anim_dim *(*const f[])(const struct unit *) = + { + [UNIT_TYPE_PEASANT] = peasant_quad + }; + + anim_dim *const dim = f[u->type](u); + + if (!dim) + return -1; + + adjust_quad(u, dim, rcfg); + + if (quad_from_sprite(dim->s, qcfg->q)) + return -1; + + return 0; +} + +int unit_render(const struct unit *const u, const struct camera *const cam, + const bool sel) +{ + if (!u->instance.alive + || u->state == UNIT_STATE_SHELTERED + || u->state == UNIT_STATE_HARVESTING_GOLD) + return 0; + + struct render_cfg rcfg; + + if (unit_quad(u, &rcfg)) + return -1; + + const struct instance_render_cfg cfg = + { + .i = &u->instance, + .prim_type = INSTANCE_RENDER_CFG_QUAD, + .prim = {.quad = &rcfg.qcfg}, + .cam = cam, + .sel = sel, + .max_hp = unit_maxhp(u), + .off = &rcfg.off + }; + + return instance_render(&cfg); +} + +static void get_dimensions(const enum unit_type type, short *const w, + short *const h) +{ + static const struct dim + { + short w, h; + } dim[] = + { + [UNIT_TYPE_PEASANT] = {.w = 36, .h = 36} + }; + + const struct dim *const d = &dim[type]; + *w = d->w; + *h = d->h; +} + +void unit_create(const struct unit_cfg *const cfg, struct unit *const u) +{ + struct instance *const i = &u->instance; + + *u = (const struct unit) + { + .instance = + { + .alive = true, + .hp = unit_maxhp(u) + }, + + .type = cfg->type, + .dir = UNIT_DIR_S, + .rx = fix16_from_int(cfg->x), + .ry = fix16_from_int(cfg->y) + }; + + get_dimensions(cfg->type, &i->r.w, &i->r.h); + unit_stop(u); +} + +const char *unit_str(const struct unit *const u) +{ + static const char *const str[] = + { + [UNIT_TYPE_PEASANT] = "Peasant" + }; + + return str[u->type]; +} diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt new file mode 100644 index 0000000..9c808f5 --- /dev/null +++ b/src/util/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(util "src/util.c") +target_include_directories(util PUBLIC "inc") diff --git a/src/util/inc/util.h b/src/util/inc/util.h new file mode 100644 index 0000000..e42c5b2 --- /dev/null +++ b/src/util/inc/util.h @@ -0,0 +1,31 @@ +#ifndef UTIL_H +#define UTIL_H + +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if __STDC_VERSION__ >= 201112L +#define UTIL_STATIC_ASSERT(exp, msg) _Static_assert(exp, msg) +#else +#define UTIL_STATIC_ASSERT__(exp, l, msg) enum {static_assert##l##__ = 1 / !!(exp)} +#define UTIL_STATIC_ASSERT_(exp, l, msg) UTIL_STATIC_ASSERT__(exp, l, msg) +#define UTIL_STATIC_ASSERT(exp, msg) UTIL_STATIC_ASSERT_(exp, __LINE__, msg) +#endif + +struct util_rect +{ + unsigned long x, y; + short w, h; +}; + +bool util_collision(const struct util_rect *a, const struct util_rect *b); + +#ifdef __cplusplus +} +#endif + +#endif /* UTIL_H */ diff --git a/src/util/src/util.c b/src/util/src/util.c new file mode 100644 index 0000000..f87c14a --- /dev/null +++ b/src/util/src/util.c @@ -0,0 +1,10 @@ +#include <util.h> +#include <stdbool.h> + +bool util_collision(const struct util_rect *const a, const struct util_rect *const b) +{ + return !((a->x >= b->x + b->w) + || (b->x >= a->x + a->w) + || (a->y >= b->y + b->h) + || (b->y >= a->y + a->h)); +} |
