diff options
| author | Vaino Kauppila <vaino@vke.fi> | 2026-07-30 11:56:27 +0300 |
|---|---|---|
| committer | Vaino Kauppila <vaino@vke.fi> | 2026-07-30 11:56:27 +0300 |
| commit | 6cdd436caf9e2103f516442ab6e70a870d4cba04 (patch) | |
| tree | 7a69242d34ef3739f3e554227213ec5d91a2a958 /src/engine | |
| parent | 521b5da83485a956811fc510cba9f139acad8b26 (diff) | |
| download | forts_clone-6cdd436caf9e2103f516442ab6e70a870d4cba04.tar.gz forts_clone-6cdd436caf9e2103f516442ab6e70a870d4cba04.zip | |
Mount devices on the strut graph so cutting supports destroys them
Implements win-condition path (c): destroy the enemy reactor by cutting
its supports so it falls. Previously devices sat at fixed map coordinates,
structurally immune to collapse.
- BuildGraph gains MountedDevice tracking: mount_device/unmount_device,
is_device_alive/get_device_hp/damage_device, nearest_device, with a
break_node index-shift fix so mount references stay correct as other
geometry on the same fort is destroyed. update_fire now also damages
any device mounted on a burning strut.
- app.cpp mounts devices onto the nearest strut (within 2 units) at both
map-load and interactive placement, and after each tick's structural
collapse pass, zeroes a device's HP if its mount didn't survive -- which
flows through the existing win-condition/cleanup code unchanged.
- tools/device_collapse_test.cpp: 15 headless tests covering collapse,
partial support loss, splash/beam/fire-through-mount damage, multiple
devices, index stability, and clean unmount.
Verified: full test suite passes (15/15), sim_check shows zero regressions
across all 3 maps.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'src/engine')
| -rw-r--r-- | src/engine/app.cpp | 32 |
1 files changed, 29 insertions, 3 deletions
diff --git a/src/engine/app.cpp b/src/engine/app.cpp index 0de71c5..cfe924c 100644 --- a/src/engine/app.cpp +++ b/src/engine/app.cpp @@ -148,7 +148,7 @@ void App::run() { device_defs[i].texture); SDL_Texture* deposit_tex = renderer.load_texture("data/textures/deposit.png"); - struct Device { int def, team; float x, y, hp, max_hp; bool on_deposit; }; + struct Device { int def, team; float x, y, hp, max_hp; bool on_deposit; int graph_id = -1; }; std::vector<Device> devices; auto devidx = [&](const char* n) -> int { @@ -177,11 +177,24 @@ void App::run() { int cur_mat = 0; int place_dev = -1; // >=0 = placing that device type; -1 = build materials + // Mount a device onto the nearest strut so cutting its supports destroys + // it (win-condition path (c): reactor falls when its supports are cut). + // Devices with no nearby strut (radius 2.0) stay unmounted (-1) and are + // only killed by weapon damage, same as before this feature existed. + const float MOUNT_RADIUS = 2.0f; + auto mount_at = [&](float x, float y, int def_idx, int team, float hp) -> int { + int eid = graph.find_nearest_edge(x, y, MOUNT_RADIUS); + if (eid < 0) return -1; + const BuildEdge& e = graph.edges[eid]; + return graph.mount_device(e.node_a, e.node_b, def_idx, team, hp); + }; + auto reset = [&]() { const MapDef& m = maps[current_map]; graph.nodes = snap_nodes; graph.edges = snap_edges; graph.rebuild(); + graph.devices.clear(); devices.clear(); for (auto& pd : m.devices) { int di = -1; @@ -190,6 +203,7 @@ void App::run() { if (di < 0) continue; Device dv{di, pd.team, pd.x, pd.y, device_defs[di].hp, device_defs[di].hp, true}; if (device_defs[di].needs_deposit) dv.on_deposit = on_deposit(pd.x, pd.y); + dv.graph_id = mount_at(pd.x, pd.y, di, pd.team, device_defs[di].hp); devices.push_back(dv); } cannons.clear(); @@ -317,8 +331,10 @@ void App::run() { const DeviceDef& def = device_defs[place_dev]; bool dep_ok = !def.needs_deposit || on_deposit(wx, wy); if (dep_ok && metal >= def.cost_metal && energy >= def.cost_energy) { - devices.push_back({ place_dev, 0, wx, wy, def.hp, def.hp, - def.needs_deposit ? on_deposit(wx, wy) : true }); + Device dv{ place_dev, 0, wx, wy, def.hp, def.hp, + def.needs_deposit ? on_deposit(wx, wy) : true }; + dv.graph_id = mount_at(wx, wy, place_dev, 0, def.hp); + devices.push_back(dv); metal -= def.cost_metal; energy -= def.cost_energy; } @@ -419,6 +435,14 @@ void App::run() { graph.update_fire(FIXED_DT); // burn DoT + spread graph.kill_grounded(); // destroy debris below the ground + // Devices mounted on the strut graph: if structural collapse (a + // snapped strut, kill_grounded) took out both mount nodes, the + // device falls too -- this is win-condition path (c), cutting + // supports to drop the reactor. + for (auto& dv : devices) + if (dv.graph_id >= 0 && !graph.is_device_alive(dv.graph_id)) + dv.hp = 0.0f; + // Weapons: tick reloads. for (auto& c : cannons) if (c.cooldown > 0.0f) c.cooldown = std::max(0.0f, c.cooldown - FIXED_DT); @@ -487,6 +511,8 @@ void App::run() { } accumulator -= FIXED_DT; } + for (auto& dv : devices) + if (dv.hp <= 0.0f && dv.graph_id >= 0) graph.unmount_device(dv.graph_id); devices.erase(std::remove_if(devices.begin(), devices.end(), [](const Device& d){ return d.hp <= 0.0f; }), devices.end()); projectiles.erase( |
