aboutsummaryrefslogtreecommitdiffstats
path: root/src/engine/app.cpp
diff options
context:
space:
mode:
authorVaino Kauppila <vaino@vke.fi>2026-07-30 11:56:27 +0300
committerVaino Kauppila <vaino@vke.fi>2026-07-30 11:56:27 +0300
commit6cdd436caf9e2103f516442ab6e70a870d4cba04 (patch)
tree7a69242d34ef3739f3e554227213ec5d91a2a958 /src/engine/app.cpp
parent521b5da83485a956811fc510cba9f139acad8b26 (diff)
downloadforts_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/app.cpp')
-rw-r--r--src/engine/app.cpp32
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(