#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; }