757 lines
20 KiB
C
757 lines
20 KiB
C
#include <human_player.h>
|
|
#include <human_player_private.h>
|
|
#include <player.h>
|
|
#include <building.h>
|
|
#include <camera.h>
|
|
#include <gfx.h>
|
|
#include <gui.h>
|
|
#include <instance.h>
|
|
#include <input.h>
|
|
#include <keyboard.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, const bool excl)
|
|
{
|
|
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)
|
|
{
|
|
struct sel_instance *sel = NULL;
|
|
|
|
if (excl)
|
|
{
|
|
sel = h->sel;
|
|
h->n_sel = 1;
|
|
|
|
for (size_t i = 1; i < sizeof h->sel / sizeof *h->sel; i++)
|
|
h->sel[i].d.u = NULL;
|
|
}
|
|
else
|
|
{
|
|
for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++)
|
|
{
|
|
struct sel_instance *const s = &h->sel[i];
|
|
|
|
if (!s->d.u)
|
|
{
|
|
sel = s;
|
|
h->n_sel++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sel)
|
|
{
|
|
sel->type = INSTANCE_TYPE_UNIT;
|
|
sel->d.u = u;
|
|
sfx_play(&unit_sounds[UNIT_SOUND_SELECTED]);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
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, const bool excl)
|
|
{
|
|
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)
|
|
{
|
|
struct sel_instance *sel = NULL;
|
|
|
|
if (excl)
|
|
{
|
|
sel = h->sel;
|
|
h->n_sel = 1;
|
|
|
|
for (size_t i = 1; i < sizeof h->sel / sizeof *h->sel; i++)
|
|
h->sel[i].d.b = NULL;
|
|
}
|
|
else
|
|
{
|
|
for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++)
|
|
{
|
|
struct sel_instance *const s = &h->sel[i];
|
|
|
|
if (!s->d.b)
|
|
{
|
|
sel = s;
|
|
h->n_sel++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sel)
|
|
{
|
|
sel->type = INSTANCE_TYPE_BUILDING;
|
|
sel->d.b = b;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool select_resources(struct human_player *const h, const short x,
|
|
const short y, const struct player_others *const o, const bool excl)
|
|
{
|
|
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)
|
|
{
|
|
struct sel_instance *sel = NULL;
|
|
|
|
if (excl)
|
|
{
|
|
sel = h->sel;
|
|
h->n_sel = 1;
|
|
|
|
for (size_t i = 1; i < sizeof h->sel / sizeof *h->sel; i++)
|
|
h->sel[i].d.r = NULL;
|
|
}
|
|
else
|
|
{
|
|
for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++)
|
|
{
|
|
struct sel_instance *const s = &h->sel[i];
|
|
|
|
if (!s->d.r)
|
|
{
|
|
sel = s;
|
|
h->n_sel++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sel)
|
|
{
|
|
sel->type = INSTANCE_TYPE_RESOURCE;
|
|
sel->d.r = r;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool select_instances(struct human_player *const h,
|
|
const struct player_others *const o, const bool excl,
|
|
const bool same_type)
|
|
{
|
|
unsigned long x, y;
|
|
|
|
cursor_pos(&h->cam, &x, &y);
|
|
|
|
if (!same_type || !h->n_sel)
|
|
return select_buildings(h, x, y, excl)
|
|
|| select_units(h, x, y, excl)
|
|
|| select_resources(h, x, y, o, excl);
|
|
else
|
|
{
|
|
switch (h->sel->type)
|
|
{
|
|
case INSTANCE_TYPE_UNIT:
|
|
return select_units(h, x, y, excl);
|
|
|
|
case INSTANCE_TYPE_BUILDING:
|
|
return select_buildings(h, x, y, excl);
|
|
|
|
case INSTANCE_TYPE_RESOURCE:
|
|
select_resources(h, x, y, o, excl);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
|
|
h->target = (const struct human_player_target)
|
|
{
|
|
.ins = t->ins,
|
|
.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)
|
|
{
|
|
for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++)
|
|
h->sel[i] = (const struct sel_instance){0};
|
|
|
|
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)
|
|
h->target = (const struct human_player_target){0};
|
|
else if (++h->target.t >= TOGGLE)
|
|
{
|
|
enum {TIMES = 5};
|
|
|
|
h->target.render ^= true;
|
|
h->target.t = 0;
|
|
|
|
if (++h->target.n >= TIMES)
|
|
h->target = (const struct human_player_target){0};
|
|
}
|
|
}
|
|
}
|
|
|
|
static void update_from_pad(struct human_player *const h,
|
|
struct player_others *const o)
|
|
{
|
|
const struct pad *const p = &h->periph.pad.pad;
|
|
const struct input *const in = &h->in;
|
|
|
|
if (input_pad_justpressed(in, p, PAD_KEY_A))
|
|
select_instances(h, o, false, false);
|
|
else if (input_pad_justpressed(in, p, PAD_KEY_B))
|
|
move_units(h, o);
|
|
else if (input_pad_justpressed(in, p, PAD_KEY_C))
|
|
deselect_instances(h);
|
|
else if (input_pad_justpressed(in, p, PAD_KEY_E))
|
|
h->top_gui ^= true;
|
|
}
|
|
|
|
static void update_from_touch(struct human_player *const h,
|
|
struct player_others *const o)
|
|
{
|
|
const struct mouse *const m = &h->periph.kbm.mouse;
|
|
const struct input *const in = &h->in;
|
|
struct peripheral_kbm *const kbm = &h->periph.kbm;
|
|
bool *const pan = &h->cam.pan;
|
|
|
|
if (input_mouse_pressed(in, m, MOUSE_BUTTON_LEFT) && !*pan)
|
|
{
|
|
enum {LONG_PRESS_THRESHOLD = 30};
|
|
|
|
if (kbm->lp_t < LONG_PRESS_THRESHOLD)
|
|
kbm->lp_t++;
|
|
else if (!kbm->long_press)
|
|
{
|
|
kbm->long_press = true;
|
|
deselect_instances(h);
|
|
}
|
|
}
|
|
else if (input_mouse_justreleased(in, m, MOUSE_BUTTON_LEFT))
|
|
{
|
|
if (!*pan && !select_instances(h, o, false, true))
|
|
move_units(h, o);
|
|
|
|
*pan = false;
|
|
kbm->long_press = false;
|
|
kbm->lp_t = 0;
|
|
}
|
|
}
|
|
|
|
static void update_from_keyboard_mouse(struct human_player *const h,
|
|
struct player_others *const o)
|
|
{
|
|
const struct mouse *const m = &h->periph.kbm.mouse;
|
|
const struct keyboard *const k = &h->periph.kbm.keyboard;
|
|
const struct input *const in = &h->in;
|
|
|
|
if (input_mouse_justreleased(in, m, MOUSE_BUTTON_LEFT))
|
|
{
|
|
const bool shift_pressed =
|
|
input_keyboard_pressed(in, k,
|
|
&KEYBOARD_COMBO(KEYBOARD_KEY_LSHIFT))
|
|
|| input_keyboard_pressed(in, k,
|
|
&KEYBOARD_COMBO(KEYBOARD_KEY_RSHIFT));
|
|
|
|
if (!select_instances(h, o, !shift_pressed, false))
|
|
deselect_instances(h);
|
|
}
|
|
else if (input_mouse_justreleased(in, m, MOUSE_BUTTON_RIGHT))
|
|
move_units(h, o);
|
|
}
|
|
|
|
void human_player_update(struct human_player *const h,
|
|
struct player_others *const o)
|
|
{
|
|
struct player *const p = &h->pl;
|
|
|
|
if (p->alive)
|
|
{
|
|
human_player_gui_update(h);
|
|
update_selected(h);
|
|
update_target(h);
|
|
peripheral_update(&h->periph);
|
|
input_update(&h->in, &h->periph);
|
|
|
|
switch (h->periph.common.type)
|
|
{
|
|
case PERIPHERAL_TYPE_PAD:
|
|
update_from_pad(h, o);
|
|
break;
|
|
|
|
case PERIPHERAL_TYPE_TOUCH:
|
|
update_from_touch(h, o);
|
|
break;
|
|
|
|
case PERIPHERAL_TYPE_KEYBOARD_MOUSE:
|
|
update_from_keyboard_mouse(h, o);
|
|
break;
|
|
}
|
|
|
|
camera_update(&h->cam, &h->periph, &h->in);
|
|
player_update(p);
|
|
}
|
|
}
|
|
|
|
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)
|
|
|| human_player_gui_render(h)
|
|
|| input_render(&h->in, &h->periph))
|
|
return -1;
|
|
|
|
switch (h->periph.common.type)
|
|
{
|
|
case PERIPHERAL_TYPE_PAD:
|
|
/* Fall through. */
|
|
case PERIPHERAL_TYPE_KEYBOARD_MOUSE:
|
|
cursor_render(&h->cam.cursor);
|
|
break;
|
|
|
|
case PERIPHERAL_TYPE_TOUCH:
|
|
break;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int human_player_init(const struct human_player_cfg *const cfg,
|
|
struct human_player *const h)
|
|
{
|
|
*h = (const struct human_player){0};
|
|
|
|
if (player_init(&cfg->pl, &h->pl))
|
|
return -1;
|
|
|
|
const struct peripheral_cfg p_cfg =
|
|
{
|
|
.type = cfg->sel_periph,
|
|
.padn = cfg->padn
|
|
};
|
|
|
|
peripheral_init(&p_cfg, &h->periph);
|
|
cursor_init(&h->cam.cursor);
|
|
h->cam.dim = cfg->dim;
|
|
h->top_gui = true;
|
|
UTIL_STATIC_ASSERT(sizeof h->gui_res == sizeof h->pl.resources,
|
|
"unexpected sizeof for h->gui_res");
|
|
memmove(h->gui_res, h->pl.resources, sizeof h->gui_res);
|
|
return 0;
|
|
}
|