kitchen: README + MISSIONS/engine-c keep-drop notes
This commit is contained in:
parent
1d351918da
commit
575d7d29b8
130 changed files with 9817 additions and 1 deletions
585
engine-c/src/units.c
Normal file
585
engine-c/src/units.c
Normal file
|
|
@ -0,0 +1,585 @@
|
|||
#include "kknd.h"
|
||||
|
||||
/* =========================================================================
|
||||
* units.c - Unit lifecycle, movement, combat and the harvester economy.
|
||||
* ========================================================================= */
|
||||
|
||||
static int unit_alloc_id(Game *g) { return g->unit_next_id++; }
|
||||
|
||||
Unit *unit_by_id(Game *g, int id) {
|
||||
for (int i = 0; i < g->unit_count; i++)
|
||||
if (!g->units[i].dead && g->units[i].id == id)
|
||||
return &g->units[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int unit_spawn(Game *g, UnitType t, Faction f, float x, float y) {
|
||||
if (g->unit_count >= MAX_UNITS) return -1;
|
||||
const UnitDef *d = unit_def(t, f);
|
||||
Unit *u = &g->units[g->unit_count++];
|
||||
memset(u, 0, sizeof(Unit));
|
||||
u->id = unit_alloc_id(g);
|
||||
u->type = t;
|
||||
u->faction = f;
|
||||
u->x = x; u->y = y;
|
||||
/* apply researched upgrades */
|
||||
float dmg_m = 1.0f + 0.15f * (float)g->upg[f].lvl[UP_DAMAGE];
|
||||
float arm_m = 1.0f + 0.15f * (float)g->upg[f].lvl[UP_ARMOR];
|
||||
float spd_m = 1.0f + 0.10f * (float)g->upg[f].lvl[UP_SPEED];
|
||||
float rel_m = 1.0f + 0.12f * (float)g->upg[f].lvl[UP_RELOAD];
|
||||
u->hp = d->hp * arm_m; u->max_hp = d->hp * arm_m;
|
||||
u->attack = d->attack * dmg_m; u->defense = d->defense;
|
||||
u->speed = d->speed * spd_m; u->range = d->range; u->sight = d->sight;
|
||||
u->reload_mul = rel_m;
|
||||
u->heading = 0;
|
||||
u->state = STATE_IDLE;
|
||||
u->order = ORDER_NONE;
|
||||
u->target_unit = -1; u->target_bld = -1;
|
||||
u->atk_cd = 0;
|
||||
u->cargo = 0;
|
||||
u->selected = 0;
|
||||
u->dead = 0;
|
||||
u->anim = 0;
|
||||
u->repath_timer = 0;
|
||||
u->path.len = 0; u->path_idx = 0;
|
||||
return g->unit_count - 1;
|
||||
}
|
||||
|
||||
void unit_remove(Game *g, int idx) {
|
||||
if (idx < 0 || idx >= g->unit_count) return;
|
||||
if (g->units[idx].selected) {
|
||||
for (int i = 0; i < g->sel_count; i++)
|
||||
if (g->selection[i] == g->units[idx].id) {
|
||||
g->selection[i] = g->selection[g->sel_count-1];
|
||||
g->sel_count--; i--;
|
||||
}
|
||||
}
|
||||
g->units[idx] = g->units[g->unit_count - 1];
|
||||
g->unit_count--;
|
||||
}
|
||||
|
||||
void unit_apply_damage(Game *g, Unit *u, float dmg) {
|
||||
float eff = dmg - u->defense * 0.25f;
|
||||
if (eff < 1) eff = 1;
|
||||
u->hp -= eff;
|
||||
particle_burst(g, u->x, u->y, 3, 2, 255, 200, 80);
|
||||
if (u->hp <= 0 && !u->dead) {
|
||||
u->dead = 1;
|
||||
u->state = STATE_DEAD;
|
||||
particle_burst(g, u->x, u->y, 14, 1, 255, 140, 40);
|
||||
particle_burst(g, u->x, u->y, 8, 3, 90, 80, 70);
|
||||
audio_sfx(2);
|
||||
if (g->cam.shake < 7) g->cam.shake = 7;
|
||||
if (u->faction == g->human)
|
||||
game_log(g, "Unit lost: %s", unit_def(u->type, u->faction)->name);
|
||||
/* leave a wreck decal on the battlefield */
|
||||
if (g->wreck_count < MAX_WRECKS) {
|
||||
Wreck *w = &g->wrecks[g->wreck_next];
|
||||
w->x = u->x; w->y = u->y; w->type = u->type;
|
||||
w->faction = u->faction; w->rot = u->heading;
|
||||
g->wreck_next = (g->wreck_next + 1) % MAX_WRECKS;
|
||||
if (g->wreck_count < MAX_WRECKS) g->wreck_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void unit_repath(Game *g, Unit *u, float gx, float gy) {
|
||||
int sx, sy, gx_t, gy_t;
|
||||
map_world_to_tile(u->x, u->y, &sx, &sy);
|
||||
map_world_to_tile(gx, gy, &gx_t, &gy_t);
|
||||
Path p;
|
||||
if (path_find(&g->map, sx, sy, gx_t, gy_t, &p)) {
|
||||
path_smooth(&g->map, &p);
|
||||
u->path = p;
|
||||
u->path_idx = (p.len > 1) ? 1 : 0;
|
||||
} else {
|
||||
u->path.len = 0;
|
||||
}
|
||||
u->repath_timer = 1.0f;
|
||||
}
|
||||
|
||||
void unit_issue_move(Game *g, int idx, float x, float y, int attackmove) {
|
||||
Unit *u = &g->units[idx];
|
||||
u->order = attackmove ? ORDER_ATTACKMOVE : ORDER_MOVE;
|
||||
u->dest_x = x; u->dest_y = y;
|
||||
u->target_unit = -1; u->target_bld = -1;
|
||||
u->state = STATE_MOVING;
|
||||
unit_repath(g, u, x, y);
|
||||
}
|
||||
|
||||
/* Spread a group of units into a grid formation centred on (cx,cy) so they
|
||||
* don't all path to the same tile and pile up. */
|
||||
void unit_issue_move_group(Game *g, const int *idxs, int n, float cx, float cy, int attackmove) {
|
||||
if (n <= 0) return;
|
||||
int cols = (int)(sqrtf((float)n) + 0.5f);
|
||||
if (cols < 1) cols = 1;
|
||||
float sp = 28.0f;
|
||||
int halfc = (cols - 1) / 2;
|
||||
for (int i = 0; i < n; i++) {
|
||||
int c = i % cols, r = i / cols;
|
||||
float ox = (c - halfc) * sp;
|
||||
float oy = (r - halfc) * sp;
|
||||
Unit *u = &g->units[idxs[i]];
|
||||
if (u->type == UT_HARVESTER) {
|
||||
unit_issue_move(g, idxs[i], cx, cy, attackmove);
|
||||
continue;
|
||||
}
|
||||
u->order = attackmove ? ORDER_ATTACKMOVE : ORDER_MOVE;
|
||||
u->dest_x = cx + ox; u->dest_y = cy + oy;
|
||||
u->target_unit = -1; u->target_bld = -1;
|
||||
u->state = STATE_MOVING;
|
||||
unit_repath(g, u, u->dest_x, u->dest_y);
|
||||
}
|
||||
}
|
||||
|
||||
void unit_issue_attack_unit(Game *g, int idx, int target) {
|
||||
Unit *u = &g->units[idx];
|
||||
u->order = ORDER_ATTACK;
|
||||
u->target_unit = target; u->target_bld = -1;
|
||||
u->state = STATE_ATTACKING;
|
||||
}
|
||||
|
||||
void unit_issue_attack_bld(Game *g, int idx, int target) {
|
||||
Unit *u = &g->units[idx];
|
||||
u->order = ORDER_ATTACK;
|
||||
u->target_bld = target; u->target_unit = -1;
|
||||
u->state = STATE_ATTACKING;
|
||||
}
|
||||
|
||||
void unit_issue_gather(Game *g, int idx, float x, float y) {
|
||||
Unit *u = &g->units[idx];
|
||||
if (u->type != UT_HARVESTER) { unit_issue_move(g, idx, x, y, 0); return; }
|
||||
u->order = ORDER_GATHER;
|
||||
u->dest_x = x; u->dest_y = y;
|
||||
u->state = STATE_MOVING;
|
||||
unit_repath(g, u, x, y);
|
||||
}
|
||||
|
||||
void unit_issue_gather_nearest(Game *g, int idx) {
|
||||
Unit *u = &g->units[idx];
|
||||
if (u->type != UT_HARVESTER) return;
|
||||
float sx, sy;
|
||||
if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) {
|
||||
u->order = ORDER_GATHER;
|
||||
u->dest_x = sx; u->dest_y = sy;
|
||||
u->state = STATE_MOVING;
|
||||
unit_repath(g, u, sx, sy);
|
||||
}
|
||||
}
|
||||
|
||||
void unit_issue_return(Game *g, int idx) {
|
||||
Unit *u = &g->units[idx];
|
||||
if (u->type != UT_HARVESTER) return;
|
||||
int rid = -1; float rbest = 1e18f;
|
||||
for (int i = 0; i < g->bld_count; i++) {
|
||||
Building *b = &g->buildings[i];
|
||||
if (b->dead || b->faction != u->faction || b->type != BT_REFINERY) continue;
|
||||
float bx = (float)(b->tx*TILE + b->w*TILE/2);
|
||||
float by = (float)(b->ty*TILE + b->h*TILE/2);
|
||||
float dd = kknd_dist2(u->x, u->y, bx, by);
|
||||
if (dd < rbest) { rbest = dd; rid = b->id; }
|
||||
}
|
||||
if (rid < 0) return;
|
||||
Building *b = bld_by_id(g, rid);
|
||||
u->dest_x = (float)(b->tx*TILE + b->w*TILE/2);
|
||||
u->dest_y = (float)(b->ty*TILE + b->h*TILE/2);
|
||||
u->order = ORDER_RETURN;
|
||||
u->state = STATE_MOVING;
|
||||
unit_repath(g, u, u->dest_x, u->dest_y);
|
||||
}
|
||||
|
||||
void unit_issue_stop(Game *g, int idx) {
|
||||
Unit *u = &g->units[idx];
|
||||
u->order = ORDER_STOP;
|
||||
u->target_unit = -1; u->target_bld = -1;
|
||||
u->state = STATE_IDLE;
|
||||
u->hold = 0; u->attack_move = 0;
|
||||
u->path.len = 0; u->path_idx = 0;
|
||||
}
|
||||
|
||||
void unit_issue_hold(Game *g, int idx) {
|
||||
Unit *u = &g->units[idx];
|
||||
u->order = ORDER_STOP;
|
||||
u->target_unit = -1; u->target_bld = -1;
|
||||
u->state = STATE_IDLE;
|
||||
u->hold = 1;
|
||||
u->path.len = 0; u->path_idx = 0;
|
||||
}
|
||||
|
||||
void unit_ability_use(Game *g, int idx) {
|
||||
Unit *u = &g->units[idx];
|
||||
if (u->ability == AB_SIEGE) {
|
||||
u->ability_on = !u->ability_on;
|
||||
audio_sfx(4);
|
||||
} else if (u->ability == AB_HEAL) {
|
||||
if (u->ability_cd <= 0) {
|
||||
u->ability_cd = 8.0f;
|
||||
float heal_r = 90.0f;
|
||||
for (int k = 0; k < g->unit_count; k++) {
|
||||
Unit *a = &g->units[k];
|
||||
if (a->dead || a->faction != u->faction) continue;
|
||||
if (kknd_dist2(u->x, u->y, a->x, a->y) < heal_r*heal_r)
|
||||
a->hp = (a->hp + 120 > a->max_hp) ? a->max_hp : a->hp + 120;
|
||||
}
|
||||
particle_burst(g, u->x, u->y, 16, 1, 90, 255, 120);
|
||||
audio_sfx(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void unit_select_single(Game *g, int idx, int add) {
|
||||
if (!add) unit_clear_selection(g);
|
||||
g->units[idx].selected = 1;
|
||||
g->selection[g->sel_count++] = g->units[idx].id;
|
||||
}
|
||||
|
||||
void unit_clear_selection(Game *g) {
|
||||
for (int i = 0; i < g->unit_count; i++) g->units[i].selected = 0;
|
||||
g->sel_count = 0;
|
||||
}
|
||||
|
||||
void unit_select_all(Game *g, Faction f) {
|
||||
unit_clear_selection(g);
|
||||
for (int i = 0; i < g->unit_count; i++) {
|
||||
Unit *u = &g->units[i];
|
||||
if (u->dead || u->faction != f) continue;
|
||||
u->selected = 1;
|
||||
g->selection[g->sel_count++] = u->id;
|
||||
}
|
||||
}
|
||||
|
||||
void unit_select_in_rect(Game *g, int x0, int y0, int x1, int y1, int add) {
|
||||
int minx = x0<x1?x0:x1, maxx = x0>x1?x0:x1;
|
||||
int miny = y0<y1?y0:y1, maxy = y0>y1?y0:y1;
|
||||
if (!add) unit_clear_selection(g);
|
||||
int added = 0;
|
||||
for (int i = 0; i < g->unit_count; i++) {
|
||||
Unit *u = &g->units[i];
|
||||
if (u->dead) continue;
|
||||
int sx = (int)(u->x - g->cam.x);
|
||||
int sy = (int)(u->y - g->cam.y);
|
||||
if (sx >= minx && sx <= maxx && sy >= miny && sy <= maxy) {
|
||||
if (u->faction == g->human && !u->selected) {
|
||||
u->selected = 1;
|
||||
g->selection[g->sel_count++] = u->id;
|
||||
added++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (added) audio_sfx(4);
|
||||
}
|
||||
|
||||
static int unit_find_enemy_unit(Game *g, Unit *u, float maxd) {
|
||||
int best = -1; float bestd = maxd * maxd;
|
||||
for (int i = 0; i < g->unit_count; i++) {
|
||||
Unit *e = &g->units[i];
|
||||
if (e->dead || e->faction == u->faction) continue;
|
||||
float d = kknd_dist2(u->x, u->y, e->x, e->y);
|
||||
if (d < bestd) { bestd = d; best = e->id; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
static int unit_find_enemy_bld(Game *g, Unit *u, float maxd) {
|
||||
int best = -1; float bestd = maxd * maxd;
|
||||
for (int i = 0; i < g->bld_count; i++) {
|
||||
Building *b = &g->buildings[i];
|
||||
if (b->dead || b->faction == u->faction) continue;
|
||||
float bx = (float)(b->tx*TILE + b->w*TILE/2);
|
||||
float by = (float)(b->ty*TILE + b->h*TILE/2);
|
||||
float d = kknd_dist2(u->x, u->y, bx, by);
|
||||
if (d < bestd) { bestd = d; best = b->id; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
static void unit_combat_step(Game *g, Unit *u, float dt) {
|
||||
if (u->atk_cd > 0) u->atk_cd -= dt;
|
||||
if (u->attack <= 0) return;
|
||||
|
||||
/* validate current target */
|
||||
Unit *tu = (u->target_unit >= 0) ? unit_by_id(g, u->target_unit) : NULL;
|
||||
Building *tb = NULL;
|
||||
if (!tu && u->target_bld >= 0) {
|
||||
for (int i = 0; i < g->bld_count; i++)
|
||||
if (g->buildings[i].id == u->target_bld && !g->buildings[i].dead)
|
||||
{ tb = &g->buildings[i]; break; }
|
||||
}
|
||||
|
||||
float tx, ty;
|
||||
if (tu) { tx = tu->x; ty = tu->y; }
|
||||
else if (tb) { tx = (float)(tb->tx*TILE + tb->w*TILE/2);
|
||||
ty = (float)(tb->ty*TILE + tb->h*TILE/2); }
|
||||
else { tx = u->dest_x; ty = u->dest_y; }
|
||||
|
||||
float d = kknd_dist(u->x, u->y, tx, ty);
|
||||
float rng = u->range * (u->ability_on ? 1.6f : 1.0f);
|
||||
|
||||
if ((tu || tb) && d <= rng) {
|
||||
u->heading = (float)atan2(ty - u->y, tx - u->x);
|
||||
if (u->atk_cd <= 0) {
|
||||
int kind = (u->type == UT_ARTILLERY || u->type == UT_MISSILE || u->type == UT_TANK)
|
||||
? 1 : 0;
|
||||
if (tu) bullet_spawn(g, u->x, u->y, tu->id, -1, u->attack, u->faction, kind);
|
||||
else bullet_spawn(g, u->x, u->y, -1, tb->id, u->attack, u->faction, kind);
|
||||
u->atk_cd = ((u->type == UT_ARTILLERY) ? 2.2f
|
||||
: (u->type == UT_INFANTRY ? 0.8f : 1.0f)) / u->reload_mul;
|
||||
u->anim = 1.0f;
|
||||
audio_sfx(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* need to close distance: keep moving along path toward target */
|
||||
if (u->hold) return; /* hold position: fire but never chase */
|
||||
if ((tu || tb) && u->order == ORDER_ATTACK) {
|
||||
if (u->repath_timer <= 0 ||
|
||||
kknd_dist2(u->dest_x, u->dest_y, tx, ty) > 64*64) {
|
||||
u->dest_x = tx; u->dest_y = ty;
|
||||
unit_repath(g, u, tx, ty);
|
||||
}
|
||||
u->state = STATE_MOVING;
|
||||
}
|
||||
}
|
||||
|
||||
static int harvester_find_refinery(Game *g, Unit *u) {
|
||||
int rid = -1; float rbest = 1e18f;
|
||||
for (int i = 0; i < g->bld_count; i++) {
|
||||
Building *b = &g->buildings[i];
|
||||
if (b->dead || b->faction != u->faction || b->type != BT_REFINERY) continue;
|
||||
float bx = (float)(b->tx*TILE + b->w*TILE/2);
|
||||
float by = (float)(b->ty*TILE + b->h*TILE/2);
|
||||
float dd = kknd_dist2(u->x, u->y, bx, by);
|
||||
if (dd < rbest) { rbest = dd; rid = b->id; }
|
||||
}
|
||||
return rid;
|
||||
}
|
||||
|
||||
/* Harvester behaviour: travels in STATE_MOVING, loads only in
|
||||
* STATE_GATHERING (whilst sitting on a scrap tile). */
|
||||
static void unit_harvester_step(Game *g, Unit *u, float dt) {
|
||||
(void)dt;
|
||||
const UnitDef *d = unit_def(u->type, u->faction);
|
||||
int tx, ty; map_world_to_tile(u->x, u->y, &tx, &ty);
|
||||
Tile *t = map_tile(&g->map, tx, ty);
|
||||
|
||||
if (u->order == ORDER_NONE) u->order = ORDER_GATHER;
|
||||
|
||||
if (u->state == STATE_IDLE) {
|
||||
if (t && t->terrain == T_SCRAP && t->scrap > 0 && u->cargo < d->cargo) {
|
||||
u->state = STATE_GATHERING;
|
||||
} else if (u->cargo >= d->cargo) {
|
||||
int rid = harvester_find_refinery(g, u);
|
||||
Building *b = (rid >= 0) ? bld_by_id(g, rid) : NULL;
|
||||
if (b) {
|
||||
u->dest_x = (float)(b->tx*TILE + b->w*TILE/2);
|
||||
u->dest_y = (float)(b->ty*TILE + b->h*TILE/2);
|
||||
u->state = STATE_MOVING; unit_repath(g, u, u->dest_x, u->dest_y);
|
||||
}
|
||||
} else {
|
||||
float sx, sy;
|
||||
if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) {
|
||||
u->order = ORDER_GATHER;
|
||||
u->dest_x = sx; u->dest_y = sy;
|
||||
u->state = STATE_MOVING; unit_repath(g, u, sx, sy);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (u->state == STATE_GATHERING) {
|
||||
if (t && t->terrain == T_SCRAP && t->scrap > 0 && u->cargo < d->cargo) {
|
||||
int take = ((int)t->scrap > 4) ? 4 : (int)t->scrap;
|
||||
int add = (d->cargo - u->cargo > take) ? take : (int)(d->cargo - u->cargo);
|
||||
u->cargo += add;
|
||||
map_deplete_scrap(&g->map, tx, ty, add);
|
||||
u->anim = 1.0f;
|
||||
}
|
||||
if (u->cargo >= d->cargo) {
|
||||
int rid = harvester_find_refinery(g, u);
|
||||
Building *b = (rid >= 0) ? bld_by_id(g, rid) : NULL;
|
||||
if (b) {
|
||||
u->dest_x = (float)(b->tx*TILE + b->w*TILE/2);
|
||||
u->dest_y = (float)(b->ty*TILE + b->h*TILE/2);
|
||||
u->state = STATE_MOVING; unit_repath(g, u, u->dest_x, u->dest_y);
|
||||
} else u->state = STATE_IDLE;
|
||||
} else if (!t || t->terrain != T_SCRAP || t->scrap == 0) {
|
||||
float sx, sy;
|
||||
if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) {
|
||||
u->dest_x = sx; u->dest_y = sy; u->state = STATE_MOVING;
|
||||
unit_repath(g, u, sx, sy);
|
||||
} else u->state = STATE_IDLE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* travelling: handle arrival at refinery (deposit) or scrap (load) */
|
||||
if (u->state == STATE_MOVING &&
|
||||
(u->order == ORDER_GATHER || u->order == ORDER_RETURN)) {
|
||||
int rid = harvester_find_refinery(g, u);
|
||||
if (rid >= 0) {
|
||||
Building *b = bld_by_id(g, rid);
|
||||
float bx = (float)(b->tx*TILE + b->w*TILE/2);
|
||||
float by = (float)(b->ty*TILE + b->h*TILE/2);
|
||||
if (kknd_dist(u->x, u->y, bx, by) < TILE*1.4f) {
|
||||
g->scrap[u->faction] += (int)u->cargo;
|
||||
u->cargo = 0; audio_sfx(3);
|
||||
float sx, sy;
|
||||
if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) {
|
||||
u->dest_x = sx; u->dest_y = sy; u->state = STATE_MOVING;
|
||||
unit_repath(g, u, sx, sy);
|
||||
} else u->state = STATE_IDLE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
int arrived = (u->path.len == 0 || u->path_idx >= u->path.len);
|
||||
if (arrived) {
|
||||
if (u->cargo < d->cargo && t && t->terrain == T_SCRAP && t->scrap > 0) {
|
||||
u->state = STATE_GATHERING;
|
||||
} else {
|
||||
float sx, sy;
|
||||
if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) {
|
||||
u->dest_x = sx; u->dest_y = sy; u->state = STATE_MOVING;
|
||||
unit_repath(g, u, sx, sy);
|
||||
} else u->state = STATE_IDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void unit_update(Game *g, float dt) {
|
||||
/* separation + obstacle-avoidance pass (cheap O(n^2) capped) */
|
||||
for (int i = 0; i < g->unit_count; i++) {
|
||||
Unit *u = &g->units[i];
|
||||
if (u->dead || u->speed <= 0) continue;
|
||||
float fx = 0, fy = 0;
|
||||
float rad = (u->type == UT_TANK || u->type == UT_BEAST) ? 15.0f : 12.0f;
|
||||
for (int j = 0; j < g->unit_count; j++) {
|
||||
if (i == j) continue;
|
||||
Unit *o = &g->units[j];
|
||||
if (o->dead) continue;
|
||||
float dx = u->x - o->x, dy = u->y - o->y;
|
||||
float d2 = dx*dx + dy*dy;
|
||||
float rr = rad * 2.0f;
|
||||
if (d2 > 0 && d2 < rr*rr) {
|
||||
float d = (float)sqrtf(d2);
|
||||
float push = (rr - d) / rr; /* 0..1, stronger when closer */
|
||||
fx += dx/d * push; fy += dy/d * push;
|
||||
}
|
||||
}
|
||||
/* push out of building footprints */
|
||||
for (int b = 0; b < g->bld_count; b++) {
|
||||
Building *bd = &g->buildings[b];
|
||||
if (bd->dead) continue;
|
||||
float bx0 = bd->tx*TILE, by0 = bd->ty*TILE;
|
||||
float bx1 = bx0 + bd->w*TILE, by1 = by0 + bd->h*TILE;
|
||||
if (u->x > bx0-rad && u->x < bx1+rad && u->y > by0-rad && u->y < by1+rad) {
|
||||
/* closest point on rect */
|
||||
float cxp = u->x < bx0 ? bx0 : (u->x > bx1 ? bx1 : u->x);
|
||||
float cyp = u->y < by0 ? by0 : (u->y > by1 ? by1 : u->y);
|
||||
float dx = u->x - cxp, dy = u->y - cyp;
|
||||
float d2 = dx*dx + dy*dy;
|
||||
if (d2 < rad*rad) {
|
||||
if (d2 < 0.01f) { dx = u->x - (bx0+bx1)/2; dy = u->y - (by0+by1)/2; d2 = dx*dx+dy*dy; }
|
||||
float d = (float)sqrtf(d2);
|
||||
fx += dx/d * 1.5f; fy += dy/d * 1.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
u->x += fx * 26 * dt;
|
||||
u->y += fy * 26 * dt;
|
||||
}
|
||||
|
||||
for (int i = 0; i < g->unit_count; i++) {
|
||||
Unit *u = &g->units[i];
|
||||
if (u->dead) continue;
|
||||
if (u->anim > 0) u->anim -= dt * 3;
|
||||
if (u->repath_timer > 0) u->repath_timer -= dt;
|
||||
if (u->ability_cd > 0) u->ability_cd -= dt;
|
||||
|
||||
/* siege mode: immobile while active */
|
||||
if (u->ability == AB_SIEGE && u->ability_on && u->state == STATE_MOVING) {
|
||||
u->state = STATE_IDLE; u->path.len = 0; u->path_idx = 0;
|
||||
}
|
||||
|
||||
/* medic / repair aura: heal nearby wounded allies */
|
||||
if (u->ability == AB_HEAL) {
|
||||
float heal_r = 70.0f;
|
||||
float rate = (u->ability_cd > 0) ? 60.0f : 22.0f; /* burst pulses faster */
|
||||
for (int k = 0; k < g->unit_count; k++) {
|
||||
Unit *a = &g->units[k];
|
||||
if (a->dead || a->faction != u->faction || a == u) continue;
|
||||
if (a->hp >= a->max_hp) continue;
|
||||
if (kknd_dist2(u->x, u->y, a->x, a->y) < heal_r*heal_r) {
|
||||
a->hp = (a->hp + rate*dt > a->max_hp) ? a->max_hp : a->hp + rate*dt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (u->type == UT_HARVESTER) {
|
||||
unit_harvester_step(g, u, dt);
|
||||
}
|
||||
|
||||
if (u->attack > 0) {
|
||||
if (u->order == ORDER_ATTACK) {
|
||||
unit_combat_step(g, u, dt);
|
||||
} else {
|
||||
/* auto-acquire while idle / attack-moving */
|
||||
if (u->state == STATE_IDLE || u->order == ORDER_ATTACKMOVE) {
|
||||
float acq = u->hold ? u->range : u->sight;
|
||||
int e = unit_find_enemy_unit(g, u, acq);
|
||||
if (e < 0) {
|
||||
int b = unit_find_enemy_bld(g, u, acq);
|
||||
if (b >= 0) { u->target_bld = b; u->order = ORDER_ATTACK;
|
||||
u->state = STATE_ATTACKING; }
|
||||
} else {
|
||||
u->target_unit = e; u->order = ORDER_ATTACK;
|
||||
u->state = STATE_ATTACKING;
|
||||
}
|
||||
} else if (u->order == ORDER_ATTACKMOVE) {
|
||||
if (u->atk_cd <= 0) {
|
||||
int e = unit_find_enemy_unit(g, u, u->range + 8);
|
||||
int b = unit_find_enemy_bld(g, u, u->range + 8);
|
||||
if (e >= 0) { u->target_unit = e; unit_combat_step(g, u, dt); }
|
||||
else if (b >= 0) { u->target_bld = b; unit_combat_step(g, u, dt); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* movement along path */
|
||||
if (u->state == STATE_MOVING && u->path.len > 0) {
|
||||
if (u->path_idx >= u->path.len) {
|
||||
u->state = STATE_IDLE;
|
||||
u->path.len = 0;
|
||||
} else {
|
||||
float wx, wy;
|
||||
map_tile_to_world(u->path.nodes[u->path_idx].tx,
|
||||
u->path.nodes[u->path_idx].ty, &wx, &wy);
|
||||
float d = kknd_dist(u->x, u->y, wx, wy);
|
||||
if (d < 4) {
|
||||
u->path_idx++;
|
||||
} else {
|
||||
float sp = u->speed * dt;
|
||||
u->heading = (float)atan2(wy - u->y, wx - u->x);
|
||||
u->x += (wx - u->x) / d * sp;
|
||||
u->y += (wy - u->y) / d * sp;
|
||||
u->anim = 1.0f;
|
||||
}
|
||||
}
|
||||
} else if (u->state == STATE_MOVING) {
|
||||
/* no path: move straight */
|
||||
float d = kknd_dist(u->x, u->y, u->dest_x, u->dest_y);
|
||||
if (d < 4) { u->state = STATE_IDLE; }
|
||||
else {
|
||||
float sp = u->speed * dt;
|
||||
u->x += (u->dest_x - u->x)/d * sp;
|
||||
u->y += (u->dest_y - u->y)/d * sp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* cull dead */
|
||||
for (int i = g->unit_count - 1; i >= 0; i--)
|
||||
if (g->units[i].dead) unit_remove(g, i);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue