aboutsummaryrefslogtreecommitdiff
path: root/src/unit
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2021-07-03 00:49:03 +0200
committerXavier Del Campo Romero <xavi.dcr@tutanota.com>2022-03-30 08:20:20 +0200
commit6b9f686913efc3725b2690033cd4f398e07076ba (patch)
treee9aa91a6b9f617d78123ebe7ad272fc42a60d306 /src/unit
parentc9e6ae44a9aeb89b3f48f3443d6baa80103f7445 (diff)
downloadjancity-6b9f686913efc3725b2690033cd4f398e07076ba.tar.gz
Add project source code
Diffstat (limited to 'src/unit')
-rw-r--r--src/unit/CMakeLists.txt3
-rw-r--r--src/unit/inc/unit.h134
-rw-r--r--src/unit/inc/unit_type.h11
-rw-r--r--src/unit/src/unit.c660
4 files changed, 808 insertions, 0 deletions
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];
+}