diff options
Diffstat (limited to 'src/game/build_graph.hpp')
| -rw-r--r-- | src/game/build_graph.hpp | 151 |
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; +}; |
