474 lines
9.5 KiB
C
474 lines
9.5 KiB
C
#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_SPRITES];
|
|
struct sound unit_sounds[MAX_UNIT_SOUNDS];
|
|
|
|
static const unsigned char anim[] = {0, 1, 0, 2};
|
|
|
|
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_E:
|
|
u->rx += sx;
|
|
break;
|
|
|
|
case UNIT_DIR_S:
|
|
u->ry += sy;
|
|
break;
|
|
|
|
case UNIT_DIR_W:
|
|
u->rx -= sx;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void get_speed(const struct unit *const u, fix16_t *const x,
|
|
fix16_t *const y)
|
|
{
|
|
struct speed
|
|
{
|
|
fix16_t x, y;
|
|
} s;
|
|
|
|
switch (u->type)
|
|
{
|
|
case UNIT_TYPE_1:
|
|
case UNIT_TYPE_2:
|
|
case UNIT_TYPE_3:
|
|
case UNIT_TYPE_4:
|
|
case UNIT_TYPE_5:
|
|
case UNIT_TYPE_6:
|
|
{
|
|
static const struct speed ref =
|
|
{
|
|
.x = FIX16_C_FROM_FLOAT(0.4),
|
|
.y = FIX16_C_FROM_FLOAT(0.4)
|
|
};
|
|
|
|
s = ref;
|
|
}
|
|
break;
|
|
|
|
case UNIT_TYPE_CAR_1:
|
|
case UNIT_TYPE_CAR_2:
|
|
{
|
|
static const struct speed ref =
|
|
{
|
|
.x = FIX16_C_FROM_FLOAT(1.5),
|
|
.y = FIX16_C_FROM_FLOAT(1.5)
|
|
};
|
|
|
|
s = ref;
|
|
}
|
|
|
|
case MAX_UNIT_TYPES:
|
|
break;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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 void unit_stop(struct unit *const u)
|
|
{
|
|
u->tx = u->rx;
|
|
u->ty = u->ry;
|
|
}
|
|
|
|
static void target_reset(struct unit *const u)
|
|
{
|
|
u->target = (const struct unit_target){0};
|
|
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)
|
|
{
|
|
target_reset(u);
|
|
return;
|
|
}
|
|
else if (t->state == u->state)
|
|
return;
|
|
|
|
struct instance *const ui = &u->instance;
|
|
|
|
if (!util_collision(&ins->r, &ui->r))
|
|
unit_chase(ins, u);
|
|
}
|
|
|
|
static bool must_move(const struct unit *const u)
|
|
{
|
|
return u->rx != u->tx || u->ry != u->ty;
|
|
}
|
|
|
|
void unit_update(struct unit *const u)
|
|
{
|
|
struct instance *const i = &u->instance;
|
|
|
|
if (!i->alive)
|
|
return;
|
|
|
|
if (u->target.ins)
|
|
target_interact(u);
|
|
else 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 = 10};
|
|
|
|
if (++u->frame.t >= FRAME_RATE)
|
|
{
|
|
u->frame.t = 0;
|
|
|
|
if (++u->frame.i >= sizeof anim / sizeof *anim)
|
|
u->frame.i = 0;
|
|
}
|
|
|
|
u->state = UNIT_STATE_IDLE_MOVING;
|
|
}
|
|
else
|
|
u->frame.i = 0;
|
|
|
|
i->r.x = fix16_to_int(u->rx);
|
|
i->r.y = fix16_to_int(u->ry);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static void adjust_walker(const struct unit *const u, struct sprite *const s,
|
|
struct instance_render_off *const off)
|
|
{
|
|
s->w = u->instance.r.w;
|
|
s->h = u->instance.r.h;
|
|
s->u += u->dir * s->w;
|
|
s->v += anim[u->frame.i] * s->h;
|
|
off->x = 0;
|
|
off->y = -2;
|
|
}
|
|
|
|
static void adjust_car(const struct unit *const u, struct sprite *const s,
|
|
struct instance_render_off *const off)
|
|
{
|
|
switch (u->dir)
|
|
{
|
|
case UNIT_DIR_W:
|
|
s->w = s->h = 32;
|
|
s->u += 32;
|
|
off->x = -5;
|
|
break;
|
|
|
|
case UNIT_DIR_S:
|
|
s->w = 16;
|
|
s->h = 32;
|
|
off->x = 2;
|
|
break;
|
|
|
|
case UNIT_DIR_N:
|
|
s->u += 16;
|
|
s->w = 16;
|
|
s->h = 32;
|
|
off->x = 2;
|
|
break;
|
|
|
|
case UNIT_DIR_E:
|
|
s->w = s->h = 32;
|
|
s->u += 64;
|
|
off->x = -5;
|
|
break;
|
|
}
|
|
|
|
off->y = -10;
|
|
}
|
|
|
|
static void adjust_sprite(const struct unit *const u, struct sprite *const s,
|
|
struct instance_render_off *const off)
|
|
{
|
|
switch (u->type)
|
|
{
|
|
case UNIT_TYPE_1:
|
|
case UNIT_TYPE_2:
|
|
case UNIT_TYPE_3:
|
|
case UNIT_TYPE_4:
|
|
case UNIT_TYPE_5:
|
|
case UNIT_TYPE_6:
|
|
adjust_walker(u, s, off);
|
|
break;
|
|
|
|
case UNIT_TYPE_CAR_1:
|
|
case UNIT_TYPE_CAR_2:
|
|
adjust_car(u, s, off);
|
|
break;
|
|
|
|
case MAX_UNIT_TYPES:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int unit_render(const struct unit *const u, const struct camera *const cam,
|
|
const bool sel)
|
|
{
|
|
if (!u->instance.alive)
|
|
return 0;
|
|
|
|
sprite_get_or_ret(s, -1);
|
|
|
|
struct instance_render_off off;
|
|
|
|
if (sprite_clone(&unit_sprites[u->type], s))
|
|
{
|
|
fprintf(stderr, "%s: sprite_clone failed\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
adjust_sprite(u, s, &off);
|
|
|
|
const struct instance_render_cfg cfg =
|
|
{
|
|
.i = &u->instance,
|
|
.prim_type = INSTANCE_RENDER_CFG_SPRITE,
|
|
.prim.s = s,
|
|
.off = &off,
|
|
.cam = cam,
|
|
.sel = sel
|
|
};
|
|
|
|
return instance_render(&cfg);
|
|
}
|
|
|
|
static void get_dimensions(const enum unit_type type, short *const w,
|
|
short *const h)
|
|
{
|
|
switch (type)
|
|
{
|
|
case UNIT_TYPE_1:
|
|
case UNIT_TYPE_2:
|
|
case UNIT_TYPE_3:
|
|
case UNIT_TYPE_4:
|
|
case UNIT_TYPE_5:
|
|
case UNIT_TYPE_6:
|
|
*w = *h = 16;
|
|
break;
|
|
|
|
case UNIT_TYPE_CAR_1:
|
|
case UNIT_TYPE_CAR_2:
|
|
*w = *h = 22;
|
|
break;
|
|
|
|
case MAX_UNIT_TYPES:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const char *get_walker_name(const enum unit_type type)
|
|
{
|
|
enum {FEMALE, MALE} gender = 0;
|
|
|
|
switch (type)
|
|
{
|
|
case UNIT_TYPE_2:
|
|
case UNIT_TYPE_5:
|
|
gender = FEMALE;
|
|
break;
|
|
|
|
case UNIT_TYPE_1:
|
|
case UNIT_TYPE_3:
|
|
case UNIT_TYPE_4:
|
|
case UNIT_TYPE_6:
|
|
gender = MALE;
|
|
break;
|
|
|
|
default:
|
|
return NULL;
|
|
}
|
|
|
|
static const char *const female[] =
|
|
{
|
|
"Amelia",
|
|
"Asher",
|
|
"Aurora",
|
|
"Ava",
|
|
"Charlotte",
|
|
"Dana",
|
|
"Ellie",
|
|
"Emma",
|
|
"Ezra",
|
|
"Haley",
|
|
"Harper",
|
|
"Isabella",
|
|
"Luna",
|
|
"Mire",
|
|
"Olivia",
|
|
"Sophia",
|
|
};
|
|
|
|
static const char *const male[] =
|
|
{
|
|
"Oliver",
|
|
"Xavi",
|
|
"Benjamin",
|
|
"Elijah",
|
|
"Ethan",
|
|
"Jack",
|
|
"James",
|
|
"Jan",
|
|
"Kyte",
|
|
"Leo",
|
|
"Levi",
|
|
"Liam",
|
|
"Luca",
|
|
"Lucas",
|
|
"Michael",
|
|
"Noah",
|
|
};
|
|
|
|
switch (gender)
|
|
{
|
|
case FEMALE:
|
|
{
|
|
const size_t i = rand() % (sizeof female / sizeof *female);
|
|
|
|
return female[i];
|
|
}
|
|
|
|
case MALE:
|
|
{
|
|
const size_t i = rand() % (sizeof male / sizeof *male);
|
|
|
|
return male[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const char *get_name(const enum unit_type type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case UNIT_TYPE_1:
|
|
case UNIT_TYPE_2:
|
|
case UNIT_TYPE_3:
|
|
case UNIT_TYPE_4:
|
|
case UNIT_TYPE_5:
|
|
case UNIT_TYPE_6:
|
|
return get_walker_name(type);
|
|
|
|
case UNIT_TYPE_CAR_1:
|
|
case UNIT_TYPE_CAR_2:
|
|
return "Car";
|
|
|
|
case MAX_UNIT_TYPES:
|
|
break;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void unit_create(const struct unit_cfg *const cfg, struct unit *const u)
|
|
{
|
|
struct instance *const i = &u->instance;
|
|
enum unit_type type = 0;
|
|
|
|
switch (cfg->type)
|
|
{
|
|
case UNIT_CFG_TYPE_WALKER:
|
|
type = UNIT_TYPE_1 + (rand() % (UNIT_TYPE_6 + 1));
|
|
break;
|
|
|
|
case UNIT_CFG_TYPE_CAR:
|
|
type = UNIT_TYPE_CAR_1 + (rand()
|
|
% (UNIT_TYPE_CAR_2 - UNIT_TYPE_CAR_1 + 1));
|
|
break;
|
|
}
|
|
|
|
*u = (const struct unit)
|
|
{
|
|
.instance.alive = true,
|
|
.dir = UNIT_DIR_S,
|
|
.rx = fix16_from_int(cfg->x),
|
|
.ry = fix16_from_int(cfg->y),
|
|
.type = type,
|
|
.name = get_name(type)
|
|
};
|
|
|
|
get_dimensions(u->type, &i->r.w, &i->r.h);
|
|
unit_stop(u);
|
|
}
|