sdl_runtime_compiler git · main
SDL3 game for running and compiling code at runtime
C++ 72.3% C 26.2%git clone https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler.gitwget https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler/archive/sdl_runtime_compiler.tar.gzRefractor Text3d Ai in own struct
src/levels/level_list.cpp | 2 +- src/levels/scene.hpp | 2 +- src/states/game_state.cpp | 26 ++++---- src/systems/pick_system.cpp | 2 +- src/systems/word_ai_system.cpp | 142 ++++++++++++++++++++--------------------- src/text_3d/text_3d.cpp | 8 +-- src/text_3d/text_3d.hpp | 46 +++++++------ 7 files changed, 117 insertions(+), 111 deletions(-) diff --git a/src/levels/level_list.cpp b/src/levels/level_list.cpp index 6d5a3e7..e8b631a 100644 --- a/src/levels/level_list.cpp +++ b/src/levels/level_list.cpp @@ -179,7 +179,7 @@ Aim with the crosshair and LEFT CLICK.)", { .text = "danger", .pos = { -6.0, static_cast<double>(-WORLD_BOX_SCALE + 2.0f), -6.0 }, - .initial_state = Text3d::Ai::Attack, + .initial_state = Text3dAI::State::Attack, .aggressiveness = 1.0, }, }, diff --git a/src/levels/scene.hpp b/src/levels/scene.hpp index 954f60d..cfb659a 100644 --- a/src/levels/scene.hpp +++ b/src/levels/scene.hpp @@ -42,7 +42,7 @@ struct PlacedPlatform { struct PlacedAi { std::string text; Vec3 pos; - Text3d::Ai initial_state = Text3d::Ai::Attack; + Text3dAI::State initial_state = Text3dAI::State::Attack; double aggressiveness = 1.0; }; diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp index 82d9257..237a6d3 100644 --- a/src/states/game_state.cpp +++ b/src/states/game_state.cpp @@ -675,10 +675,10 @@ GameState::render(SDL_Window& window, GlRenderer& renderer) if (d.word) d.word->draw(renderer, *_ctx.camera, *_ctx.light); } - { FP_ZONE("render.ai_stolen"); + { FP_ZONE("render.ai.stolen"); for (const Text3dPtr& t : _ctx.words) { if (!t) continue; - for (const Text3dPtr& s : t->ai_stolen) + for (const Text3dPtr& s : t->ai.stolen) if (s) s->draw(renderer, *_ctx.camera, *_ctx.light); } } @@ -791,23 +791,23 @@ GameState::scene_Setup(const Scene& scene) Text3dPtr t = make_Text3d(a.text, Text3d::depth_floor, 0.003f, a.pos, Text3d::color_floor); t->body.hover_height = a.pos.y; - t->ai_aggressiveness = a.aggressiveness; + t->ai.aggressiveness = a.aggressiveness; switch (a.initial_state) { - case Text3d::Ai::Attack: - t->ai_state = Text3d::Ai::Attack; + case Text3dAI::State::Attack: + t->ai.state = Text3dAI::State::Attack; t->body.mode = PhysicsMode::AiHover; t->set_Color(Text3d::color_attack); - t->ai_state_timer = 0.0; - t->ai_phase_timer = 0.5; + t->ai.state_timer = 0.0; + t->ai.phase_timer = 0.5; break; - case Text3d::Ai::Flee: - t->ai_state = Text3d::Ai::Flee; + case Text3dAI::State::Flee: + t->ai.state = Text3dAI::State::Flee; t->body.mode = PhysicsMode::AiHover; t->set_Color(Text3d::color_flee); break; - case Text3d::Ai::Idle: - case Text3d::Ai::Stunned: + case Text3dAI::State::Idle: + case Text3dAI::State::Stunned: break; } @@ -937,8 +937,8 @@ GameState::substep(double dt) Text3d* word = static_cast<Text3d*>(ev.other); _step_ledger.ai_defeated.push_back(word->text); - word->ai_state = Text3d::Ai::Stunned; - word->ai_state_timer = 0.0; + word->ai.state = Text3dAI::State::Stunned; + word->ai.state_timer = 0.0; word->body.vel = Vec3{0, 0, 0}; word->set_Color(Text3d::color_stunned); } diff --git a/src/systems/pick_system.cpp b/src/systems/pick_system.cpp index ced445f..6f6b43a 100644 --- a/src/systems/pick_system.cpp +++ b/src/systems/pick_system.cpp @@ -80,7 +80,7 @@ pick(const Camera& camera, for (size_t i = 0; i < words.size(); ++i) { const Text3dPtr& w = words[i]; if (!w || !w->interactable) continue; - if (w->ai_state != Text3d::Ai::Idle) continue; + if (w->ai.state != Text3dAI::State::Idle) continue; double a = ang_score(*w, eye, camera.fwd); if (a >= 0.0 && a < best_angle) { diff --git a/src/systems/word_ai_system.cpp b/src/systems/word_ai_system.cpp index c141d4b..b2e7790 100644 --- a/src/systems/word_ai_system.cpp +++ b/src/systems/word_ai_system.cpp @@ -39,7 +39,7 @@ WordAiSystem::update(DynArray<Text3dPtr>& words, int active_ai_count = 0; for (const Text3dPtr& w : words) { if (!w) continue; - if (w->ai_state != Text3d::Ai::Idle) ++active_ai_count; + if (w->ai.state != Text3dAI::State::Idle) ++active_ai_count; } // Index-based loop so reallocation doesn't invalidate. @@ -49,31 +49,31 @@ WordAiSystem::update(DynArray<Text3dPtr>& words, Text3dPtr& w = words[i]; if (!w) continue; - if (w->ai_state != Text3d::Ai::Idle && + if (w->ai.state != Text3dAI::State::Idle && w->body.mode != PhysicsMode::AiHover && w->body.mode != PhysicsMode::Hover) { - for (Text3dPtr& s : w->ai_stolen) { + for (Text3dPtr& s : w->ai.stolen) { if (!s) continue; s->body.mode = PhysicsMode::Dynamic; s->body.vel = w->body.vel * 0.5; s->set_Color(s->token_info ? TokenColor::get(s->token_info->type) : Text3d::color_floor); released.push_back(std::move(s)); // deferred — not words.push_back } - w->ai_stolen.clear(); - w->ai_state = Text3d::Ai::Idle; + w->ai.stolen.clear(); + w->ai.state = Text3dAI::State::Idle; w->set_Color(w->token_info ? TokenColor::get(w->token_info->type) : Text3d::color_floor); continue; } - w->ai_state_timer += dt; + w->ai.state_timer += dt; - switch (w->ai_state) { - case Text3d::Ai::Idle: update_Idle(*w, player, active_ai_count, dt); break; - case Text3d::Ai::Attack: update_Attack(*w, player, carried, words, dropped, released, dt); + switch (w->ai.state) { + case Text3dAI::State::Idle: update_Idle(*w, player, active_ai_count, dt); break; + case Text3dAI::State::Attack: update_Attack(*w, player, carried, words, dropped, released, dt); break; - case Text3d::Ai::Flee: update_Flee(*w, player, released, dt); break; - case Text3d::Ai::Stunned: update_Stunned(*w, released, dt); break; + case Text3dAI::State::Flee: update_Flee(*w, player, released, dt); break; + case Text3dAI::State::Stunned: update_Stunned(*w, released, dt); break; } update_StolenTrail(*w, dt); @@ -100,7 +100,7 @@ WordAiSystem::update_Idle(Text3d& w, const Player& p, int active_ai_count, doubl if (dist_sq > TRIGGER_RANGE * TRIGGER_RANGE) return; - double roll_threshold = IDLE_AGGRO_ROLL * w.ai_aggressiveness * dt; + double roll_threshold = IDLE_AGGRO_ROLL * w.ai.aggressiveness * dt; if (random_unit() > roll_threshold) return; if (random_unit() < FLEE_BIAS) enter_Flee(w); @@ -124,12 +124,12 @@ void WordAiSystem::update_Attack(Text3d& w, const Player& p, CarriedLine& carrie constexpr double ORBIT_SPEED = 0.6; constexpr double ORBIT_HEIGHT = 0.5; - w.ai_orbit_angle += w.ai_orbit_dir * ORBIT_SPEED * dt; + w.ai.orbit_angle += w.ai.orbit_dir * ORBIT_SPEED * dt; target = { - p.pos.x + ORBIT_RADIUS * SDL_cos(w.ai_orbit_angle), + p.pos.x + ORBIT_RADIUS * SDL_cos(w.ai.orbit_angle), p.pos.y + ORBIT_HEIGHT, - p.pos.z + ORBIT_RADIUS * SDL_sin(w.ai_orbit_angle) + p.pos.z + ORBIT_RADIUS * SDL_sin(w.ai.orbit_angle) }; } @@ -137,23 +137,23 @@ void WordAiSystem::update_Attack(Text3d& w, const Player& p, CarriedLine& carrie double dist = length(to_target); // Pause/dash phase machine (unchanged) - w.ai_phase_timer -= dt; - if (w.ai_phase_timer <= 0.0) { - w.ai_in_pause = !w.ai_in_pause; - w.ai_phase_timer = w.ai_in_pause + w.ai.phase_timer -= dt; + if (w.ai.phase_timer <= 0.0) { + w.ai.in_pause = !w.ai.in_pause; + w.ai.phase_timer = w.ai.in_pause ? (0.4 + random_unit() * 0.8) : (1.0 + random_unit() * 1.5); } Vec3 target_vel{}; - if (!w.ai_in_pause && dist > 0.01) { + if (!w.ai.in_pause && dist > 0.01) { double inv = 1.0 / dist; Vec3 dir = { to_target.x * inv, to_target.y * inv, to_target.z * inv }; - target_vel = dir * w.ai_speed; + target_vel = dir * w.ai.speed; - w.ai_wiggle_phase += w.ai_wiggle_freq * dt; + w.ai.wiggle_phase += w.ai.wiggle_freq * dt; Vec3 perp = perpendicular_horizontal(dir); - double wig = SDL_sin(w.ai_wiggle_phase) * WIGGLE_AMPLITUDE; + double wig = SDL_sin(w.ai.wiggle_phase) * WIGGLE_AMPLITUDE; if (!has_carry) wig *= 0.4; // gentler wobble while orbiting target_vel.x += perp.x * wig; target_vel.z += perp.z * wig; @@ -163,7 +163,7 @@ void WordAiSystem::update_Attack(Text3d& w, const Player& p, CarriedLine& carrie // ── Contact: attacker touches carried word? ────────────────────── if (!carried.empty()) { - if (w.ai_has_stolen) { + if (w.ai.has_stolen) { // Already stole this session — touching carry does nothing. } else { DynArray<CarriedWordNode*> nodes = carried.reverse_nodes(); @@ -175,7 +175,7 @@ void WordAiSystem::update_Attack(Text3d& w, const Player& p, CarriedLine& carrie Text3dPtr stolen = std::move(node->word); stolen->body.mode = PhysicsMode::AiHover; stolen->set_Color(Text3d::color_attack); - w.ai_stolen.push_back(std::move(stolen)); + w.ai.stolen.push_back(std::move(stolen)); enter_Flee(w); // run away with the loot carried.compact(); break; @@ -198,7 +198,7 @@ void WordAiSystem::update_Attack(Text3d& w, const Player& p, CarriedLine& carrie for (Text3dPtr& other : all_words) { if (!other) continue; if (other.get() == &w) continue; - if (other->ai_state != Text3d::Ai::Idle) continue; + if (other->ai.state != Text3dAI::State::Idle) continue; if (other->body.mode != PhysicsMode::Hover) continue; if (!Collision::aabb_Overlap(w, *other)) continue; @@ -230,23 +230,23 @@ WordAiSystem::update_Flee(Text3d& w, const Player& p, DynArray<Text3dPtr>& relea double dist = length(away); // Pause/dash similar to attack - w.ai_phase_timer -= dt; - if (w.ai_phase_timer <= 0.0) { - w.ai_in_pause = !w.ai_in_pause; - w.ai_phase_timer = w.ai_in_pause + w.ai.phase_timer -= dt; + if (w.ai.phase_timer <= 0.0) { + w.ai.in_pause = !w.ai.in_pause; + w.ai.phase_timer = w.ai.in_pause ? (0.3 + random_unit() * 0.5) : (1.5 + random_unit() * 1.5); } Vec3 target_vel{}; - if (!w.ai_in_pause && dist > 0.01) { + if (!w.ai.in_pause && dist > 0.01) { double inv = 1.0 / dist; Vec3 dir = { away.x * inv, 0.0, away.z * inv }; // horizontal flee - target_vel = dir * w.ai_speed * 1.2; + target_vel = dir * w.ai.speed * 1.2; - w.ai_wiggle_phase += w.ai_wiggle_freq * dt; + w.ai.wiggle_phase += w.ai.wiggle_freq * dt; Vec3 perp = perpendicular_horizontal(dir); - double wig = SDL_sin(w.ai_wiggle_phase) * WIGGLE_AMPLITUDE * 0.6; + double wig = SDL_sin(w.ai.wiggle_phase) * WIGGLE_AMPLITUDE * 0.6; target_vel.x += perp.x * wig; target_vel.z += perp.z * wig; } @@ -255,9 +255,9 @@ WordAiSystem::update_Flee(Text3d& w, const Player& p, DynArray<Text3dPtr>& relea // Escape condition: far enough, for long enough. if (dist > FLEE_ESCAPE_DIST) { - if (w.ai_state_timer > FLEE_ESCAPE_TIME) { + if (w.ai.state_timer > FLEE_ESCAPE_TIME) { // Got away. If carrying loot, roll for drop-vs-keep. - if (!w.ai_stolen.empty()) { + if (!w.ai.stolen.empty()) { if (random_unit() < AI_DROP_LOOT_PROBABILITY) { release_StolenWords(w, released); } @@ -277,7 +277,7 @@ WordAiSystem::update_Stunned(Text3d& w, DynArray<Text3dPtr>& released, double dt { apply_Locomotion(w, Vec3{}, dt); - if (w.ai_state_timer > STUNNED_DURATION) { + if (w.ai.state_timer > STUNNED_DURATION) { release_StolenWords(w, released); enter_Idle(w); } @@ -286,43 +286,43 @@ WordAiSystem::update_Stunned(Text3d& w, DynArray<Text3dPtr>& released, double dt void WordAiSystem::release_StolenWords(Text3d& w, DynArray<Text3dPtr>& out) { - for (Text3dPtr& s : w.ai_stolen) { + for (Text3dPtr& s : w.ai.stolen) { if (!s) continue; s->body.mode = PhysicsMode::Dynamic; s->body.vel = { w.body.vel.x * 0.5, 2.0, w.body.vel.z * 0.5 }; s->set_Color(s->token_info ? TokenColor::get(s->token_info->type) : Text3d::color_floor); out.push_back(std::move(s)); } - w.ai_stolen.clear(); + w.ai.stolen.clear(); } void WordAiSystem::enter_Attack(Text3d& w) { - w.ai_state = Text3d::Ai::Attack; - w.ai_state_timer = 0.0; - w.ai_phase_timer = 0.5 + random_unit() * 1.5; - w.ai_in_pause = false; - w.ai_has_stolen = false; - w.ai_hover_height_saved = w.body.hover_height; + w.ai.state = Text3dAI::State::Attack; + w.ai.state_timer = 0.0; + w.ai.phase_timer = 0.5 + random_unit() * 1.5; + w.ai.in_pause = false; + w.ai.has_stolen = false; + w.ai.hover_height_saved = w.body.hover_height; w.body.mode = PhysicsMode::AiHover; w.set_Color(Text3d::color_attack); // Pick an orbit direction and starting angle for the no-carry wander. - w.ai_orbit_angle = random_unit() * SDL_PI_D * 2.0; - w.ai_orbit_dir = (random_unit() < 0.5) ? -1.0 : 1.0; + w.ai.orbit_angle = random_unit() * SDL_PI_D * 2.0; + w.ai.orbit_dir = (random_unit() < 0.5) ? -1.0 : 1.0; } void WordAiSystem::enter_Flee(Text3d& w) { - w.ai_state = Text3d::Ai::Flee; - w.ai_state_timer = 0.0; - w.ai_phase_timer = 0.3 + random_unit() * 0.7; - w.ai_in_pause = false; - w.ai_hover_height_saved = w.body.hover_height; + w.ai.state = Text3dAI::State::Flee; + w.ai.state_timer = 0.0; + w.ai.phase_timer = 0.3 + random_unit() * 0.7; + w.ai.in_pause = false; + w.ai.hover_height_saved = w.body.hover_height; w.body.mode = PhysicsMode::AiHover; w.set_Color(Text3d::color_flee); } @@ -331,8 +331,8 @@ WordAiSystem::enter_Flee(Text3d& w) void WordAiSystem::enter_Stunned(Text3d& w) { - w.ai_state = Text3d::Ai::Stunned; - w.ai_state_timer = 0.0; + w.ai.state = Text3dAI::State::Stunned; + w.ai.state_timer = 0.0; w.body.vel = Vec3{}; w.set_Color(Text3d::color_stunned); } @@ -341,10 +341,10 @@ WordAiSystem::enter_Stunned(Text3d& w) void WordAiSystem::enter_Idle(Text3d& w) { - w.ai_state = Text3d::Ai::Idle; - w.ai_state_timer = 0.0; + w.ai.state = Text3dAI::State::Idle; + w.ai.state_timer = 0.0; w.body.mode = PhysicsMode::Hover; - w.body.hover_height = w.ai_hover_height_saved; + w.body.hover_height = w.ai.hover_height_saved; w.set_Color(w.token_info ? TokenColor::get(w.token_info->type) : Text3d::color_floor); } @@ -379,7 +379,7 @@ WordAiSystem::apply_Locomotion(Text3d& w, const Vec3& target_vel, double dt) void WordAiSystem::update_StolenTrail(Text3d& w, double dt) { - if (w.ai_stolen.empty()) return; + if (w.ai.stolen.empty()) return; // ── Smoothed back direction ───────────────────────────────────── double speed = SDL_sqrt(w.body.vel.x * w.body.vel.x + @@ -388,28 +388,28 @@ WordAiSystem::update_StolenTrail(Text3d& w, double dt) if (speed > 0.5) { Vec3 raw_back = { -w.body.vel.x / speed, 0.0, -w.body.vel.z / speed }; double k = 1.0 - SDL_exp(-3.0 * dt); - w.ai_trail_dir.x += (raw_back.x - w.ai_trail_dir.x) * k; - w.ai_trail_dir.z += (raw_back.z - w.ai_trail_dir.z) * k; + w.ai.trail_dir.x += (raw_back.x - w.ai.trail_dir.x) * k; + w.ai.trail_dir.z += (raw_back.z - w.ai.trail_dir.z) * k; - double len = SDL_sqrt(w.ai_trail_dir.x * w.ai_trail_dir.x + - w.ai_trail_dir.z * w.ai_trail_dir.z); + double len = SDL_sqrt(w.ai.trail_dir.x * w.ai.trail_dir.x + + w.ai.trail_dir.z * w.ai.trail_dir.z); if (len > 0.01) { - w.ai_trail_dir.x /= len; - w.ai_trail_dir.z /= len; + w.ai.trail_dir.x /= len; + w.ai.trail_dir.z /= len; } } - if (SDL_fabs(w.ai_trail_dir.x) < 0.01 && SDL_fabs(w.ai_trail_dir.z) < 0.01) - w.ai_trail_dir = { 1.0, 0.0, 0.0 }; + if (SDL_fabs(w.ai.trail_dir.x) < 0.01 && SDL_fabs(w.ai.trail_dir.z) < 0.01) + w.ai.trail_dir = { 1.0, 0.0, 0.0 }; // ── Lerp stolen words toward their slot ───────────────────────── double offset = static_cast<double>(w.half_extent); double pos_lerp_k = 1.0 - SDL_exp(-8.0 * dt); - Vec3 perp = perpendicular_horizontal(w.ai_trail_dir); - double target_yaw = SDL_atan2(-w.ai_trail_dir.x, -w.ai_trail_dir.z); + Vec3 perp = perpendicular_horizontal(w.ai.trail_dir); + double target_yaw = SDL_atan2(-w.ai.trail_dir.x, -w.ai.trail_dir.z); - for (Text3dPtr& s : w.ai_stolen) { + for (Text3dPtr& s : w.ai.stolen) { if (!s) continue; offset += static_cast<double>(s->half_extent) + 0.18; @@ -419,9 +419,9 @@ WordAiSystem::update_StolenTrail(Text3d& w, double dt) double sway = SDL_sin(sway_t) * 0.12; Vec3 target { - w.pos.x + w.ai_trail_dir.x * offset + perp.x * sway, + w.pos.x + w.ai.trail_dir.x * offset + perp.x * sway, w.pos.y + SDL_sin(bob_t) * 0.08, - w.pos.z + w.ai_trail_dir.z * offset + perp.z * sway + w.pos.z + w.ai.trail_dir.z * offset + perp.z * sway }; s->pos.x += (target.x - s->pos.x) * pos_lerp_k; diff --git a/src/text_3d/text_3d.cpp b/src/text_3d/text_3d.cpp index 86a8157..5b24441 100644 --- a/src/text_3d/text_3d.cpp +++ b/src/text_3d/text_3d.cpp @@ -29,10 +29,10 @@ Text3d::Text3d(const std::string& txt, // AI personality - ai_aggressiveness = (rand() % 1000) / 1000.0; - ai_wiggle_phase = (rand() % 1000) / 1000.0 * SDL_PI_D * 2.0; - ai_wiggle_freq = 3.0 + (rand() % 1000) / 1000.0 * 4.0; // 3–7 rad/s - ai_speed = 2.5 + (rand() % 1000) / 1000.0 * 2.5; + ai.aggressiveness = (rand() % 1000) / 1000.0; + ai.wiggle_phase = (rand() % 1000) / 1000.0 * SDL_PI_D * 2.0; + ai.wiggle_freq = 3.0 + (rand() % 1000) / 1000.0 * 4.0; // 3–7 rad/s + ai.speed = 2.5 + (rand() % 1000) / 1000.0 * 2.5; } diff --git a/src/text_3d/text_3d.hpp b/src/text_3d/text_3d.hpp index 671075c..a08a54a 100644 --- a/src/text_3d/text_3d.hpp +++ b/src/text_3d/text_3d.hpp @@ -14,32 +14,38 @@ class Camera; class GlRenderer; -struct Text3d final : public GameObject { - static inline const std::string font_path = - "/usr/share/fonts/dejavu/DejaVuSansMono.ttf"; - - enum class Ai : uint8_t { +struct Text3dAI +{ + enum class State : uint8_t { Idle, Attack, Flee, Stunned, }; - Ai ai_state = Ai::Idle; - double ai_state_timer = 0.0; // time in current state - double ai_aggressiveness = 0.0; // 0..1, personal trait set at spawn - double ai_phase_timer = 0.0; // pause/dash phase timer - double ai_wiggle_phase = 0.0; // random init at spawn - double ai_wiggle_freq = 4.0; // rad/s - double ai_speed = 2.0; // base movement speed - bool ai_in_pause = false; - double ai_hover_height_saved = 0.0; // restore on exit - double ai_orbit_angle = 0.0; // current angle around player (radians) - double ai_orbit_dir = 1.0; // +1 = clockwise, -1 = counter-clockwise - bool ai_has_stolen = false; - DynArray<Text3dPtr> - ai_stolen; - Vec3 ai_trail_dir { 1.0, 0.0, 0.0 }; + State state = State::Idle; + double state_timer = 0.0; + double aggressiveness = 0.0; + double phase_timer = 0.0; + double wiggle_phase = 0.0; + double wiggle_freq = 4.0; + double speed = 2.0; + + bool in_pause = false; + double hover_height_saved = 0.0; + double orbit_angle = 0.0; + double orbit_dir = 1.0; + bool has_stolen = false; + + DynArray<Text3dPtr> stolen; + Vec3 trail_dir {1.0, 0.0, 0.0}; +}; + +struct Text3d final : public GameObject { + static inline const std::string font_path = + "/usr/share/fonts/dejavu/DejaVuSansMono.ttf"; + + Text3dAI ai; float glow_strength { 0.2f }; bool glow_enabled { false };