Nimbin[12]?SDL & Graphics / sdl_runtime_compiler / src/levels/director.cpp

sdl_runtime_compiler git · main

SDL3 game for running and compiling code at runtime

sdl3 c++ compiler dlopen cmake · first commit 2026-04-19 · last commit 2026-07-03 (3 months ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-runtime-compiler

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.gz
src/levels/director.cpp 7.5 KB · 251 lines raw

#include <algorithm>
#include <unordered_set>

#include "entities/camera.hpp"
#include "entities/plate.hpp"
#include "entities/solid_box.hpp"
#include "entities/world.hpp"
#include "levels/director.hpp"
#include "levels/level.hpp"
#include "states/game_context.hpp"
#include "systems/celebration_system.hpp"
#include "systems/particle_system.hpp"
#include "utils/config.hpp"
#include "text/text.hpp"
#include "wall/wall.hpp"

namespace
Nimbin
{

namespace
Level
{

Director::Director(const Level::Data& level)
    : _runner(cpputils::make_unique<Level::Progression>(level))
{}


int
Director::get_MaxAiForStep() const
{
    if (_runner->is_Complete()) return 0;

    return _runner->get_Current().max_ai_words;
}


void
Director::enter_Step(GameContext& ctx)
{
    spawn_Grants(ctx);
    setup_Scene (ctx, _runner->get_Current().scene);
    show_Details(ctx);
    _step_age = 0.0;
}


void
Director::spawn_Grants(GameContext& ctx)
{
    DynArray<std::string> tokens = _runner->get_GarantsForStep();

    static constexpr float spacing = 3.0f;
    static constexpr int   per_row = 8;
    static constexpr float row_gap = 3.0f;

    const double hover_y = World::get_FloorY<double>() + 1.5;

    for (size_t i = 0; i < tokens.size(); i++) {
        size_t col = i % per_row;
        size_t row = i / per_row;

        Vec3 pos {
            -static_cast<float>(per_row) * spacing * 0.5f + static_cast<float>(col) * spacing,
            hover_y,
            static_cast<float>(row) * row_gap
        };

        Text::TokenPtr obj = Text::make_Token(tokens[i], Text::Token::depth_floor, 0.003f, pos);
        obj->body.hover_height = hover_y;
        ctx.words.push_back(std::move(obj));
    }
}


void
Director::setup_Scene(GameContext& ctx, const Scene::Data& scene)
{
    for (const Scene::Placed::Word& pw : scene.words) {
        Text::TokenPtr t = Text::make_Token(pw.text, Text::Token::depth_floor, 0.003f, pw.pos, pw.color);
        t->body.hover_height = pw.pos.y;
        ctx.words.push_back(std::move(t));
    }

    for (const Scene::Placed::Plate& pp : scene.plates) {
        auto plate = cpputils::make_unique<Plate>(pp.pos);
        Plate* raw = plate.get();
        _step_plates .push_back({ raw, pp.tag });
        _step_scenery.push_back(raw);
        ctx.scenery  .push_back(std::move(plate));
    }

    for (const Scene::Placed::Platform& pl : scene.platforms) {
        auto box = cpputils::make_unique<SolidBox>(pl.pos, pl.half_extents,
                                                   pl.fill_color, pl.wire_color);
        GameObject* raw = box.get();
        _step_scenery.push_back(raw);
        ctx.scenery  .push_back(std::move(box));
    }

    for (const Scene::Placed::Ai& a : scene.ai_seeds) {
        Text::TokenPtr t = Text::make_Token(a.text, Text::Token::depth_floor, 0.003f, a.pos, Config::Color::floor);
        t->body.hover_height = a.pos.y;
        t->ai.aggressiveness = a.aggressiveness;

        switch (a.initial_state) {
            case Text::Token::Ai::State::Attack:
                t->ai.state  = Text::Token::Ai::State::Attack;
                t->body.mode = PhysicsMode::AiHover;
                t->set_Color      (Config::Color::attack);
                t->set_ColorSource(Text::ColorSource::Static);
                t->ai.state_timer = 0.0;
                t->ai.phase_timer = 0.5;
                break;
            case Text::Token::Ai::State::Flee:
                t->ai.state  = Text::Token::Ai::State::Flee;
                t->body.mode = PhysicsMode::AiHover;
                t->set_Color      (Config::Color::flee);
                t->set_ColorSource(Text::ColorSource::Static);
                break;
            case Text::Token::Ai::State::Idle:
            case Text::Token::Ai::State::Stunned:
                break;
        }

        ctx.words.push_back(std::move(t));
    }
}


void
Director::teardown_Scene(GameContext& ctx)
{
    if (!_step_scenery.empty()) {
        std::unordered_set<GameObject*> scenery_set(_step_scenery.begin(), _step_scenery.end());
        ctx.scenery.erase(
            std::remove_if(ctx.scenery.begin(), ctx.scenery.end(),
                [&scenery_set](const GameObjectPtr& sp) {
                    return scenery_set.count(sp.get()) > 0;
                }),
            ctx.scenery.end());
    }
    _step_scenery.clear();
    _step_plates .clear();
}


void Director::show_Details(GameContext& ctx) const
{
    if (!ctx.info_wall) return;
    Text::Handler& info = *ctx.info_wall;

    if (_runner->is_Complete()) { info.clear(); _detail_built_for = nullptr; return; }

    const Level::Step* step = &_runner->get_Current();

    if (_detail_built_for != step) {
        _detail_tokens.clear();
        const std::string& details = step->details;
        DynArray<std::string> lines = details.empty()
                                    ? DynArray<std::string>{}
                                    : Text::wrap_String(details, 92);

        float y = World::get_FloorY<float>() + 1.0f;
        const Wall::Transform tf = Wall::get_Transform(Wall::Role::Info, World::get_BoxScale());
        for (const std::string& line : lines) {
            if (line.empty()) { y += Config::Wall::text_line_spacing; continue; }
            Text::TokenPtr t = Text::make_Token(line, Text::Token::depth_wall, 0.003f,
                                                tf.position, Config::Color::white);
            t->yaw = tf.yaw;
            t->pos = tf.position;
            t->pos.y = y;
            t->set_ColorSource(Text::ColorSource::Static);
            t->set_Color(Config::Color::white);
            _detail_tokens.push_back(std::move(t));
            y += Config::Wall::text_line_spacing;
        }
        _detail_built_for = step;
    }

    info.clear();
    info.set_Borrowed(&_detail_tokens);
}


Goal::Context
Director::build_GoalCtx(const GameContext& ctx) const
{
    Goal::Context gctx;

    for (const StepPlate& sp : _step_plates)
        if (sp.plate && sp.plate->active)
            gctx.active_plate_tags.push_back(sp.tag);

    gctx.buttons_pressed_this_step   = _ledger.buttons_pressed;
    gctx.dropped_this_step           = _ledger.dropped;
    gctx.shot_this_step              = _ledger.shot;
    gctx.compile_ok_this_step        = _ledger.compile_ok;
    gctx.ai_words_defeated_this_step = _ledger.ai_defeated;
    gctx.picked_up_this_step         = _ledger.picked_up;

    if (gctx.compile_ok_this_step && ctx.editor_wall) {
        std::string src;
        for (const std::string& l : ctx.editor_wall->as_Lines())
            { src += l; src += '\n'; }

        gctx.editor_normalized = Level::Check::normalize(src);
    }
    return gctx;
}


Director::AdvanceResult
Director::try_Advance(GameContext& ctx, double dt,
                      CelebrationSystem& celebration, ParticleSystem& particles)
{
    _step_age += dt;
    AdvanceResult res;

    if (celebration.is_Active()) {
        if (celebration.get_Progress() >= 1.0) {
            celebration.finish(particles);

            teardown_Scene(ctx);
            _ledger.clear();

            res.stepped     = true;
            res.advanced    = _runner->advance();
            res.reset_carry = res.advanced && _runner->get_Current().reset_carry;
            _step_age       = 0.0;
        }
        return res;
    }

    if (_runner->is_Complete()) return res;

    Goal::Context gctx = build_GoalCtx(ctx);
    if (!Goal::is_Satisfied(_runner->get_Current().goal, gctx)) return res;
    if (_step_age < Config::Level::settle_time_for_step)  return res;

    double glyph_yaw = SDL_atan2(-ctx.camera->fwd.x, -ctx.camera->fwd.z);
    celebration.start(*ctx.camera, glyph_yaw);
    return res;
}


} // namespace Level

} // namespace Nimbin