aboutsummaryrefslogtreecommitdiffstats
path: root/src/game
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/game
parentff85499d5567c36ce63e2d7903d5f872fd9aef1d (diff)
downloadforts_clone-main.tar.gz
forts_clone-main.zip
New maps and map selectormain
Diffstat (limited to 'src/game')
-rw-r--r--src/game/data.cpp107
-rw-r--r--src/game/data.hpp37
2 files changed, 105 insertions, 39 deletions
diff --git a/src/game/data.cpp b/src/game/data.cpp
index 3d9f6c0..bbcf2d0 100644
--- a/src/game/data.cpp
+++ b/src/game/data.cpp
@@ -163,11 +163,17 @@ std::vector<WeaponDef> load_weapons(ScriptEngine& script, const std::string& dat
return out;
}
-void load_structures(ScriptEngine& script, const std::string& data_dir,
- BuildGraph& graph) {
- run_layered(script, data_dir, "structures.lua");
- sol::optional<sol::table> structs = script.lua()["Structures"];
- if (!structs) return;
+void build_map(const MapDef& m, BuildGraph& graph) {
+ graph.nodes.clear();
+ graph.edges.clear();
+ // Clearing the vectors does NOT touch the private adjacency and edge-key
+ // caches, so rebuild them now. Skipping this leaves the PREVIOUS map's edge
+ // keys in the set and add_edge then refuses the new map's beams as
+ // duplicates, quietly producing a fort that collapses on load.
+ graph.rebuild();
+ // Set the ground BEFORE welding: node_at asks is_on_ground() to decide which
+ // endpoints are pinned foundations.
+ graph.ground_level = m.ground_level;
auto mat_index = [&](const std::string& nm) -> int {
for (int i = 0; i < (int)graph.materials.size(); i++)
@@ -180,25 +186,22 @@ void load_structures(ScriptEngine& script, const std::string& data_dir,
};
int built = 0;
- for (std::size_t si = 1; ; si++) {
- sol::object so = (*structs)[si];
- if (so.get_type() != sol::type::table) break;
- sol::table S = so.as<sol::table>();
- sol::optional<sol::table> beams = S["beams"];
- if (!beams) continue;
- for (std::size_t bi = 1; ; bi++) {
- sol::object bo = (*beams)[bi];
- if (bo.get_type() != sol::type::table) break;
- sol::table b = bo.as<sol::table>();
- float x1 = b.get_or(1, 0.0f), y1 = b.get_or(2, 0.0f);
- float x2 = b.get_or(3, 0.0f), y2 = b.get_or(4, 0.0f);
- std::string mat = b.get_or(5, std::string("wood"));
- if (graph.add_edge(node_at(x1, y1), node_at(x2, y2), mat_index(mat)) >= 0)
- built++;
- }
+ for (const auto& b : m.beams) {
+ int na = node_at(b.x1, b.y1), nb = node_at(b.x2, b.y2);
+ if (graph.add_edge(na, nb, mat_index(b.material)) >= 0) { built++; continue; }
+ // A dropped beam silently weakens the fort, so never fail quietly:
+ // endpoints closer than SNAP_RADIUS collapse onto one node, and a
+ // repeated node pair is refused as a duplicate.
+ fprintf(stderr,
+ "map '%s': beam (%g,%g)-(%g,%g) [%s] rejected -> nodes %d,%d%s\n",
+ m.name.c_str(), b.x1, b.y1, b.x2, b.y2, b.material.c_str(),
+ na, nb, na == nb ? " (endpoints snapped together)" : " (duplicate)");
}
+
for (auto& e : graph.edges) e.settle = 0.0f; // prebuilt = already cured
- printf("Loaded %d prebuilt struts from Structures\n", built);
+ graph.rebuild();
+ printf("Map '%s': %d prebuilt struts, ground %.1f\n",
+ m.name.c_str(), built, m.ground_level);
}
namespace {
@@ -243,10 +246,14 @@ std::vector<DeviceDef> load_devices(ScriptEngine& script, const std::string& dat
return out;
}
-MapData load_map(ScriptEngine& script, const std::string& data_dir) {
- MapData m;
- run_layered(script, data_dir, "map.lua");
- if (sol::optional<sol::table> dep = script.lua()["Deposits"]) {
+namespace {
+MapDef read_map(const sol::table& M) {
+ MapDef m;
+ m.name = M.get_or("name", std::string("unnamed"));
+ m.description = M.get_or("description", std::string(""));
+ m.ground_level = M.get_or("ground_level", 1.0f);
+
+ if (sol::optional<sol::table> dep = M["deposits"]) {
for (std::size_t i = 1; ; i++) {
sol::object o = (*dep)[i];
if (o.get_type() != sol::type::table) break;
@@ -254,9 +261,9 @@ MapData load_map(ScriptEngine& script, const std::string& data_dir) {
m.deposits.emplace_back(p.get_or(1, 0.0f), p.get_or(2, 0.0f));
}
}
- if (sol::optional<sol::table> md = script.lua()["MapDevices"]) {
+ if (sol::optional<sol::table> dv = M["devices"]) {
for (std::size_t i = 1; ; i++) {
- sol::object o = (*md)[i];
+ sol::object o = (*dv)[i];
if (o.get_type() != sol::type::table) break;
sol::table d = o.as<sol::table>();
m.devices.push_back({ d.get_or("type", std::string("reactor")),
@@ -264,6 +271,48 @@ MapData load_map(ScriptEngine& script, const std::string& data_dir) {
d.get_or("team", 0) });
}
}
- printf("Loaded map: %zu deposits, %zu devices\n", m.deposits.size(), m.devices.size());
+ if (sol::optional<sol::table> wp = M["weapons"]) {
+ for (std::size_t i = 1; ; i++) {
+ sol::object o = (*wp)[i];
+ if (o.get_type() != sol::type::table) break;
+ sol::table w = o.as<sol::table>();
+ m.weapons.push_back({ w.get_or("type", std::string("cannon")),
+ w.get_or("x", 0.0f), w.get_or("y", 0.0f) });
+ }
+ }
+ // structures[] -> beams[] {x1,y1,x2,y2,material}, flattened: which fort a
+ // beam belonged to does not matter once it is welded into the graph.
+ if (sol::optional<sol::table> st = M["structures"]) {
+ for (std::size_t si = 1; ; si++) {
+ sol::object so = (*st)[si];
+ if (so.get_type() != sol::type::table) break;
+ sol::table S = so.as<sol::table>();
+ sol::optional<sol::table> beams = S["beams"];
+ if (!beams) continue;
+ for (std::size_t bi = 1; ; bi++) {
+ sol::object bo = (*beams)[bi];
+ if (bo.get_type() != sol::type::table) break;
+ sol::table b = bo.as<sol::table>();
+ m.beams.push_back({ b.get_or(1, 0.0f), b.get_or(2, 0.0f),
+ b.get_or(3, 0.0f), b.get_or(4, 0.0f),
+ b.get_or(5, std::string("wood")) });
+ }
+ }
+ }
return m;
}
+} // namespace
+
+std::vector<MapDef> load_maps(ScriptEngine& script, const std::string& data_dir) {
+ std::vector<MapDef> out;
+ run_layered(script, data_dir, "maps.lua");
+ sol::optional<sol::table> maps = script.lua()["Maps"];
+ if (!maps) { fprintf(stderr, "load_maps: no global 'Maps' table\n"); return out; }
+ for (std::size_t i = 1; ; i++) {
+ sol::object o = (*maps)[i];
+ if (o.get_type() != sol::type::table) break;
+ out.push_back(read_map(o.as<sol::table>()));
+ }
+ printf("Loaded %zu maps\n", out.size());
+ return out;
+}
diff --git a/src/game/data.hpp b/src/game/data.hpp
index 9b9aec1..3207ba0 100644
--- a/src/game/data.hpp
+++ b/src/game/data.hpp
@@ -43,11 +43,9 @@ std::vector<MaterialDef> load_materials(ScriptEngine& script,
std::vector<WeaponDef> load_weapons(ScriptEngine& script,
const std::string& data_dir);
-// Loads prebuilt structures from <data_dir>/structures.lua (+ mods) and builds
-// them into `graph`. Each structure is a list of beams {x1,y1,x2,y2,material}.
-// Used for scenario forts (e.g. the enemy target). Requires graph.materials set.
-void load_structures(ScriptEngine& script, const std::string& data_dir,
- BuildGraph& graph);
+// A prebuilt strut of a scenario fort, kept in map data so a map can be rebuilt
+// on demand (the dev UI switches maps at runtime).
+struct MapBeam { float x1, y1, x2, y2; std::string material; };
// A device type (reactor / mine / turbine / battery / ...), from data/devices.lua.
struct DeviceDef {
@@ -66,13 +64,32 @@ struct DeviceDef {
float r, g, b; // fallback colour when no texture
};
-// Scenario map: metal deposits + pre-placed devices (player reactor, enemy
-// reactor, starter generators), from data/map.lua (+ mods).
struct PlacedDevice { std::string type; float x, y; int team; };
-struct MapData {
+struct PlacedWeapon { std::string type; float x, y; };
+
+// One selectable scenario from data/maps.lua (+ mod layers): the ground height,
+// metal deposits, pre-placed devices (reactors, generators), the player's
+// starting weapons, and every prebuilt strut. Self-contained on purpose, so the
+// dev UI can tear the world down and rebuild it from another map at runtime.
+//
+// NOTE: terrain is still a flat half-plane, so `ground_level` moves the ground
+// up and down but cannot change its SHAPE. Real terrain is M6.
+struct MapDef {
+ std::string name;
+ std::string description;
+ float ground_level = 1.0f;
std::vector<std::pair<float,float>> deposits;
- std::vector<PlacedDevice> devices;
+ std::vector<PlacedDevice> devices;
+ std::vector<PlacedWeapon> weapons;
+ std::vector<MapBeam> beams;
};
std::vector<DeviceDef> load_devices(ScriptEngine& script, const std::string& data_dir);
-MapData load_map(ScriptEngine& script, const std::string& data_dir);
+
+// Loads every map from <data_dir>/maps.lua (+ mods), same layering as the rest.
+std::vector<MapDef> load_maps(ScriptEngine& script, const std::string& data_dir);
+
+// Clears `graph` and rebuilds it from `m`: sets ground_level, then welds every
+// prebuilt beam (creating/merging endpoint nodes, ground-level ones becoming
+// foundations). Requires graph.materials to be loaded.
+void build_map(const MapDef& m, BuildGraph& graph);