rts/src/player/src/human_player.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;
}