aboutsummaryrefslogtreecommitdiffstats
path: root/src/game/build_graph.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/game/build_graph.hpp')
-rw-r--r--src/game/build_graph.hpp151
1 files changed, 151 insertions, 0 deletions
diff --git a/src/game/build_graph.hpp b/src/game/build_graph.hpp
new file mode 100644
index 0000000..f92dbd3
--- /dev/null
+++ b/src/game/build_graph.hpp
@@ -0,0 +1,151 @@
+#pragma once
+
+#include <vector>
+#include <cstdint>
+#include <string>
+#include <unordered_set>
+
+// The building structure is a single graph of nodes (point masses) joined by
+// edges (struts). The solver is a stiff damped mass-spring system — the canon
+// Forts model: each strut is a Hookean spring (F = k.dx) with damping, and the
+// stiff springs are integrated with oversampling for stability.
+//
+// There is ONE index space (node and edge indices). Rigidity is geometric
+// (triangulation): a lone strut is a free-hinging pin joint that SNAPS once it
+// rotates past its material's angle threshold (Forts' 30-degree rule), or once
+// it deforms axially past MaxCompression/MaxExpansion.
+
+// Material definition — loaded from Lua data (see game/data.cpp).
+struct MaterialDef {
+ float stiffness; // spring constant k (force per unit stretch)
+ float damping; // axial spring damping
+ float mass; // contributed to each endpoint node
+ float max_compression; // snap if length/rest < this (e.g. 0.90 = -10%)
+ float max_expansion; // snap if length/rest > this (e.g. 1.10 = +10%)
+ float angle_threshold; // snap a loose strut rotated this far (radians)
+ float min_length; // shortest a single strut may be (placement rejected below)
+ float max_length; // longest a single segment (a longer drag subdivides)
+ float max_link_length; // longest a whole drag/link (rejected beyond)
+ float hit_points; // HP pool depleted by weapon damage (separate from stress)
+ bool tension_only; // ropes: slack (no force, no snap) in compression
+ bool flammable; // can catch fire
+ float burn_rate; // HP lost per second while burning
+ float spread_time; // seconds of burning before it ignites a neighbour
+ bool blocks_projectiles; // stops cannon shells (wood yes; bg-brace/rope no)
+ bool blocks_beam; // stops laser beams (all yes in Forts)
+ float half_width; // visual thickness
+ float r, g, b, a; // visual colour (also tints the texture)
+ std::string name;
+ std::string texture; // optional texture path ("" = flat colour)
+};
+
+struct BuildNode {
+ float x, y; // position
+ float vx, vy; // velocity
+ float mass; // aggregate of incident struts (>= MIN_NODE_MASS)
+ bool is_foundation; // pinned to the ground
+};
+
+struct BuildEdge {
+ int node_a, node_b; // node indices
+ int mat; // index into MATERIALS
+ float rest_length; // captured at build time
+ float rest_angle; // world orientation at build time
+ float stress; // signed deformation (length-rest)/rest, for colour
+ float hp, max_hp; // weapon-damage health pool
+ bool burning; // on fire (takes damage-over-time, spreads)
+ float burn; // seconds spent burning (drives spread timing)
+ float settle; // build-grace timer (s); while >0 the strut is held rigid
+ bool triangulated; // cached: part of a triangle (rigid) — set by topology
+ float half_width; // cached from material (render)
+ float r, g, b, a; // cached from material (render)
+};
+
+struct BuildFace { int node_a, node_b, node_c; };
+
+class BuildGraph {
+public:
+ // Material table, loaded from Lua at startup (build modes index into it).
+ std::vector<MaterialDef> materials;
+
+ // --- tunables ---------------------------------------------------------
+ static constexpr float SETTLE_TIME = 4.0f; // rigid build grace (Forts TempBracing)
+ static constexpr float SNAP_RADIUS = 0.4f; // node merge distance
+ static constexpr float MIN_BRACE_LENGTH = 0.6f; // smallest auto brace
+ static constexpr int OVERSAMPLES = 14; // spring substeps per tick
+ static constexpr float MIN_NODE_MASS = 0.05f;
+
+ float ground_level = 1.0f; // below = solid ground, above = sky
+ float gravity = 10.0f; // world units / s^2
+ float air_drag = 0.8f; // linear velocity drag coefficient
+
+ // --- construction -----------------------------------------------------
+ int add_node(float x, float y, bool foundation = false);
+ int add_edge(int na, int nb, int mat);
+
+ // Place a strut between two nodes, subdividing into segments no longer than
+ // the material's max_length (intermediate nodes auto-created). Rejects links
+ // outside [min_length, max_link_length]. Returns segments created (0 = none).
+ int add_link(int na, int nb, int mat);
+
+ void extrude_edge(int edge_id, float off_x, float off_y);
+
+ // --- simulation (call inside the fixed timestep) ----------------------
+ void step(float dt); // integrate the mass-spring system (oversampled)
+ int check_strain(); // snap struts past axial / angle limits
+ void update_timers(float dt); // build-grace countdown
+ int kill_grounded(); // destroy debris below the ground
+
+ // --- weapon damage ----------------------------------------------------
+ // Radial splash at (x,y): damages struts (HP, linear falloff), applies
+ // knockback to nodes, and breaks struts whose HP hits zero. Structure does
+ // not block splash (pure radius falloff, like Forts AoE).
+ void apply_splash(float x, float y, float radius, float damage, float knockback);
+
+ // Trace a laser beam from (ox,oy) along (dx,dy) up to `range`: damage +
+ // optionally ignite EVERY strut it crosses, passing through beam-transparent
+ // materials (bg-brace, ropes) and STOPPING at the first blocking one (wood).
+ // Fills the stop point and returns the beam length.
+ float beam_fire(float ox, float oy, float dx, float dy, float range,
+ float damage, bool ignite, float& hit_x, float& hit_y);
+
+ // Nearest edge to (x,y) within radius that blocks projectiles (what a
+ // cannon shell detonates on). Returns edge index or -1.
+ int find_blocking_edge(float x, float y, float radius) const;
+
+ // --- fire -------------------------------------------------------------
+ void ignite_edge(int edge_id); // set a strut on fire (if flammable)
+ void ignite_area(float x, float y, float radius); // ignite flammable struts in radius
+ void update_fire(float dt); // burn DoT + spread + destroy at 0 HP
+
+ // Recompute adjacency/faces/masses (call after directly assigning nodes/edges,
+ // e.g. restoring a scenario snapshot).
+ void rebuild() { rebuild_topology(); }
+
+ // --- destruction primitives -------------------------------------------
+ void break_edge(int edge_id);
+ void break_node(int node_id);
+
+ // --- queries ----------------------------------------------------------
+ int find_nearest_node(float x, float y, float radius = 0.5f) const;
+ int find_nearest_edge(float x, float y, float radius = 0.5f) const;
+ bool is_on_ground(float y) const { return y <= ground_level + 0.3f; }
+ BuildEdge* mutable_edge(int id);
+
+ // --- data -------------------------------------------------------------
+ std::vector<BuildNode> nodes;
+ std::vector<BuildEdge> edges;
+ std::vector<BuildFace> faces;
+
+ struct Destroyed { float x, y; };
+ std::vector<Destroyed> destroyed_events; // drained by the app for FX
+
+private:
+ std::vector<std::vector<std::pair<int,int>>> adj_; // node -> (neighbour, edge)
+ std::unordered_set<uint64_t> edge_set_;
+ std::vector<float> fx_, fy_; // per-node force scratch
+ std::vector<char> held_; // per-node build-grace freeze
+
+ void rebuild_topology(); // adj_, edge_set_, faces, triangulated, node mass
+ bool edge_exists(int a, int b) const;
+};