openkknd-pitchwell-archive/engine-c/src/input.c
2026-09-17 21:48:48 +02:00

292 lines
12 KiB
C

#include "kknd.h"
/* =========================================================================
* input.c - Mouse / keyboard handling and camera control.
* ========================================================================= */
static int g_keys[SDL_NUM_SCANCODES];
int input_screen_to_world(Game *g, int sx, int sy, float *wx, float *wy) {
*wx = (float)sx + g->cam.x;
*wy = (float)sy + g->cam.y;
return 1;
}
static int pick_unit(Game *g, int sx, int sy, float *wx, float *wy) {
input_screen_to_world(g, sx, sy, wx, wy);
int best = -1; float bestd = 18*18;
for (int i = 0; i < g->unit_count; i++) {
Unit *u = &g->units[i];
if (u->dead) continue;
float d = kknd_dist2(*wx, *wy, u->x, u->y);
if (d < bestd) { bestd = d; best = i; }
}
return best;
}
static int pick_building(Game *g, float wx, float wy) {
for (int i = 0; i < g->bld_count; i++) {
Building *b = &g->buildings[i];
if (b->dead) continue;
float bx = (float)(b->tx * TILE), by = (float)(b->ty * TILE);
if (wx >= bx && wx <= bx + b->w*TILE && wy >= by && wy <= by + b->h*TILE)
return i;
}
return -1;
}
static void issue_orders(Game *g, float wx, float wy) {
/* attack if enemy unit under cursor */
int ei = -1; float bestd = 22*22;
for (int i = 0; i < g->unit_count; i++) {
Unit *u = &g->units[i];
if (u->dead || u->faction == g->human) continue;
float d = kknd_dist2(wx, wy, u->x, u->y);
if (d < bestd) { bestd = d; ei = i; }
}
int bi = pick_building(g, wx, wy);
if (bi >= 0 && g->buildings[bi].faction != g->human) ei = -2;
int issued = 0;
if (ei >= 0 || ei == -2) {
for (int s = 0; s < g->sel_count; s++) {
Unit *u = unit_by_id(g, g->selection[s]);
if (!u) continue;
if (ei >= 0) unit_issue_attack_unit(g, (int)(u - g->units), ei);
else unit_issue_attack_bld(g, (int)(u - g->units), g->buildings[bi].id);
issued++;
}
} else {
/* gather selection and issue a formation move */
int sel[MAX_SELECTION], sn = 0;
for (int s = 0; s < g->sel_count; s++) {
Unit *u = unit_by_id(g, g->selection[s]);
if (!u) continue;
if (u->type == UT_HARVESTER) {
int tx, ty; map_world_to_tile(wx, wy, &tx, &ty);
Tile *t = map_tile(&g->map, tx, ty);
if (t && t->terrain == T_SCRAP) unit_issue_gather(g, (int)(u - g->units), wx, wy);
else unit_issue_move(g, (int)(u - g->units), wx, wy, 0);
} else {
sel[sn++] = (int)(u - g->units);
}
issued++;
}
if (sn > 0) unit_issue_move_group(g, sel, sn, wx, wy, 1);
}
if (issued) audio_sfx(0);
}
static void handle_hud_click(Game *g, int sx, int sy) {
HudButton btns[64]; int n = 0;
hud_layout(g, btns, &n);
for (int i = 0; i < n; i++) {
if (sx >= btns[i].x && sx <= btns[i].x + btns[i].w &&
sy >= btns[i].y && sy <= btns[i].y + btns[i].h) {
if (!btns[i].enabled) { audio_sfx(5); return; }
if (btns[i].kind == 0) {
g->placing = 1; g->place_type = (BuildingType)btns[i].id;
audio_sfx(4);
} else if (btns[i].kind == 2) {
bld_research(g, g->human, btns[i].id);
} else if (btns[i].kind == 3) {
Faction enemy = g->enemy;
int ex = g->map.spawn_x[enemy], ey = g->map.spawn_y[enemy];
float tx, ty; map_tile_to_world(ex, ey, &tx, &ty);
int sel[MAX_SELECTION], sn = 0;
for (int s = 0; s < g->sel_count; s++) {
Unit *u = unit_by_id(g, g->selection[s]);
if (!u || u->faction != g->human) continue;
int idx = (int)(u - g->units);
if (btns[i].id == 0) unit_issue_stop(g, idx);
else if (btns[i].id == 4) unit_issue_hold(g, idx);
else if (btns[i].id == 2 && u->type == UT_HARVESTER)
unit_issue_gather_nearest(g, idx);
else if (btns[i].id == 3 && u->type == UT_HARVESTER)
unit_issue_return(g, idx);
else if (btns[i].id == 1 && u->type != UT_HARVESTER)
sel[sn++] = idx;
}
if (sn > 0 && btns[i].id == 1) unit_issue_move_group(g, sel, sn, tx, ty, 1);
audio_sfx(0);
} else if (btns[i].kind == 4) {
for (int s = 0; s < g->sel_count; s++) {
Unit *u = unit_by_id(g, g->selection[s]);
if (u && u->faction == g->human && u->ability == (AbilityType)btns[i].id)
unit_ability_use(g, (int)(u - g->units));
}
audio_sfx(4);
} else {
/* queue unit on selected production building */
for (int j = 0; j < g->bld_count; j++) {
Building *b = &g->buildings[j];
if (b->dead || !b->selected || b->faction != g->human) continue;
const BldDef *d = bld_def(b->type);
for (int k = 0; k < d->produces_count; k++)
if (d->produces[k] == btns[i].id)
bld_queue_unit(g, (int)(b - g->buildings), (UnitType)btns[i].id);
}
}
return;
}
}
}
static void try_place(Game *g, int sx, int sy) {
float wx, wy; input_screen_to_world(g, sx, sy, &wx, &wy);
int tx, ty; map_world_to_tile(wx, wy, &tx, &ty);
const BldDef *d = bld_def(g->place_type);
if (g->scrap[g->human] < d->cost) { audio_sfx(5); return; }
int ret = bld_spawn(g, g->place_type, g->human, tx, ty);
if (ret == -2) { audio_sfx(5); return; }
if (ret >= 0) {
g->scrap[g->human] -= d->cost;
audio_sfx(3);
}
g->placing = 0;
}
void input_handle(Game *g, SDL_Event *e) {
switch (e->type) {
case SDL_KEYDOWN:
g_keys[e->key.keysym.scancode] = 1;
if (g->state == GAME_MENU) {
int n = mission_count();
if (e->key.keysym.sym == SDLK_f) {
g->human = (g->human == FACTION_SURVIVOR) ? FACTION_EVOLVED
: (g->human == FACTION_EVOLVED) ? FACTION_SERIES9
: FACTION_SURVIVOR;
audio_sfx(4);
} else if (e->key.keysym.sym == SDLK_UP) {
g->menu_sel = (g->menu_sel > 0) ? g->menu_sel - 1 : n - 1;
audio_sfx(6);
} else if (e->key.keysym.sym == SDLK_DOWN) {
g->menu_sel = (g->menu_sel < n - 1) ? g->menu_sel + 1 : 0;
audio_sfx(6);
} else if (e->key.keysym.sym == SDLK_RETURN
|| e->key.keysym.sym == SDLK_KP_ENTER) {
if (g->menu_sel < g->unlocked) { engine_setup_match(g, g->menu_sel); audio_sfx(3); }
} else if (e->key.keysym.sym >= SDLK_0 && e->key.keysym.sym <= SDLK_9) {
int mid = e->key.keysym.sym - SDLK_0;
if (mid < g->unlocked) { engine_setup_match(g, mid); audio_sfx(3); }
}
break;
}
if ((g->state == GAME_WON || g->state == GAME_LOST) &&
e->key.keysym.sym == SDLK_m) {
g->state = GAME_MENU;
break;
}
if (e->key.keysym.sym == SDLK_a && g->state == GAME_PLAYING) {
unit_select_all(g, g->human);
audio_sfx(4);
break;
}
if (e->key.keysym.sym >= SDLK_1 && e->key.keysym.sym <= SDLK_9 &&
g->state == GAME_PLAYING) {
int gi = e->key.keysym.sym - SDLK_1;
if (SDL_GetModState() & KMOD_CTRL) {
g->group_len[gi] = g->sel_count;
for (int s = 0; s < g->sel_count; s++) g->groups[gi][s] = g->selection[s];
audio_sfx(4);
} else if (g->group_len[gi] > 0) {
unit_clear_selection(g);
for (int s = 0; s < g->group_len[gi]; s++) {
Unit *u = unit_by_id(g, g->groups[gi][s]);
if (u && !u->dead) { g->selection[g->sel_count++] = g->groups[gi][s]; u->selected = 1; }
}
if (g->sel_count > 0) audio_sfx(4);
}
break;
}
if (e->key.keysym.sym == SDLK_SPACE) {
if (g->state == GAME_PLAYING) g->state = GAME_PAUSED;
else if (g->state == GAME_PAUSED) g->state = GAME_PLAYING;
} else if (e->key.keysym.sym == SDLK_F5) {
save_game(g, "savegame.kknd");
} else if (e->key.keysym.sym == SDLK_ESCAPE) {
g->placing = 0;
}
break;
case SDL_KEYUP:
g_keys[e->key.keysym.scancode] = 0;
break;
case SDL_MOUSEBUTTONDOWN:
if (e->button.button == SDL_BUTTON_LEFT) {
if (g->placing) { try_place(g, e->button.x, e->button.y); break; }
if (e->button.y > VIEW_H - 140) { handle_hud_click(g, e->button.x, e->button.y); break; }
/* minimap click */
if (e->button.x > VIEW_W - 212 && e->button.x < VIEW_W - 8 &&
e->button.y > 34 && e->button.y < 238) {
int mx = VIEW_W - 210, my = 36;
g->cam.x = (e->button.x - mx) / 200.0f * MAP_PIX_W - VIEW_W/2;
g->cam.y = (e->button.y - my) / 200.0f * MAP_PIX_H - VIEW_H/2;
break;
}
g->dragging = 1;
g->drag_x0 = g->drag_x1 = e->button.x;
g->drag_y0 = g->drag_y1 = e->button.y;
} else if (e->button.button == SDL_BUTTON_RIGHT) {
if (g->placing) { g->placing = 0; break; }
if (g->sel_count > 0) {
float wx, wy; input_screen_to_world(g, e->button.x, e->button.y, &wx, &wy);
issue_orders(g, wx, wy);
}
}
break;
case SDL_MOUSEBUTTONUP:
if (e->button.button == SDL_BUTTON_LEFT && g->dragging) {
g->dragging = 0;
int dx = abs(e->button.x - g->drag_x0);
int dy = abs(e->button.y - g->drag_y0);
if (dx < 6 && dy < 6) {
/* click select */
float wx, wy;
int ui = pick_unit(g, e->button.x, e->button.y, &wx, &wy);
if (ui >= 0 && g->units[ui].faction == g->human) {
unit_select_single(g, ui, g_keys[SDL_SCANCODE_LSHIFT]);
audio_sfx(4);
} else {
int bi = pick_building(g, wx, wy);
if (bi >= 0 && g->buildings[bi].faction == g->human) {
if (!g_keys[SDL_SCANCODE_LSHIFT]) unit_clear_selection(g);
g->buildings[bi].selected = 1;
g->selection[g->sel_count++] = g->buildings[bi].id;
audio_sfx(4);
} else if (!g_keys[SDL_SCANCODE_LSHIFT]) {
unit_clear_selection(g);
}
}
} else {
unit_select_in_rect(g, g->drag_x0, g->drag_y0,
e->button.x, e->button.y,
g_keys[SDL_SCANCODE_LSHIFT]);
}
}
break;
case SDL_MOUSEMOTION:
if (g->dragging) { g->drag_x1 = e->motion.x; g->drag_y1 = e->motion.y; }
break;
default:
break;
}
}
void input_camera(Game *g, float dt) {
float sp = 600 * dt;
if (g_keys[SDL_SCANCODE_W] || g_keys[SDL_SCANCODE_UP]) g->cam.y -= sp;
if (g_keys[SDL_SCANCODE_S] || g_keys[SDL_SCANCODE_DOWN]) g->cam.y += sp;
if (g_keys[SDL_SCANCODE_A] || g_keys[SDL_SCANCODE_LEFT]) g->cam.x -= sp;
if (g_keys[SDL_SCANCODE_D] || g_keys[SDL_SCANCODE_RIGHT])g->cam.x += sp;
/* edge scroll */
int mx, my; SDL_GetMouseState(&mx, &my);
if (mx < 8) g->cam.x -= sp;
if (mx > VIEW_W - 8) g->cam.x += sp;
if (my < 8) g->cam.y -= sp;
if (my > VIEW_H - 80 && my < VIEW_H - 8) g->cam.y += sp;
if (g->cam.x < 0) g->cam.x = 0;
if (g->cam.y < 0) g->cam.y = 0;
if (g->cam.x > MAP_PIX_W - VIEW_W) g->cam.x = MAP_PIX_W - VIEW_W;
if (g->cam.y > MAP_PIX_H - VIEW_H + 72) g->cam.y = MAP_PIX_H - VIEW_H + 72;
}