aboutsummaryrefslogtreecommitdiffstats
path: root/src/engine/app.cpp
diff options
context:
space:
mode:
authorEmil A. Overbeck <emil.a.overbeck@gmail.com>2026-07-19 04:19:48 +0200
committerEmil A. Overbeck <emil.a.overbeck@gmail.com>2026-07-19 04:19:48 +0200
commit521b5da83485a956811fc510cba9f139acad8b26 (patch)
tree29b210263300479e9a565af568812c5e707d23f7 /src/engine/app.cpp
parentff85499d5567c36ce63e2d7903d5f872fd9aef1d (diff)
downloadforts_clone-521b5da83485a956811fc510cba9f139acad8b26.tar.gz
forts_clone-521b5da83485a956811fc510cba9f139acad8b26.zip
New maps and map selectormain
Diffstat (limited to 'src/engine/app.cpp')
-rw-r--r--src/engine/app.cpp65
1 files changed, 51 insertions, 14 deletions
diff --git a/src/engine/app.cpp b/src/engine/app.cpp
index fa1155c..0de71c5 100644
--- a/src/engine/app.cpp
+++ b/src/engine/app.cpp
@@ -89,7 +89,9 @@ void App::run() {
camera.set_zoom(10.0f);
// The world is a simple half-plane: below ground_level is solid ground,
- // above is sky. Anything that falls into the ground is destroyed.
+ // above is sky. Anything that falls into the ground is destroyed. The
+ // selected map sets the actual height (build_map); this is just the default
+ // until the first map is loaded below.
graph.ground_level = 1.0f;
// Resolve each material's texture once: prefer BYO Forts art, fall back to
@@ -137,7 +139,9 @@ void App::run() {
// --- Devices + map (M5) ---------------------------------------------------
std::vector<DeviceDef> device_defs = load_devices(script, "data");
- MapData map = load_map(script, "data");
+ std::vector<MapDef> maps = load_maps(script, "data");
+ if (maps.empty()) maps.push_back(MapDef{}); // empty fallback: flat, bare
+ int current_map = 0; // Maps[1] in Lua = the default
std::vector<SDL_Texture*> dev_tex(device_defs.size(), nullptr);
for (size_t i = 0; i < device_defs.size(); i++)
dev_tex[i] = renderer.load_texture_or(device_defs[i].texture_forts,
@@ -155,7 +159,7 @@ void App::run() {
int turbine_i = devidx("turbine"), mine_i = devidx("mine"), battery_i = devidx("battery");
auto on_deposit = [&](float x, float y) -> bool {
- for (auto& d : map.deposits)
+ for (auto& d : maps[current_map].deposits)
if (std::hypot(x - d.first, y - d.second) < 1.6f) return true;
return false;
};
@@ -164,22 +168,22 @@ void App::run() {
float metal = 0.0f, energy = 0.0f, ecap = BASE_ECAP, mcap = BASE_MCAP;
bool game_over = false; int winner = -1; // 0 = player, 1 = enemy
- // Build the scenario once, snapshot the graph, then reset() rebuilds it all
- // (used on 'R') WITHOUT re-running the Lua (which would duplicate structures).
- load_structures(script, "data", graph);
- std::vector<BuildNode> snap_nodes = graph.nodes;
- std::vector<BuildEdge> snap_edges = graph.edges;
+ // Snapshot of the current map's prebuilt structure. reset() ('R') restores
+ // from it rather than re-running the Lua, which would duplicate the forts.
+ std::vector<BuildNode> snap_nodes;
+ std::vector<BuildEdge> snap_edges;
int selected_cannon = -1;
int cur_mat = 0;
int place_dev = -1; // >=0 = placing that device type; -1 = build materials
auto reset = [&]() {
+ const MapDef& m = maps[current_map];
graph.nodes = snap_nodes;
graph.edges = snap_edges;
graph.rebuild();
devices.clear();
- for (auto& pd : map.devices) {
+ for (auto& pd : m.devices) {
int di = -1;
for (size_t i = 0; i < device_defs.size(); i++)
if (device_defs[i].name == pd.type) di = (int)i;
@@ -189,14 +193,29 @@ void App::run() {
devices.push_back(dv);
}
cannons.clear();
- cannons.push_back({-18.0f, graph.ground_level + 0.6f, 0.0f, 0.0f, 0.0f, cannon_type});
- if (laser_type >= 0)
- cannons.push_back({-14.0f, graph.ground_level + 0.6f, 0.0f, 0.0f, 0.0f, laser_type});
+ for (auto& pw : m.weapons) {
+ int t = pw.type == "laser" ? laser_type : cannon_type;
+ if (t < 0) continue;
+ cannons.push_back({pw.x, pw.y, 0.0f, 0.0f, 0.0f, t});
+ }
+ if (cannons.empty()) // map declared no weapons: fall back to a cannon
+ cannons.push_back({-18.0f, graph.ground_level + 0.6f,
+ 0.0f, 0.0f, 0.0f, cannon_type});
projectiles.clear(); beams.clear(); explosions.clear();
metal = 200.0f; energy = 200.0f;
game_over = false; winner = -1; selected_cannon = -1; place_dev = -1;
};
- reset();
+
+ // Tear the world down and rebuild it from maps[i]. Used at startup and by
+ // the Dev panel's map selector.
+ auto load_map_at = [&](int i) {
+ current_map = std::clamp(i, 0, (int)maps.size() - 1);
+ build_map(maps[current_map], graph); // also sets graph.ground_level
+ snap_nodes = graph.nodes;
+ snap_edges = graph.edges;
+ reset();
+ };
+ load_map_at(current_map);
// Build state
int drag_node = -1;
@@ -517,7 +536,7 @@ void App::run() {
}
// Metal deposits (markers on the ground; mines must sit on these).
- for (auto& d : map.deposits) {
+ for (auto& d : maps[current_map].deposits) {
if (deposit_tex) renderer.draw_sprite(d.first, d.second + 0.2f, 0.35f, 0.35f, 0.0f, deposit_tex);
else renderer.draw_box(d.first, d.second + 0.2f, 0.3f, 0.3f, 0.0f, 0.8f, 0.7f, 0.2f, 0.9f);
}
@@ -788,6 +807,24 @@ void App::run() {
if (battery_i >= 0) { ImGui::SameLine(); if (ImGui::Button("battery")) { place_dev = battery_i; selected_cannon = -1; } }
if (ImGui::Button("restart (R)")) reset();
ImGui::Separator();
+ // Map selector. Picking one rebuilds the world from that map.
+ if (ImGui::CollapsingHeader("Map", ImGuiTreeNodeFlags_DefaultOpen)) {
+ const MapDef& cur = maps[current_map];
+ if (ImGui::BeginCombo("scenario", cur.name.c_str())) {
+ for (int i = 0; i < (int)maps.size(); i++) {
+ bool sel = (i == current_map);
+ if (ImGui::Selectable(maps[i].name.c_str(), sel) && !sel)
+ load_map_at(i);
+ if (sel) ImGui::SetItemDefaultFocus();
+ }
+ ImGui::EndCombo();
+ }
+ if (!cur.description.empty())
+ ImGui::TextWrapped("%s", cur.description.c_str());
+ ImGui::Text("ground %.1f deposits %zu struts %zu",
+ cur.ground_level, cur.deposits.size(), graph.edges.size());
+ if (ImGui::Button("reload map")) load_map_at(current_map);
+ }
if (ImGui::CollapsingHeader("Physics", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::SliderFloat("gravity", &graph.gravity, 0.0f, 40.0f);
ImGui::SliderFloat("air drag", &graph.air_drag, 0.0f, 3.0f);