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.gzStart implementing Connect pult With Claude
src/states/game_state.cpp | 602 +++++++++++++++------ src/states/game_state.hpp | 45 +- ...mod_loader_system.cpp => dyn_loader_system.cpp} | 49 +- ...mod_loader_system.hpp => dyn_loader_system.hpp} | 12 +- src/systems/game_mod_abi.h | 12 +- src/systems/input_system.cpp | 41 +- src/systems/input_system.hpp | 13 +- src/systems/physics_system.cpp | 17 +- src/systems/physics_system.hpp | 6 +- src/systems/projectile_system.cpp | 5 +- src/systems/projectile_system.hpp | 3 +- src/systems/pult_system.cpp | 10 + src/systems/pult_system.hpp | 2 + src/systems/word_anim.cpp | 5 +- src/systems/word_anim.hpp | 2 +- src/text_3d/vim_buffer.hpp | 174 ++++++ src/utils/ascii.hpp | 2 +- src/wall/text_wall.cpp | 22 +- src/wall/text_wall.hpp | 5 +- 19 files changed, 762 insertions(+), 265 deletions(-) diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp index fe7fd9d..0bb6d94 100644 --- a/src/states/game_state.cpp +++ b/src/states/game_state.cpp @@ -41,7 +41,7 @@ GameState::GameState(size_t starting_level) _word_anim_sys = cpputils::make_unique<WordAnimSystem> (); _freelook_sys = cpputils::make_unique<FreelookSystem> (); _particle_sys = cpputils::make_unique<ParticleSystem> (); - _mod_loader_sys = cpputils::make_unique<ModLoader> (); + _dyn_loader_sys = cpputils::make_unique<DynLoaderSystem> (); _celebration_sys = cpputils::make_unique<CelebrationSystem>(); _floor_sys = cpputils::make_unique<FloorMandelbrot> (); @@ -93,20 +93,11 @@ void GameState::debug_SpawnWords([[maybe_unused]]size_t n) { DynArray<std::string> tokens = TokenDB::get_TestArrayAllKeywords(); - /* - DynArray<std::string> tokens; - tokens.reserve(n); - static const char* samples[] = { - "int", "for", "while", "return", "void", "char", - "if", "else", "(", ")", "{", "}", "==", "++" - }; - for (size_t i = 0; i < n; ++i) - tokens.push_back(samples[i % (sizeof(samples)/sizeof(samples[0]))]); - */ step_Spawn(tokens); } + std::tuple<Vec3, Vec3, double> GameState::anchorframe_Current() const { @@ -181,6 +172,12 @@ GameState::pult_DispatchAction(PultAction act) const double floor_y = World::get_FloorY<double>(); switch (act) { + case PultAction::Compile: + on_Compile(); + break; + case PultAction::RunProgram: + on_Run(); + break; case PultAction::ScrollEditorUp: TextWallScroll::scroll_Up(_ctx.wall_texts, Wall::Role::Editor); _editor_wall.scroll(ConfigWall::scroll_step); @@ -193,14 +190,8 @@ GameState::pult_DispatchAction(PultAction act) TextWallScroll::clear_ToFloor(_ctx.wall_texts, _ctx.words, Wall::Role::Editor, floor_y); _editor_wall.clear(); break; - case PultAction::Compile: - on_Compile(); - break; - case PultAction::RunProgram: - on_Run(); - break; + case PultAction::ConnectEditor: connect_BeginEditor(); break; case PultAction::ConnectProgram: connect_Begin(Conn::Program); break; - case PultAction::ConnectEditor: break; } } @@ -223,7 +214,6 @@ GameState::on_Pick() PickResult r = PickSystem::pick(*_ctx.camera, _ctx.words, _ctx.scenery, _ctx.wall_texts); if (!r.hit()) return false; - // ── Wall text: pluck off the wall, animate into carry ─────────── if (r.kind == PickKind::Wall) { if (_ctx.wall_texts[r.wall_index] && _ctx.wall_texts[r.wall_index]->text3d) _editor_wall.on_WordRemoved(_ctx.wall_texts[r.wall_index]->text3d.get()); @@ -235,13 +225,12 @@ GameState::on_Pick() if (base->type == ObjectType::Text) { _step_ledger.picked_up.push_back(base->text); base->set_Look(ConfigColor::keep, Text3d::depth_floor); - TextWall::show_TokenInfo(_ctx.wall_texts, base->text, World::get_BoxScale()); + TextWall::show_TokenInfo(_ctx.wall_texts, base->text); } - carry_BeginAnim(std::move(base), /*with_pitch=*/true); + carry_BeginAnim(std::move(base), true); return true; } - // ── Word: same flow, takes Text3dPtr out of _ctx.words ─────────── if (r.kind == PickKind::Word) { Text3dPtr word = std::move(_ctx.words[r.word_index]); _ctx.words.erase(_ctx.words.begin() + static_cast<std::ptrdiff_t>(r.word_index)); @@ -249,9 +238,9 @@ GameState::on_Pick() _step_ledger.picked_up.push_back(word->text); word->set_Look(ConfigColor::keep, Text3d::depth_floor); - TextWall::show_TokenInfo(_ctx.wall_texts, word->text, World::get_BoxScale()); + TextWall::show_TokenInfo(_ctx.wall_texts, word->text); - carry_BeginAnim(std::move(word), /*with_pitch=*/false); + carry_BeginAnim(std::move(word), false); return true; } return false; @@ -276,7 +265,7 @@ GameState::on_DropLast() _anims_drop.push_back(std::move(drop)); if (!_carried.empty()) - TextWall::show_TokenInfo(_ctx.wall_texts, _carried.last->word->text, World::get_BoxScale()); + TextWall::show_TokenInfo(_ctx.wall_texts, _carried.last->word->text); else infowall_StepdetailsShow(); @@ -301,14 +290,12 @@ GameState::on_ShootOne() single.push_back(std::move(word)); Aim::Result aim = Aim::crosshair_Target(*_ctx.camera, _ctx.words, _ctx.scenery, - _ctx.wall_texts, World::get_BoxScale()); - _projectiles_sys->launch(_projectiles, std::move(single /* or all_words */), + _ctx.wall_texts, World::get_BoxScale()); + _projectiles_sys->launch(_projectiles, std::move(single), _ctx.camera->fwd, _ctx.camera->pos, aim); if (!_carried.empty() && _carried.last && _carried.last->word) - TextWall::show_TokenInfo(_ctx.wall_texts, - _carried.last->word->text, - World::get_BoxScale()); + TextWall::show_TokenInfo(_ctx.wall_texts, _carried.last->word->text); else infowall_StepdetailsShow(); _step_ledger.shot = true; @@ -334,7 +321,7 @@ GameState::on_ShootAll() _carried = CarriedLine(); if (!all_words.empty()) { Aim::Result aim = Aim::crosshair_Target(*_ctx.camera, _ctx.words, _ctx.scenery, - _ctx.wall_texts, World::get_BoxScale()); + _ctx.wall_texts, World::get_BoxScale()); _projectiles_sys->launch(_projectiles, std::move(all_words), _ctx.camera->fwd, _ctx.camera->pos, aim); } @@ -348,14 +335,15 @@ GameState::on_ShootAll() void GameState::on_Compile() { std::string src = TextWall::extract_Text(_ctx.wall_texts, Wall::Role::Editor); - if (_level_runner_sys && !_level_runner_sys->complete() && _level_runner_sys->dynamic_lib()) { + if ( !_level_runner_sys->complete() && + _level_runner_sys->dynamic_lib()) { const std::string path = sandbox_CanonicalPath(); std::string stripped; for (char c : src) if (!std::isspace((unsigned char)c)) { stripped = src; break; } if (!path.empty() && !stripped.empty()) SourceTokens::write_File(path, src); - _mod_loader_sys->build(_ctx.wall_texts, src, World::get_BoxScale(), true); + _dyn_loader_sys->build(_ctx.wall_texts, src, true); } else { _compiler_sys->compile(_ctx.wall_texts, src); } @@ -411,45 +399,45 @@ GameState::on_NumberAdjust(int delta) if (_carried.empty() || !_carried.last || !_carried.last->word) return; Text3d* w = _carried.last->word.get(); - // Parse the text as an integer; bail if it isn't one. const std::string& s = w->text; if (s.empty()) return; + size_t start = (s[0] == '-') ? 1 : 0; if (start == s.size()) return; for (size_t i = start; i < s.size(); ++i) - if (!std::isdigit(static_cast<unsigned char>(s[i]))) return; // not a plain integer + if (!std::isdigit(static_cast<unsigned char>(s[i]))) return; long val = std::strtol(s.c_str(), nullptr, 10); val += delta; w->text = std::to_string(val); - w->rebuild_Mesh(); // regenerate glyphs + half_extent for the new text + w->rebuild_Mesh(); } void -GameState::on_TextStart(FrameInput& input) +GameState::on_TypingStart(FrameInput& input) { - input.text_mode = true; - input.text_buffer.clear(); - _typing_words .clear(); - _typing_last .clear(); + input.typing_mode = true; + input.typing_buffer.clear(); + _typing_words.clear(); + _typing_last .clear(); } void -GameState::on_TextCommit(FrameInput& input) +GameState::on_TypingCommit(FrameInput& input) { - input.text_mode = false; - input.text_buffer.clear(); - _typing_words .clear(); - _typing_last .clear(); + input.typing_mode = false; + input.typing_buffer.clear(); + _typing_words.clear(); + _typing_last .clear(); } void -GameState::on_TextCancel(FrameInput& input) +GameState::on_TypingCancel(FrameInput& input) { typing_ClearPreview(); - input.text_mode = false; - input.text_buffer.clear(); - _typing_last .clear(); + input.typing_mode = false; + input.typing_buffer.clear(); + _typing_last.clear(); } @@ -525,9 +513,9 @@ GameState::projectile_LandOnWall(ProjectileResult& result) w->body.mode = PhysicsMode::Static; w->body.vel = Vec3{0, 0, 0}; - TextWallPtr wt = TextWall::from_Drop(std::move(w), World::get_BoxScale()); + TextWallPtr wt = TextWall::from_Drop(std::move(w)); if (wt && wt->text3d && wt->role == Wall::Role::Editor) - editor_group.push_back(wt->text3d.get()); // collect, don't place yet + editor_group.push_back(wt->text3d.get()); placed.push_back(std::move(wt)); } @@ -606,6 +594,57 @@ GameState::handle_Event(Game& game, const SDL_Event& event) _input_sys->handle_Event(game, event, _frame_input); } +void +GameState::handle_Input(FrameInput& input, double dt) { + for (InputCommand cmd : input.commands) { + switch (cmd) { + case InputCommand::Jump: on_Jump(); break; + case InputCommand::Click: on_Click(); break; + case InputCommand::Drop: on_DropLast(); break; + case InputCommand::ShootAll: on_ShootAll(); break; + case InputCommand::Compile: on_Compile(); break; + case InputCommand::Run: on_Run(); break; + case InputCommand::DebugPrint: on_DebugPrint(); break; + case InputCommand::FreelookStart: on_FreelookStart(); break; + case InputCommand::FreelookEnd: on_FreelookEnd(); break; + case InputCommand::NumberInc: on_NumberAdjust(+1); break; + case InputCommand::NumberDec: on_NumberAdjust(-1); break; + case InputCommand::TypingStart: on_TypingStart (input); break; + case InputCommand::TypingCommit: on_TypingCommit(input); break; + case InputCommand::TypingCancel: on_TypingCancel(input); break; + case InputCommand::SandboxRestore: sandbox_Restore(); break; + case InputCommand::ConnectPultSubmit: conn_Submit(); break; + case InputCommand::ConnectPultExit: connect_End(); break; + } + } + if (input.capture_vim) { + editor_FeedVim(input.vim_keys); + input.vim_keys.clear(); + } else if (input.capture_typing) { + if (input.typing_buffer != _conn_last) { conn_RebuildPreview(); _conn_last = input.typing_buffer; } + } else if (input.typing_mode) { + input.forward = input.backward = input.left = input.right = false; + if (input.typing_buffer != _typing_last) { typing_RebuildPreview(input.typing_buffer); _typing_last = input.typing_buffer; } + } + if (_pick_cooldown > 0.0) _pick_cooldown -= dt; + if (input.pick && _pick_cooldown <= 0.0) + { if (on_Pick()) _pick_cooldown = 0.15; } + + if (_shoot_cooldown > 0.0) _shoot_cooldown -= dt; + if (input.shoot && _shoot_cooldown <= 0.0) + { if (on_ShootOne()) _shoot_cooldown = 0.15; } +} + +void +GameState::update_PlayerCamera(FrameInput& input, double dt) +{ + if (!_freelook_sys->state.active) + _ctx.player->add_Look(input.look_x, input.look_y); + + _freelook_sys->update(dt); + _ctx.player->compute_Movement(input); + _ctx.camera->syncFromPlayer(*_ctx.player, 0.0); +} void GameState::update(double dt) @@ -615,75 +654,18 @@ GameState::update(double dt) _step_age += dt; { FP_ZONE("update.input_dispatch"); - for (InputCommand cmd : input.commands) { - switch (cmd) { - case InputCommand::Jump: on_Jump(); break; - case InputCommand::Click: on_Click(); break; - case InputCommand::Drop: on_DropLast(); break; - case InputCommand::ShootAll: on_ShootAll(); break; - case InputCommand::Compile: on_Compile(); break; - case InputCommand::Run: on_Run(); break; - case InputCommand::DebugPrint: on_DebugPrint(); break; - case InputCommand::FreelookStart: on_FreelookStart(); break; - case InputCommand::FreelookEnd: on_FreelookEnd(); break; - case InputCommand::NumberInc: on_NumberAdjust(+1); break; - case InputCommand::NumberDec: on_NumberAdjust(-1); break; - case InputCommand::TextStart: on_TextStart (input); break; - case InputCommand::TextCommit: on_TextCommit(input); break; - case InputCommand::TextCancel: on_TextCancel(input); break; - case InputCommand::SandboxRestore:sandbox_Restore(); break; - case InputCommand::ConnectPultSubmit: conn_Submit(); break; - case InputCommand::ConnectPultExit: connect_End(); break; - } - } - if (input.capture_typing) { - // ctrl+WASD already set movement in InputSystem; don't clear it. - if (input.text_buffer != _conn_last) { - conn_RebuildPreview(); - _conn_last = input.text_buffer; - } - } else if (input.text_mode) { - input.forward = input.backward = input.left = input.right = false; - if (input.text_buffer != _typing_last) { - typing_RebuildPreview(input.text_buffer); - _typing_last = input.text_buffer; - } - } - if (_pick_cooldown > 0.0) _pick_cooldown -= dt; - if (input.pick && _pick_cooldown <= 0.0) - { if (on_Pick()) _pick_cooldown = 0.15; } - - if (_shoot_cooldown > 0.0) _shoot_cooldown -= dt; - if (input.shoot && _shoot_cooldown <= 0.0) - { if (on_ShootOne()) _shoot_cooldown = 0.15; } + handle_Input(input, dt); } - - // Freelook freezes the view only while held. Once released the player - // keeps full camera control immediately and the carried line flies back - // on its own (see anchorframe_Current / FreelookSystem::update). - if (!_freelook_sys->state.active) - _ctx.player->add_Look(input.look_x, input.look_y); - - _freelook_sys->update(dt); - - _accumulator += dt; - _ctx.player->compute_Movement(input); - - // why do i still have that MAX_SUBSTEPS and not whatever frames we get - int steps = 0; - while (_accumulator >= dt && steps < ConfigGame::max_substeps) { - FP_ZONE("update.fixed_substep"); - substep(dt); - _accumulator -= dt; - ++steps; + { FP_ZONE("update.player_cam"); + update_PlayerCamera(input, dt); } - _accumulator = SDL_min(_accumulator, dt * ConfigGame::max_substeps); - _alpha = _accumulator / dt; + { FP_ZONE("update.world_simulation"); + update_WorldSimulation(dt); + } { FP_ZONE("update.post_step"); - _ctx.camera->syncFromPlayer(*_ctx.player, _alpha); for (TextWallPtr& wt : _ctx.wall_texts) { if (!wt || !wt->text3d) continue; @@ -695,13 +677,10 @@ GameState::update(double dt) carriedline_Update(); - const double floor_y = World::get_FloorY<double>(); - _word_anim_sys->update(_anims_add, _anims_drop, _carried, _ctx.words, dt, floor_y); + _word_anim_sys->update(_anims_add, _anims_drop, _carried, _ctx.words, dt); _pult_sys-> update(dt, *_ctx.camera); - _plate_sys-> update(dt, *_ctx.player, _step_plates); - _editor_wall.update(dt, _ctx.camera->pos); if (_conn != Conn::None) _conn_blink += dt; @@ -723,10 +702,10 @@ GameState::update(double dt) _step_ledger.compile_ok = true; } - _mod_loader_sys->update(_ctx.wall_texts, World::get_BoxScale()); - if (_mod_loader_sys->last_build_ok) { - _mod_loader_sys->last_build_ok = false; - mod_Apply(_mod_loader_sys->load_Objects()); + _dyn_loader_sys->update(_ctx.wall_texts); + if (_dyn_loader_sys->last_build_ok) { + _dyn_loader_sys->last_build_ok = false; + dyn_Apply(_dyn_loader_sys->load_Objects()); SourceTokens::copy_File(sandbox_CanonicalPath(), sandbox_BackupPath()); } } @@ -765,7 +744,7 @@ GameState::render(SDL_Window& window, GlRenderer& renderer) { FP_ZONE("render.set_Light"); renderer.set_Light(*_ctx.light); } { FP_ZONE("render.world"); _ctx.world ->draw(renderer, *_ctx.camera, *_ctx.light); } - { FP_ZONE("render._editor_wall"); _editor_wall.draw(renderer, *_ctx.camera, *_ctx.light); } + { FP_ZONE("render._editor_wall"); _editor_wall.draw(renderer, *_ctx.camera, *_ctx.light); } { FP_ZONE("render.scenery"); for (const GameObjectPtr& s : _ctx.scenery) @@ -788,10 +767,16 @@ GameState::render(SDL_Window& window, GlRenderer& renderer) } { FP_ZONE("render.wall_edit"); - if (_conn != Conn::None) { + if (_conn == Conn::Program) { if (_conn_input) _conn_input->draw(renderer, *_ctx.camera, *_ctx.light); conn_DrawCursor(renderer); conn_DrawElectric(renderer); + } else if (_conn == Conn::Editor) { + for (const Text3dPtr& t : _edit_render) if (t) t->draw(renderer, *_ctx.camera, *_ctx.light); + for (const Text3dPtr& n : _edit_linenums) if (n) n->draw(renderer, *_ctx.camera, *_ctx.light); + if (_edit_cmdline) _edit_cmdline->draw(renderer, *_ctx.camera, *_ctx.light); + edit_DrawCursor(renderer); + conn_DrawElectric(renderer); } } @@ -964,7 +949,7 @@ GameState::scene_Teardown() ); } - mod_Clear(); + dyn_Clear(); _step_scenery.clear(); _step_plates .clear(); @@ -972,7 +957,7 @@ GameState::scene_Teardown() void -GameState::substep(double dt) +GameState::update_WorldSimulation(double dt) { _ctx.player->prev_pos = _ctx.player->pos; @@ -984,14 +969,12 @@ GameState::substep(double dt) } DynArray<GameObject*> colliders; - { FP_ZONE("subst.build_colliders"); - colliders.reserve(_ctx.scenery.size() + _ctx.words.size()); - for (const GameObjectPtr& p : _ctx.scenery) if (p) colliders.push_back(p.get()); - for (const Text3dPtr& p : _ctx.words) if (p) colliders.push_back(p.get()); - } + colliders.reserve(_ctx.scenery.size() + _ctx.words.size()); + for (const GameObjectPtr& p : _ctx.scenery) if (p) colliders.push_back(p.get()); + for (const Text3dPtr& p : _ctx.words) if (p) colliders.push_back(p.get()); { FP_ZONE("subst.phys_objects"); - _physics_sys->update_Objects(colliders, dt, 25.0, World::get_BoxScale()); + _physics_sys->update_Objects(colliders, dt, 25.0); } { FP_ZONE("subst.word_ai"); @@ -1037,12 +1020,10 @@ GameState::substep(double dt) }); } - // Hit Line Number for (const EditorWall::NumberHit& nh : _editor_wall.number_Hitboxes()) wall_text_hits.push_back({ Wall::Position::NegX, nh.aabb_min, nh.aabb_max, nh.index }); - // Hit Wall Label for (size_t li = 0; li < _wall_labels.size(); ++li) { const DynArray<Text3dPtr>& words = _wall_labels[li].label.words(); for (size_t wi = 0; wi < words.size(); ++wi) { @@ -1061,7 +1042,7 @@ GameState::substep(double dt) DynArray<ProjectileResult> proj_results; { FP_ZONE("subst.projectile_sys"); _projectiles_sys->update(_projectiles, proj_results, - wall_text_hits, dt, World::get_BoxScale(), + wall_text_hits, dt, _program_sys->is_Running()); } @@ -1112,7 +1093,6 @@ GameState::substep(double dt) _physics_sys->resolve_PlayerVsObjects(*_ctx.player, _carried, colliders); - // Jump-on-button if (std::optional<PultAction> act = _pult_sys->handle_PlayerContact(*_ctx.player, _ctx.buttons); act) pult_DispatchAction(*act); @@ -1202,11 +1182,11 @@ GameState::level_Advance() void -GameState::mod_Clear() +GameState::dyn_Clear() { - if (_mod_scenery.empty()) { return; } + if (_dyn_scenery.empty()) { return; } - std::unordered_set<GameObject*> set(_mod_scenery.begin(), _mod_scenery.end()); + std::unordered_set<GameObject*> set(_dyn_scenery.begin(), _dyn_scenery.end()); _ctx.scenery.erase( std::remove_if(_ctx.scenery.begin(), _ctx.scenery.end(), [&set](const GameObjectPtr& sp) { return set.count(sp.get()) > 0; }), @@ -1215,31 +1195,31 @@ GameState::mod_Clear() std::remove_if(_ctx.words.begin(), _ctx.words.end(), [&set](const Text3dPtr& wp) { return set.count(wp.get()) > 0; }), _ctx.words.end()); - _mod_scenery.clear(); + _dyn_scenery.clear(); } void -GameState::mod_Apply(const DynArray<ModObject>& objs) +GameState::dyn_Apply(const DynArray<DynObject>& objs) { - mod_Clear(); + dyn_Clear(); // Build fresh objects from the descriptions. - for (const ModObject& o : objs) { - if (o.kind == MOD_BOX) { + for (const DynObject& o : objs) { + if (o.kind == DYN_BOX) { auto box = cpputils::make_unique<SolidBox>( Vec3{ o.x, o.y, o.z }, Vec3{ o.hx, o.hy, o.hz }, SDL_FColor{ o.r, o.g, o.b, 1.0f }); box->body.mode = o.dynamic ? PhysicsMode::Dynamic : PhysicsMode::Static; - _mod_scenery.push_back(box.get()); + _dyn_scenery.push_back(box.get()); _ctx.scenery.push_back(std::move(box)); - } else { // MOD_TEXT + } else { // DYN_TEXT Text3dPtr t = make_Text3d(o.text, Text3d::depth_floor, 0.003f, Vec3{ o.x, o.y, o.z }, SDL_FColor{ o.r, o.g, o.b, 1.0f }); t->body.mode = PhysicsMode::Static; // lib text just sits where placed - _mod_scenery.push_back(t.get()); + _dyn_scenery.push_back(t.get()); _ctx.words.push_back(std::move(t)); } } @@ -1376,17 +1356,7 @@ GameState::connect_Begin(Conn c) _conn = c; _conn_pult_pos = _pult_sys->get_ConnectProgramPos(); _frame_input.capture_typing = true; - _frame_input.text_buffer.clear(); - _conn_last.clear(); - _conn_input.reset(); -} - -void -GameState::connect_End() -{ - _conn = Conn::None; - _frame_input.capture_typing = false; - _frame_input.text_buffer.clear(); + _frame_input.typing_buffer.clear(); _conn_last.clear(); _conn_input.reset(); } @@ -1394,8 +1364,8 @@ GameState::connect_End() void GameState::conn_Submit() { - const std::string line = _frame_input.text_buffer; - _frame_input.text_buffer.clear(); + const std::string line = _frame_input.typing_buffer; + _frame_input.typing_buffer.clear(); _conn_last.clear(); _conn_input.reset(); @@ -1419,7 +1389,7 @@ GameState::conn_RebuildPreview() Vec3 pos = tf.position; pos.y = ProgramSystem::start_y; - _conn_input = make_Text3d("> " + _frame_input.text_buffer, + _conn_input = make_Text3d("> " + _frame_input.typing_buffer, Text3d::depth_wall, 0.003f, pos, ConfigColor::white); _conn_input->yaw = tf.yaw; } @@ -1428,10 +1398,10 @@ void GameState::conn_DrawCursor(GlRenderer& gl) { if (!_conn_input) return; - if (SDL_fmod(_conn_blink, 0.8) > 0.4) return; // blink: on half the cycle + if (SDL_fmod(_conn_blink, 0.8) > 0.4) return; Vec3 c = _conn_input->pos; - c.z = _conn_input->pos.z - (static_cast<double>(_conn_input->half_extent) + 0.15); // just past the line (wall +X → text runs along z) + c.z = _conn_input->pos.z - (static_cast<double>(_conn_input->half_extent) + 0.15); Mat4 view = buildViewMat4(*_ctx.camera); Mat4 model = buildModelMat4(c, _conn_input->yaw); @@ -1482,3 +1452,307 @@ GameState::conn_DrawElectric(GlRenderer& gl) gl.draw_Lines(pts.data(), static_cast<int>(pts.size()) / 3); gl.set_Wireframe(false); } + + +void +GameState::connect_BeginEditor() +{ + _conn = Conn::Editor; + _conn_pult_pos = _pult_sys->get_ConnectEditorPos(); + _frame_input.capture_vim = true; + _edit_status.clear(); + + editor_Absorb(); // loads _vim, removes real wall words + + if (_vim.is_Empty()) + _vim.start_Insert(); // empty wall → fresh line, ready to type + // else: load() left cursor at row 0 / col 0 in Normal mode + + _vim_seen_version = _vim.version(); + editor_RebuildRender(); +} + +void +GameState::editor_Absorb() +{ + // Pull existing editor source (lines joined by spaces, '\n' separated). + std::string src = TextWall::extract_Text(_ctx.wall_texts, Wall::Role::Editor); + + DynArray<std::string> lines; + std::string cur; + for (char ch : src) { + if (ch == '\n') { lines.push_back(cur); cur.clear(); } + else { cur.push_back(ch); } + } + if (!cur.empty()) lines.push_back(cur); + + // VimBuffer wants std::vector; convert (DynArray ↔ vector). + std::vector<std::string> v(lines.begin(), lines.end()); + _vim.load(v); + + // Remove the real editor words — the buffer is the source of truth now. + _editor_wall.clear(); + _ctx.wall_texts.erase( + std::remove_if(_ctx.wall_texts.begin(), _ctx.wall_texts.end(), + [](const TextWallPtr& wt){ return wt->role == Wall::Role::Editor; }), + _ctx.wall_texts.end()); +} + + +void +GameState::editor_RebuildRender() +{ + _edit_render.clear(); + _edit_linenums.clear(); + + const float ws = World::get_BoxScale(); + Wall::Transform tf = Wall::get_Transform(Wall::Role::Editor, ws); + const double lh = ConfigWall::line_height; + const double eye_y = _ctx.camera->pos.y; + const int R = _vim.row(); + const int N = (int)_vim.lines().size(); + const double bottom_y = -static_cast<double>(ws) + 1.2; + + const double anchor_cursor = eye_y + static_cast<double>(R) * lh; + const double anchor_bottom = bottom_y + static_cast<double>(N - 1) * lh; + _edit_row0_y = SDL_min(anchor_cursor, anchor_bottom); + + const auto& lines = _vim.lines(); + + // ── Pass 1: build the renderables in a LOCAL left-aligned frame ────────── + // u = 0 at block's left edge, increasing rightward. We track each line's + // text width to find the block width (gutter + widest line). + struct Row { Text3dPtr num; Text3dPtr txt; double txt_w; }; + DynArray<Row> rows; + rows.reserve(lines.size()); + + // Gutter width: built from the widest line-number ("N"), plus a gap. + // Build numbers first to measure. + double gutter_w = 0.0; + for (size_t i = 0; i < lines.size(); ++i) { + Text3dPtr num = make_Text3d(std::to_string(i + 1), Text3d::depth_wall, 0.0022f, + tf.position, ConfigColor::editor); + num->yaw = tf.yaw; + gutter_w = SDL_max(gutter_w, 2.0 * static_cast<double>(num->half_extent)); + Row r; r.num = std::move(num); rows.push_back(std::move(r)); + } + const double gutter_gap = 0.6; // space between number and code + const double code_u0 = gutter_w + gutter_gap; // code's left edge in u + + double block_w = code_u0; // at least the gutter; grows with widest line + for (size_t i = 0; i < lines.size(); ++i) { + const bool drawable = lines[i].find_first_not_of(' ') != std::string::npos; + if (drawable) { + Text3dPtr t = make_Text3d(lines[i], Text3d::depth_wall, 0.003f, + tf.position, ConfigColor::wall); + t->yaw = tf.yaw; + rows[i].txt_w = 2.0 * static_cast<double>(t->half_extent); + rows[i].txt = std::move(t); + } else { + rows[i].txt_w = 0.0; + } + block_w = SDL_max(block_w, code_u0 + rows[i].txt_w); + } + + // ── Pass 2: center the block on the wall (z = 0) and emit ──────────────── + // Block spans u ∈ [0, block_w]; we want it centered, so its world left edge + // (largest z) is at z = block_w/2, and z = z_left - u. + const double z_block_left = block_w * 0.5; + + for (size_t i = 0; i < rows.size(); ++i) { + const double row_y = _edit_row0_y - static_cast<double>(i) * lh; + + // number: left-aligned at u = 0 → its left edge sits at z_block_left + if (rows[i].num) { + const double he = static_cast<double>(rows[i].num->half_extent); + rows[i].num->pos = tf.position; + rows[i].num->pos.y = row_y; + rows[i].num->pos.z = z_block_left - he; // left edge at u=0, center inward + _edit_linenums.push_back(std::move(rows[i].num)); + } + + // code: left edge at u = code_u0 + if (rows[i].txt) { + const double he = static_cast<double>(rows[i].txt->half_extent); + rows[i].txt->pos = tf.position; + rows[i].txt->pos.y = row_y; + rows[i].txt->pos.z = z_block_left - code_u0 - he; // left-aligned within block + _edit_render.push_back(std::move(rows[i].txt)); + } else { + _edit_render.push_back(Text3dPtr{}); // keep row index alignment + } + } + + // stash for the cursor (which needs the code column's world left edge) + _edit_code_left_z = z_block_left - code_u0; + + editor_RebuildCmdline(); +} + + +void +GameState::editor_RebuildCmdline() +{ + _edit_cmdline.reset(); + + std::string text; + if (_vim.mode() == VimBuffer::Mode::Command) text = _vim.cmdline(); // includes ':' + else if (!_edit_status.empty()) text = _edit_status[0]; // help/first status line + else if (!_vim.status().empty()) text = _vim.status(); + else { + switch (_vim.mode()) { + case VimBuffer::Mode::Insert: text = "-- INSERT --"; break; + default: text = "-- NORMAL --"; break; + } + } + if (text.empty()) return; + + const float ws = World::get_BoxScale(); + Wall::Transform tf = Wall::get_Transform(Wall::Role::Editor, ws); + + Vec3 pos = tf.position; + pos.x += 0.8; // pull toward room center (off the wall, in front) + pos.y = -static_cast<double>(ws) + 0.8; // fixed near the floor — always visible + pos.z = static_cast<double>(ws) - 1.0; // left-aligned with the gutter + + _edit_cmdline = make_Text3d(text, Text3d::depth_wall, 0.0028f, pos, ConfigColor::white); + _edit_cmdline->yaw = tf.yaw; + _edit_cmdline->pos.z = 0.0; +} + + +void +GameState::editor_FeedVim(DynArray<int>& keys) +{ + for (int k : keys) { + if (k == -1) _vim.key_enter(); + else if (k == -2) _vim.key_escape(); + else if (k == -3) _vim.key_backspace(); + else if (k > 0) _vim.key_char(static_cast<char>(k)); + } + + switch (_vim.take_Signal()) { + case VimBuffer::Signal::Exit: editor_CommitToWall(); connect_End(); return; + case VimBuffer::Signal::Write: editor_CommitToWall(); editor_Absorb(); + _vim_seen_version = _vim.version() - 1; break; + case VimBuffer::Signal::Help: + _edit_status.clear(); + _edit_status.push_back("-- VIM HELP --"); + _edit_status.push_back("i a A o O insert Esc normal h j k l move"); + _edit_status.push_back("0 $ line ends gg G :N jump line x dd delete"); + _edit_status.push_back(":w write to wall :exit leave ctrl+WASD move"); + break; + default: break; + } + + if (_vim.version() != _vim_seen_version) { + _vim_seen_version = _vim.version(); + editor_RebuildRender(); + } +} + +void +GameState::editor_CommitToWall() +{ + // buffer lines → real editor-wall words (throwing/compile/scroll work again) + DynArray<std::string> lines(_vim.lines().begin(), _vim.lines().end()); + editor_PlaceLines(lines); + _edit_render.clear(); + _edit_status.clear(); +} + +void +GameState::connect_End() // editor-aware version (replaces the Step-1 one) +{ + if (_conn == Conn::Editor) { + _frame_input.capture_vim = false; + _frame_input.vim_keys.clear(); + _edit_render.clear(); + _edit_linenums.clear(); + _edit_cmdline.reset(); + _edit_status.clear(); + } else { + _frame_input.capture_typing = false; + _frame_input.typing_buffer.clear(); + _conn_last.clear(); + _conn_input.reset(); + } + _conn = Conn::None; +} + +void +GameState::edit_DrawCursor(GlRenderer& gl) +{ + if (_conn != Conn::Editor) return; + if (SDL_fmod(_conn_blink, 0.8) > 0.4) return; + + const int row = _vim.row(); + if (row < 0 || row >= (int)_vim.lines().size()) return; + + const float ws = World::get_BoxScale(); + Wall::Transform tf = Wall::get_Transform(Wall::Role::Editor, ws); + const double lh = ConfigWall::line_height; + + const std::string& s = _vim.lines()[(size_t)row]; + const double row_y = _edit_row0_y - static_cast<double>(row) * lh; // same formula + + double cw, line_x = tf.position.x, he_y = 0.2; + if (row < (int)_edit_render.size() && _edit_render[(size_t)row]) { + const Text3dPtr& line = _edit_render[(size_t)row]; + const int N = s.empty() ? 1 : (int)s.size(); + cw = (2.0 * static_cast<double>(line->half_extent)) / static_cast<double>(N); + line_x = line->pos.x; + he_y = static_cast<double>(line->half_extent_y); + } else { + cw = 0.18; // empty line: nominal advance + } + + const int col = _vim.col(); + // left edge of the code column (set in editor_RebuildRender after centering) + const double zL = _edit_code_left_z; + const double cz = zL - (static_cast<double>(col) + 0.5) * cw; + + Vec3 c { line_x, row_y - (he_y + 0.06), cz }; // underline: just below the glyphs + + Mat4 view = buildViewMat4(*_ctx.camera); + Mat4 model = buildModelMat4(c, tf.yaw); + Mat4 mvp = Mat4::mul(gl.projection(), Mat4::mul(view, model)); + + gl.set_Wireframe(false); + gl.set_MVP(model, mvp); + gl.set_NormalMat(model); + gl.set_Light(*_ctx.light); + gl.set_Color(0.6f, 1.0f, 1.0f); + gl.set_Emissive(0.6f, 1.0f, 1.0f, 0.9f); + gl.draw_Box(static_cast<float>(cw * 0.45), 0.015f, 0.03f); // wide+thin = underline + gl.set_Emissive(0.0f, 0.0f, 0.0f, 0.0f); +} + + +void +GameState::editor_PlaceLines(const DynArray<std::string>& lines) +{ + const float ws = World::get_BoxScale(); + const double x_wall = -static_cast<double>(ws) + 0.05; + Wall::Transform tf = Wall::get_Transform(Wall::Role::Editor, ws); + + double y = static_cast<double>(ws) - 2.0; + const double z_start = static_cast<double>(ws) - 1.0; + + for (const std::string& line : lines) { + double z = z_start; + std::istringstream in(line); + std::string tk; + while (in >> tk) { + Text3dPtr t = make_Text3d(tk, Text3d::depth_wall, 0.003f, + Vec3{ x_wall, y, z }, ConfigColor::wall); + t->yaw = tf.yaw; + Vec3 snapped = _editor_wall.on_WordPlaced(t.get(), Vec3{ x_wall, y, z }); + t->pos = snapped; + _ctx.wall_texts.push_back(cpputils::make_unique<TextWall>(TextWall{ + Wall::Position::NegX, Wall::Role::Editor, std::move(t) })); + z = snapped.z - 0.5; + } + y -= ConfigWall::line_height; + } +} diff --git a/src/states/game_state.hpp b/src/states/game_state.hpp index 4a92833..9c7e67e 100644 --- a/src/states/game_state.hpp +++ b/src/states/game_state.hpp @@ -12,11 +12,11 @@ #include "systems/carry_system.hpp" #include "systems/celebration_system.hpp" #include "systems/compiler_system.hpp" +#include "systems/dyn_loader_system.hpp" #include "systems/freelook_system.hpp" #include "systems/hint_system.hpp" #include "systems/input_system.hpp" #include "systems/level_runner_system.hpp" -#include "systems/mod_loader_system.hpp" #include "systems/particle_system.hpp" #include "systems/physics_system.hpp" #include "systems/plate_system.hpp" @@ -25,6 +25,7 @@ #include "systems/pult_system.hpp" #include "systems/word_ai_system.hpp" #include "systems/word_anim.hpp" +#include "text_3d/vim_buffer.hpp" #include "utils/carry_math.hpp" #include "utils/globals.hpp" #include "wall/editor_wall.hpp" @@ -69,12 +70,15 @@ class GameState : public IState double _pick_cooldown { 0.0 }; double _shoot_cooldown { 0.0 }; double _step_age { 0.0 }; - double _accumulator { 0.0 }; - double _alpha { 0.0 }; - - // TODO START: OBJECT ORIENT ME + VimBuffer _vim; + unsigned _vim_seen_version { 0 }; + DynArray<Text3dPtr> _edit_render; + DynArray<std::string> _edit_status; + double _edit_code_left_z { 0.0 }; // world z of the code column's left edge (for cursor) + double _edit_row0_y { 0.0 }; // world-y of buffer row 0 (scroll anchor) + enum class Conn : uint8_t { None, Program, Editor }; Conn _conn { Conn::None }; std::string _conn_last; // last previewed buffer @@ -87,6 +91,16 @@ class GameState : public IState void conn_RebuildPreview (); void conn_DrawCursor (GlRenderer& gl); void conn_DrawElectric (GlRenderer& gl); + void connect_BeginEditor (); + void editor_Absorb (); + void editor_RebuildRender(); + void editor_CommitToWall (); + void editor_FeedVim (DynArray<int>& keys); + void edit_DrawCursor (GlRenderer& gl); + void editor_PlaceLines (const DynArray<std::string>& lines); + void editor_RebuildCmdline(); + DynArray<Text3dPtr> _edit_linenums; // per-row "1,2,3…" labels (left gutter) + Text3dPtr _edit_cmdline; // pinned command/status bar (in front of wall) // TODO END: OBJECT ORIENT ME @@ -99,7 +113,7 @@ class GameState : public IState DynArray<WordAnim_Add> _anims_add; DynArray<WordAnim_Drop> _anims_drop; - DynArray<GameObject*> _mod_scenery; + DynArray<GameObject*> _dyn_scenery; DynArray<GameObject*> _step_scenery; DynArray<StepPlate> _step_plates; @@ -117,7 +131,7 @@ class GameState : public IState cpputils::unique_ptr<ParticleSystem> _particle_sys; cpputils::unique_ptr<FloorMandelbrot> _floor_sys; cpputils::unique_ptr<LevelRunner> _level_runner_sys; - cpputils::unique_ptr<ModLoader> _mod_loader_sys; + cpputils::unique_ptr<DynLoaderSystem> _dyn_loader_sys; cpputils::unique_ptr<CelebrationSystem>_celebration_sys; @@ -133,14 +147,14 @@ class GameState : public IState void on_Compile (); void on_Run (); void on_NumberAdjust (int dt); - void on_TextStart (FrameInput&); - void on_TextCommit (FrameInput&); - void on_TextCancel (FrameInput&); + void on_TypingStart (FrameInput&); + void on_TypingCommit (FrameInput&); + void on_TypingCancel (FrameInput&); void scene_Setup (const Scene& scene); void scene_Teardown (); - void substep (double dt); - + void update_WorldSimulation + (double dt); void step_Spawn (const DynArray<std::string>& tokens); void step_SettingsApply(); @@ -170,9 +184,12 @@ class GameState : public IState std::tuple<Vec3, Vec3, double> anchorframe_Current () const; + void handle_Input (FrameInput&, double dt); + void update_PlayerCamera(FrameInput& input, double dt); + // dynamic-lib sandbox START - void mod_Clear (); - void mod_Apply (const DynArray<ModObject>& objs); + void dyn_Clear (); + void dyn_Apply (const DynArray<DynObject>& objs); void sandbox_LoadFileToWall (); void sandbox_Restore (); std::string sandbox_CanonicalPath() const; diff --git a/src/systems/mod_loader_system.cpp b/src/systems/dyn_loader_system.cpp similarity index 82% rename from src/systems/mod_loader_system.cpp rename to src/systems/dyn_loader_system.cpp index fcf7468..307175a 100644 --- a/src/systems/mod_loader_system.cpp +++ b/src/systems/dyn_loader_system.cpp @@ -4,7 +4,8 @@ #include <string> #include <sys/select.h> -#include "systems/mod_loader_system.hpp" +#include "entities/world.hpp" +#include "systems/dyn_loader_system.hpp" #include "text_3d/text_3d.hpp" #include "utils/ascii.hpp" @@ -22,36 +23,36 @@ static constexpr const char* MOD_SRC_PATH = "/tmp/game_mod.c"; // the engine-provided output array. Bounds-checked so a player can't // overflow `out`. std::string -ModLoader::wrap_Source(const std::string& user_body) +DynLoaderSystem::wrap_Source(const std::string& user_body) { std::string s; s += "#include \"game_mod_abi.h\"\n"; s += "#include <string.h>\n"; s += "\n"; - s += "static ModObject* g_out;\n"; + s += "static DynObject* g_out;\n"; s += "static int g_max;\n"; s += "static int g_n;\n"; s += "\n"; s += "static void box(double x,double y,double z," "double hx,double hy,double hz){\n"; s += " if(g_n>=g_max) return;\n"; - s += " ModObject* o=&g_out[g_n++];\n"; - s += " o->kind=MOD_BOX; o->x=x;o->y=y;o->z=z;\n"; + s += " DynObject* o=&g_out[g_n++];\n"; + s += " o->kind=DYN_BOX; o->x=x;o->y=y;o->z=z;\n"; s += " o->hx=hx;o->hy=hy;o->hz=hz;\n"; s += " o->r=0.55f;o->g=0.55f;o->b=0.75f; o->text[0]=0;\n"; s += "}\n"; s += "\n"; s += "static void text(double x,double y,double z,const char* t){\n"; s += " if(g_n>=g_max) return;\n"; - s += " ModObject* o=&g_out[g_n++];\n"; - s += " o->kind=MOD_TEXT; o->x=x;o->y=y;o->z=z;\n"; + s += " DynObject* o=&g_out[g_n++];\n"; + s += " o->kind=DYN_TEXT; o->x=x;o->y=y;o->z=z;\n"; s += " o->hx=o->hy=o->hz=0.0;\n"; s += " o->r=1.0f;o->g=1.0f;o->b=1.0f;\n"; s += " strncpy(o->text, t?t:\"\", sizeof(o->text)-1);\n"; s += " o->text[sizeof(o->text)-1]=0;\n"; s += "}\n"; s += "\n"; - s += "int game_build(ModObject* out,int max){\n"; + s += "int game_build(DynObject* out,int max){\n"; s += " g_out=out; g_max=max; g_n=0;\n"; s += " /* ---- player source begins ---- */\n"; s += user_body; @@ -63,7 +64,7 @@ ModLoader::wrap_Source(const std::string& user_body) void -ModLoader::build(TextWallList& texts, const std::string& user_src, float world_scale, bool full_source) +DynLoaderSystem::build(TextWallList& texts, const std::string& user_src, bool full_source) { last_build_ok = false; @@ -80,8 +81,8 @@ ModLoader::build(TextWallList& texts, const std::string& user_src, float world_s FILE* f = fopen(MOD_SRC_PATH, "w"); if (!f) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "ModLoader: cannot write %s", MOD_SRC_PATH); - push_Line(texts, "internal error: cannot write source", world_scale); + "DynLoaderSystem: cannot write %s", MOD_SRC_PATH); + push_Line(texts, "internal error: cannot write source"); return; } const std::string out = full_source ? user_src : wrap_Source(user_src); @@ -104,12 +105,12 @@ ModLoader::build(TextWallList& texts, const std::string& user_src, float world_s if (_pipe) { pclose(_pipe); _pipe = nullptr; } _pipe = popen(cmd.c_str(), "r"); if (!_pipe) - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ModLoader: popen failed"); + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "DynLoaderSystem: popen failed"); } void -ModLoader::update(TextWallList& texts, float world_scale) +DynLoaderSystem::update(TextWallList& texts) { if (!_pipe) return; @@ -126,14 +127,14 @@ ModLoader::update(TextWallList& texts, float world_scale) if (_count == 0) { last_build_ok = true; _last_so_path = _pending_so; - push_Line(texts, "BUILD OK", world_scale); + push_Line(texts, "BUILD OK"); } return; } std::string line(buf); if (!line.empty() && line.back() == '\n') line.pop_back(); if (!line.empty()) - push_Line(texts, line, world_scale); + push_Line(texts, line); FD_ZERO(&set); FD_SET(fd, &set); } @@ -141,14 +142,14 @@ ModLoader::update(TextWallList& texts, float world_scale) void -ModLoader::push_Line(TextWallList& texts, const std::string& line, float world_scale) +DynLoaderSystem::push_Line(TextWallList& texts, const std::string& line) { // Shift existing compiler lines up, drop the new one at the bottom. for (TextWallPtr& wt : texts) if (wt->role == Wall::Role::Compiler) wt->text3d->pos.y += static_cast<double>(line_height); - Wall::Transform tf = Wall::get_Transform(Wall::Role::Compiler, world_scale); + Wall::Transform tf = Wall::get_Transform(Wall::Role::Compiler, World::get_BoxScale()); Vec3 pos = tf.position; pos.y = start_y; @@ -165,10 +166,10 @@ ModLoader::push_Line(TextWallList& texts, const std::string& line, float world_s _count++; } -DynArray<ModObject> -ModLoader::load_Objects() +DynArray<DynObject> +DynLoaderSystem::load_Objects() { - DynArray<ModObject> result; + DynArray<DynObject> result; if (_last_so_path.empty()) return result; // Close the previous handle before loading the new one. Safe because the @@ -183,17 +184,17 @@ ModLoader::load_Objects() _handle = dlopen(_last_so_path.c_str(), RTLD_NOW | RTLD_LOCAL); if (!_handle) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "ModLoader: dlopen failed: %s", dlerror()); + "DynLoaderSystem: dlopen failed: %s", dlerror()); return result; } - using BuildFn = int(*)(ModObject*, int); + using BuildFn = int(*)(DynObject*, int); // The cast-through-void* dance silences -Wpedantic on function-pointer casts. BuildFn game_build = reinterpret_cast<BuildFn>(dlsym(_handle, "game_build")); if (!game_build) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "ModLoader: dlsym(game_build) failed: %s", dlerror()); + "DynLoaderSystem: dlsym(game_build) failed: %s", dlerror()); return result; } @@ -207,7 +208,7 @@ ModLoader::load_Objects() } -ModLoader::~ModLoader() +DynLoaderSystem::~DynLoaderSystem() { if (_handle) dlclose(_handle); if (_pipe) pclose(_pipe); diff --git a/src/systems/mod_loader_system.hpp b/src/systems/dyn_loader_system.hpp similarity index 82% rename from src/systems/mod_loader_system.hpp rename to src/systems/dyn_loader_system.hpp index 5e19860..47ddd95 100644 --- a/src/systems/mod_loader_system.hpp +++ b/src/systems/dyn_loader_system.hpp @@ -7,7 +7,7 @@ #include "utils/globals.hpp" #include "wall/text_wall.hpp" -class ModLoader +class DynLoaderSystem { FILE* _pipe { nullptr }; int _count { 0 }; @@ -22,21 +22,21 @@ class ModLoader static std::string wrap_Source(const std::string& user_body); - void push_Line (TextWallList& texts, const std::string& line, float world_scale); + void push_Line (TextWallList& texts, const std::string& line); public: bool last_build_ok { false }; - void build (TextWallList& texts, const std::string& user_src, float world_scale, bool full_source); - void update (TextWallList& texts, float world_scale); + void build (TextWallList& texts, const std::string& user_src, bool full_source); + void update (TextWallList& texts); bool is_Running () const { return _pipe != nullptr; } const std::string& last_so_path() const { return _last_so_path; } // Checkpoint 2: dlopen the last successful .so, call game_build, // return the spawned descriptions. Empty vector on any failure. - DynArray<ModObject> + DynArray<DynObject> load_Objects(); - ~ModLoader(); // dlclose on teardown + ~DynLoaderSystem(); // dlclose on teardown }; diff --git a/src/systems/game_mod_abi.h b/src/systems/game_mod_abi.h index 8ee2c8d..3f5be47 100644 --- a/src/systems/game_mod_abi.h +++ b/src/systems/game_mod_abi.h @@ -20,26 +20,26 @@ extern "C" { #endif typedef enum { - MOD_TEXT = 0, - MOD_BOX = 1 -} ModObjKind; + DYN_TEXT = 0, + DYN_BOX = 1 +} DynObjKind; /* A flat, POD description of one object to spawn. No pointers, no C++: * the library fills these in, the engine constructs the real Text3d / * SolidBox from them. Nothing the library allocates outlives dlclose(). */ typedef struct { - int kind; /* ModObjKind */ + int kind; /* DynObjKind */ int dynamic; double x, y, z; /* world position (centre) */ double hx, hy, hz; /* half-extents — BOX only; ignored for TEXT */ float r, g, b; /* colour 0..1 */ char text[64]; /* TEXT only, null-terminated; ignored for BOX */ -} ModObject; +} DynObject; /* The user's compiled library MUST export this symbol. * Fills `out` (capacity `max` entries) and returns the number written. * Returning more than `max` is undefined; the engine passes a generous max. */ -int game_build(ModObject* out, int max); +int game_build(DynObject* out, int max); #ifdef __cplusplus } diff --git a/src/systems/input_system.cpp b/src/systems/input_system.cpp index 0d53365..1c389b4 100644 --- a/src/systems/input_system.cpp +++ b/src/systems/input_system.cpp @@ -29,6 +29,20 @@ InputSystem::handle_Event(Game& game, const SDL_Event& e, FrameInput& input) case SDL_EVENT_KEY_DOWN: { SDL_Keycode sym = e.key.key; + if (input.capture_vim) { + const bool ctrl = (e.key.mod & SDL_KMOD_CTRL); + if (ctrl) { + switch (sym) { + case SDLK_W: input.forward = true; break; + case SDLK_A: input.left = true; break; + case SDLK_S: input.backward = true; break; + case SDLK_D: input.right = true; break; + } + } else if (sym == SDLK_RETURN) input.vim_keys.push_back(-1); + else if (sym == SDLK_ESCAPE) input.vim_keys.push_back(-2); + else if (sym == SDLK_BACKSPACE) input.vim_keys.push_back(-3); + break; + } if (input.capture_typing) { const bool ctrl = (e.key.mod & SDL_KMOD_CTRL); if (ctrl) { @@ -42,19 +56,19 @@ InputSystem::handle_Event(Game& game, const SDL_Event& e, FrameInput& input) input.commands.push_back(InputCommand::ConnectPultSubmit); } else if (sym == SDLK_ESCAPE) { input.commands.push_back(InputCommand::ConnectPultExit); - } else if (sym == SDLK_BACKSPACE && !input.text_buffer.empty()) { - input.text_buffer.pop_back(); + } else if (sym == SDLK_BACKSPACE && !input.typing_buffer.empty()) { + input.typing_buffer.pop_back(); } break; } - if (input.text_mode) { + if (input.typing_mode) { if (sym == SDLK_RETURN) - input.commands.push_back(InputCommand::TextCommit); + input.commands.push_back(InputCommand::TypingCommit); else if (sym == SDLK_ESCAPE) - input.commands.push_back(InputCommand::TextCancel); - else if (sym == SDLK_BACKSPACE && !input.text_buffer.empty()) - input.text_buffer.pop_back(); + input.commands.push_back(InputCommand::TypingCancel); + else if (sym == SDLK_BACKSPACE && !input.typing_buffer.empty()) + input.typing_buffer.pop_back(); break; // swallow everything else — no movement while typing } @@ -74,7 +88,7 @@ InputSystem::handle_Event(Game& game, const SDL_Event& e, FrameInput& input) case SDLK_C: input.commands.push_back(InputCommand::Compile); break; case SDLK_R: input.commands.push_back(InputCommand::Run); break; case SDLK_P: input.commands.push_back(InputCommand::DebugPrint); break; - case SDLK_T: input.commands.push_back(InputCommand::TextStart); break; + case SDLK_T: input.commands.push_back(InputCommand::TypingStart); break; case SDLK_LSHIFT: case SDLK_RSHIFT: input.commands.push_back(InputCommand::FreelookStart); break; case SDLK_ESCAPE: game.change_State(cpputils::make_unique<MenuState>()); break; @@ -103,15 +117,20 @@ InputSystem::handle_Event(Game& game, const SDL_Event& e, FrameInput& input) case SDLK_LSHIFT: case SDLK_RSHIFT: - if (!input.capture_typing) input.commands.push_back(InputCommand::FreelookEnd); + if (!input.capture_typing && !input.capture_vim) + input.commands.push_back(InputCommand::FreelookEnd); break; } break; } case SDL_EVENT_TEXT_INPUT: - if (input.capture_typing || input.text_mode) - input.text_buffer += e.text.text; + if (input.capture_vim) { + for (const char* p = e.text.text; *p; ++p) + input.vim_keys.push_back((unsigned char)*p); + } else if (input.capture_typing || input.typing_mode) { + input.typing_buffer += e.text.text; + } break; } } diff --git a/src/systems/input_system.hpp b/src/systems/input_system.hpp index c819ca5..ba551cd 100644 --- a/src/systems/input_system.hpp +++ b/src/systems/input_system.hpp @@ -17,7 +17,7 @@ enum class InputCommand : uint8_t { DebugPrint, FreelookStart, FreelookEnd, NumberInc, NumberDec, - TextStart, TextCommit, TextCancel, + TypingStart, TypingCommit, TypingCancel, SandboxRestore, ConnectPultSubmit, ConnectPultExit, @@ -32,16 +32,17 @@ struct FrameInput { bool pick = false; bool shoot = false; bool capture_typing = false; + bool capture_vim = false; + bool typing_mode = false; + bool typing_commit = false; + bool typing_cancel = false; int look_x = 0; int look_y = 0; - bool text_mode = false; // currently typing - std::string text_buffer; // accumulated text - bool text_commit = false; // Enter pressed this frame - bool text_cancel = false; // Esc pressed this frame - + std::string typing_buffer; + DynArray<int> vim_keys; DynArray<InputCommand> commands; }; diff --git a/src/systems/physics_system.cpp b/src/systems/physics_system.cpp index 6457086..5f7d286 100644 --- a/src/systems/physics_system.cpp +++ b/src/systems/physics_system.cpp @@ -1,6 +1,7 @@ #include <SDL3/SDL.h> #include <algorithm> +#include "entities/world.hpp" #include "systems/collision.hpp" #include "systems/physics_system.hpp" #include "systems/projectile_system.hpp" @@ -191,7 +192,7 @@ PhysicsSystem::try_Pair(GameObject* a, GameObject* b) void PhysicsSystem::update_Objects(const DynArray<GameObject*>& objects, - double dt, double gravity, float world_scale) + double dt, double gravity) { // Integration pass { FP_ZONE("phys.integrate"); @@ -222,7 +223,7 @@ PhysicsSystem::update_Objects(const DynArray<GameObject*>& objects, { FP_ZONE("phys.bounds"); for (GameObject* obj : objects) { if (!obj || obj->body.mode != PhysicsMode::Dynamic) continue; - resolve_WorldBounds(*obj, world_scale); + resolve_WorldBounds(*obj); } } @@ -232,7 +233,7 @@ PhysicsSystem::update_Objects(const DynArray<GameObject*>& objects, // which is the bulk of the old O(n²) work. { FP_ZONE("phys.pairs"); // Build grid over ALL objects (passive included — movers must find them). - _grid.build(objects, static_cast<double>(world_scale)); + _grid.build(objects); // Only active bodies initiate collision tests. DynArray<GameObject*> active; @@ -250,9 +251,9 @@ PhysicsSystem::update_Objects(const DynArray<GameObject*>& objects, void -PhysicsSystem::resolve_WorldBounds(GameObject& obj, float world_scale) +PhysicsSystem::resolve_WorldBounds(GameObject& obj) { - const double ws = static_cast<double>(world_scale); + const double ws = World::get_BoxScale<double>(); Vec3& he = obj.shape.half_extents; // Floor @@ -491,13 +492,13 @@ PhysicsSystem::resolve_Projectiles(DynArray<Projectile>& projectiles, void -PhysicsSystem::SpatialGrid::build(const DynArray<GameObject*>& objs, double world_scale) +PhysicsSystem::SpatialGrid::build(const DynArray<GameObject*>& objs) { // Cell size ~ a couple world units; tune if objects are much larger/smaller. cell = 2.0; inv = 1.0 / cell; - min_x = min_z = -world_scale; - const double span = world_scale * 2.0; + min_x = min_z = -World::get_BoxScale<double>(); + const double span = World::get_BoxScale<double>() * 2.0; nx = nz = static_cast<int>(span * inv) + 1; const int total = nx * nz; diff --git a/src/systems/physics_system.hpp b/src/systems/physics_system.hpp index 5ec9b97..8bd2c07 100644 --- a/src/systems/physics_system.hpp +++ b/src/systems/physics_system.hpp @@ -25,14 +25,14 @@ class PhysicsSystem { double min_x = 0, min_z = 0; DynArray<DynArray<GameObject*>> cells; - void build(const DynArray<GameObject*>& objs, double world_scale); + void build(const DynArray<GameObject*>& objs); int index(int cx, int cz) const { return cz * nx + cx; } }; SpatialGrid _grid; static void - resolve_WorldBounds(GameObject& obj, float world_scale); + resolve_WorldBounds(GameObject& obj); bool resolve_SingleBoxCollision(Projectile& proj, const Vec3& box_min, const Vec3& box_max, const Vec3& word_he, double restitution, @@ -44,7 +44,7 @@ class PhysicsSystem { public: void update_Player(Player& player, double acc_x, double acc_z, double dt); void update_Objects(const DynArray<GameObject*>& objects, - double dt, double gravity, float world_scale); + double dt, double gravity); void update_Projectiles(DynArray<Projectile>& projectiles, double dt, const DynArray<Text3dPtr>& words); diff --git a/src/systems/projectile_system.cpp b/src/systems/projectile_system.cpp index b753f10..fb73e57 100644 --- a/src/systems/projectile_system.cpp +++ b/src/systems/projectile_system.cpp @@ -459,10 +459,9 @@ void ProjectileSystem::update(DynArray<Projectile>& projectiles, DynArray<ProjectileResult>& results, const DynArray<WallTextHitTest>& wall_text_hits, - double dt, float world_scale, - bool program_running) + double dt, bool program_running) { - const double ws = static_cast<double>(world_scale); + const double ws = World::get_BoxScale<double>(); // ── 1. Advance stick animations ────────────────────────────────────────── for (Projectile& p : projectiles) diff --git a/src/systems/projectile_system.hpp b/src/systems/projectile_system.hpp index 473af94..45963f1 100644 --- a/src/systems/projectile_system.hpp +++ b/src/systems/projectile_system.hpp @@ -74,6 +74,5 @@ public: void update(DynArray<Projectile>& projectiles, DynArray<ProjectileResult>& results, const DynArray<WallTextHitTest>& wall_text_hits, - double dt, float world_scale, - bool program_running); + double dt, bool program_running); }; diff --git a/src/systems/pult_system.cpp b/src/systems/pult_system.cpp index 7ab1f9a..f7041c9 100644 --- a/src/systems/pult_system.cpp +++ b/src/systems/pult_system.cpp @@ -75,6 +75,10 @@ PultSystem::init(DynArray<GameObjectPtr>& scenery, Pult* connp = spawn_pult("Connect", Wall::Role::Program, lane_3); add_btn(connp, "CONN", PultAction::ConnectProgram, 0.0f, 0.0f); _connect_program = connp; + + Pult* conne = spawn_pult("Edit", Wall::Role::Editor, lane_3); + add_btn(conne, "EDIT", PultAction::ConnectEditor, 0.0f, 0.0f); + _connect_editor = conne; } @@ -182,3 +186,9 @@ PultSystem::get_ConnectProgramPos() const { return _connect_program ? _connect_program->pos : Vec3{}; } + +Vec3 +PultSystem::get_ConnectEditorPos() const +{ + return _connect_editor ? _connect_editor->pos : Vec3{}; +} diff --git a/src/systems/pult_system.hpp b/src/systems/pult_system.hpp index a1a5819..c899c71 100644 --- a/src/systems/pult_system.hpp +++ b/src/systems/pult_system.hpp @@ -17,6 +17,7 @@ class PultSystem { DynArray<Pult*> _pults; Pult* _connect_program { nullptr }; + Pult* _connect_editor { nullptr }; public: void init (DynArray<GameObjectPtr>& scenery, DynArray<PultButton*>& buttons_out, @@ -31,6 +32,7 @@ public: handle_PlayerContact (const Player& player, const DynArray<PultButton*>& buttons); Vec3 get_ConnectProgramPos () const; + Vec3 get_ConnectEditorPos () const; DynArray<TextLabel*> get_Labels (); diff --git a/src/systems/word_anim.cpp b/src/systems/word_anim.cpp index a58d0d2..07ddd8f 100644 --- a/src/systems/word_anim.cpp +++ b/src/systems/word_anim.cpp @@ -1,5 +1,6 @@ #include <algorithm> +#include "entities/world.hpp" #include "word_anim.hpp" #include "utils/config.hpp" @@ -18,9 +19,9 @@ WordAnimSystem::update(DynArray<WordAnim_Add>& anims_add, DynArray<WordAnim_Drop>& anims_drop, CarriedLine& carried, DynArray<Text3dPtr>& words, - double dt, - double floor_y) + double dt) { + double floor_y = World::get_FloorY<double>(); // Add animations for (WordAnim_Add& anim : anims_add) { if (anim.complete) continue; diff --git a/src/systems/word_anim.hpp b/src/systems/word_anim.hpp index ac53435..c3242b1 100644 --- a/src/systems/word_anim.hpp +++ b/src/systems/word_anim.hpp @@ -27,5 +27,5 @@ public: DynArray<WordAnim_Drop>& anims_drop, CarriedLine& carried, DynArray<Text3dPtr>& words, - double dt, double floor_y); + double dt); }; diff --git a/src/text_3d/vim_buffer.hpp b/src/text_3d/vim_buffer.hpp new file mode 100644 index 0000000..0b292c8 --- /dev/null +++ b/src/text_3d/vim_buffer.hpp @@ -0,0 +1,174 @@ +#pragma once +// Dependency-free vim editing model: lines of text, a cursor, modes, and the +// agreed motion/edit subset. No rendering, no game types — GameState drives it +// and reads `lines()`/`row()`/`col()` to render. `version()` bumps on any change +// so the caller knows when to re-render. Signals (Exit/Help/Write) surface +// commands the host must act on. + +#include <string> +#include <vector> +#include <cctype> + +class VimBuffer { +public: + enum class Mode : uint8_t { Normal, Insert, Command }; + enum class Signal : uint8_t { None, Exit, Help, Write }; + + void load(const std::vector<std::string>& src) { + _lines = src; + if (_lines.empty()) _lines.push_back(""); + _row = _col = 0; _mode = Mode::Normal; + _count = 0; _pending.clear(); _cmd.clear(); _status.clear(); + _sig = Signal::None; ++_version; + } + + // ── feed (call in input order) ────────────────────────────────────────── + void key_char(char c) { + if (_mode == Mode::Insert) { insert_Char(c); } + else if (_mode == Mode::Command) { _cmd.push_back(c); touch(); } + else { normal_Key(c); } + } + void key_enter() { + if (_mode == Mode::Insert) { split_Line(); } + else if (_mode == Mode::Command) { run_Command(); } + else { _row = clampRow(_row + 1); clampCol(); touch(); } + } + void key_backspace() { + if (_mode == Mode::Insert) { backspace_Insert(); } + else if (_mode == Mode::Command) { if (_cmd.size() > 1) _cmd.pop_back(); else cancel_Command(); touch(); } + else { _col = (_col > 0 ? _col - 1 : 0); touch(); } + } + void key_escape() { + if (_mode == Mode::Command) { cancel_Command(); return; } + _mode = Mode::Normal; + if (_col > 0) --_col; + clampCol(); touch(); + } + + // ── host accessors ────────────────────────────────────────────────────── + const std::vector<std::string>& lines() const { return _lines; } + int row() const { return _row; } + int col() const { return _col; } + Mode mode() const { return _mode; } + unsigned version() const { return _version; } + bool is_Empty() const { return _lines.size() == 1 && _lines[0].empty(); } + void start_Insert() { _mode = Mode::Insert; _col = 0; touch(); } + const std::string& cmdline() const { return _cmd; } // includes leading ':' + const std::string& status() const { return _status; } + Signal take_Signal() { Signal s = _sig; _sig = Signal::None; return s; } + +private: + std::vector<std::string> _lines { "" }; + int _row = 0, _col = 0; + Mode _mode = Mode::Normal; + int _count = 0; + std::string _pending; // "d" or "g" awaiting second key + std::string _cmd; // command-line text (":...") + std::string _status; + Signal _sig = Signal::None; + unsigned _version = 0; + + void touch() { ++_version; } + int lineLen() const { return (int)_lines[(size_t)_row].size(); } + int clampRow(int r) const { int n=(int)_lines.size(); return r<0?0:(r>=n?n-1:r); } + void clampCol() { + int maxc = (_mode==Mode::Insert) ? lineLen() : (lineLen()>0 ? lineLen()-1 : 0); + if (_col < 0) _col = 0; + if (_col > maxc) _col = maxc; + } + int eff() const { return _count > 0 ? _count : 1; } + + // ── Insert-mode edits ──────────────────────────────────────────────────── + void insert_Char(char c) { + std::string& L = _lines[(size_t)_row]; + if (_col > (int)L.size()) _col = (int)L.size(); + L.insert((size_t)_col, 1, c); ++_col; touch(); + } + void split_Line() { + std::string& L = _lines[(size_t)_row]; + std::string tail = L.substr((size_t)_col); + L.erase((size_t)_col); + _lines.insert(_lines.begin() + _row + 1, tail); + ++_row; _col = 0; touch(); + } + void backspace_Insert() { + if (_col > 0) { _lines[(size_t)_row].erase((size_t)(_col-1), 1); --_col; } + else if (_row > 0) { + int prevlen = (int)_lines[(size_t)(_row-1)].size(); + _lines[(size_t)(_row-1)] += _lines[(size_t)_row]; + _lines.erase(_lines.begin() + _row); + --_row; _col = prevlen; + } + touch(); + } + + // ── Normal-mode interpreter ─────────────────────────────────────────────── + void normal_Key(char c) { + if (c == ':') { _mode = Mode::Command; _cmd = ":"; _status.clear(); touch(); return; } + + if (std::isdigit((unsigned char)c)) { + if (c == '0' && _count == 0 && _pending.empty()) { _col = 0; touch(); return; } // 0 = motion + _count = _count * 10 + (c - '0'); return; + } + + if (_pending == "d") { _pending.clear(); + if (c == 'd') { delete_Lines(eff()); _count = 0; touch(); return; } + _count = 0; return; + } + if (_pending == "g") { _pending.clear(); + if (c == 'g') { _row = clampRow(_count > 0 ? _count - 1 : 0); _count = 0; clampCol(); touch(); return; } + _count = 0; return; + } + + switch (c) { + case 'h': _col -= eff(); break; + case 'l': _col += eff(); break; + case 'j': _row = clampRow(_row + eff()); break; + case 'k': _row = clampRow(_row - eff()); break; + case '0': _col = 0; break; + case '$': _col = lineLen() > 0 ? lineLen() - 1 : 0; break; + case 'x': delete_Chars(eff()); break; + case 'i': _mode = Mode::Insert; break; + case 'a': _col = (lineLen() > 0 ? _col + 1 : 0); _mode = Mode::Insert; break; + case 'A': _col = lineLen(); _mode = Mode::Insert; break; + case 'o': open_Below(); _mode = Mode::Insert; break; + case 'O': open_Above(); _mode = Mode::Insert; break; + case 'G': _row = clampRow(_count > 0 ? _count - 1 : (int)_lines.size() - 1); break; + case 'd': _pending = "d"; return; + case 'g': _pending = "g"; return; + default: break; + } + _count = 0; clampCol(); touch(); + } + + void delete_Chars(int n) { + std::string& L = _lines[(size_t)_row]; + for (int i = 0; i < n && _col < (int)L.size(); ++i) L.erase((size_t)_col, 1); + clampCol(); + } + void delete_Lines(int n) { + for (int i = 0; i < n && (int)_lines.size() > 0; ++i) { + if (_lines.size() == 1) { _lines[0].clear(); break; } + _lines.erase(_lines.begin() + _row); + if (_row >= (int)_lines.size()) _row = (int)_lines.size() - 1; + } + clampCol(); + } + void open_Below() { _lines.insert(_lines.begin() + _row + 1, ""); ++_row; _col = 0; } + void open_Above() { _lines.insert(_lines.begin() + _row, ""); _col = 0; } + + // ── command line ────────────────────────────────────────────────────────── + void run_Command() { + std::string c = _cmd.size() ? _cmd.substr(1) : ""; // strip ':' + _cmd.clear(); _mode = Mode::Normal; + if (c == "exit" || c == "q" || c == "q!") _sig = Signal::Exit; + else if (c == "help") _sig = Signal::Help; + else if (c == "w" || c == "write") _sig = Signal::Write; + else if (!c.empty() && std::all_of(c.begin(), c.end(), + [](char ch){ return std::isdigit((unsigned char)ch); })) { + _row = clampRow(std::stoi(c) - 1); clampCol(); + } else _status = "not a command: " + c; + touch(); + } + void cancel_Command() { _cmd.clear(); _mode = Mode::Normal; touch(); } +}; diff --git a/src/utils/ascii.hpp b/src/utils/ascii.hpp index bc5a66a..86c4bdc 100644 --- a/src/utils/ascii.hpp +++ b/src/utils/ascii.hpp @@ -7,7 +7,7 @@ // gcc diagnostics use Unicode directional quotes (U+2018/2019 ‘ ’, U+201C/201D // “ ”) which the text renderer can't draw. This maps the common ones to plain // ' and ", and replaces any other multibyte sequence with '?'. Used by both -// CompilerSystem and ModLoader so their wall output stays renderable. +// CompilerSystem and DynLoaderSystem so their wall output stays renderable. // // Note: also set LC_ALL=C on the gcc invocation to make gcc emit ASCII quotes // at the source; this function is the belt-and-suspenders fallback. diff --git a/src/wall/text_wall.cpp b/src/wall/text_wall.cpp index 2131075..afbf9f7 100644 --- a/src/wall/text_wall.cpp +++ b/src/wall/text_wall.cpp @@ -1,24 +1,25 @@ +#include "entities/world.hpp" #include "text_wall.hpp" #include "utils/config.hpp" TextWallPtr -TextWall::from_Drop(Text3dPtr src, float world_scale) +TextWall::from_Drop(Text3dPtr src) { - float x = static_cast<float>(src->pos.x); - float z = static_cast<float>(src->pos.z); + double ws = World::get_BoxScale<double>(); + double x = src->pos.x; + double z = src->pos.z; Vec3& he = src->shape.half_extents; - float dPX = world_scale - x; - float dNX = world_scale + x; - float dPZ = world_scale - z; - float dNZ = world_scale + z; + double dPX = ws - x; + double dNX = ws + x; + double dPZ = ws - z; + double dNZ = ws + z; Wall::Position wall_pos; float yaw; - double ws = static_cast<double>(world_scale); // he.x is the text WIDTH half-extent, he.z is the text DEPTH (thickness). // After yaw rotation, the depth axis becomes the wall-normal axis. @@ -128,8 +129,7 @@ TextWall::draw(GlRenderer& g, const Camera& c, const Light& l) const void TextWall::show_TokenInfo(TextWallList& texts, - const std::string& token_text, - float world_scale) + const std::string& token_text) { const Token* tok = TokenDB::find_OrSynthesize(token_text); @@ -141,7 +141,7 @@ TextWall::show_TokenInfo(TextWallList& texts, if (!tok) return; - Wall::Transform tf = Wall::get_Transform(Wall::Role::Info, world_scale); + Wall::Transform tf = Wall::get_Transform(Wall::Role::Info, World::get_BoxScale()); // Line 1: token text + type std::string line1 = tok->text + " [" + TokenDB::get_TypeName(tok->type) + "]"; diff --git a/src/wall/text_wall.hpp b/src/wall/text_wall.hpp index 6aa0cf8..2f84c38 100644 --- a/src/wall/text_wall.hpp +++ b/src/wall/text_wall.hpp @@ -14,7 +14,7 @@ struct TextWall Text3dPtr text3d; static TextWallPtr - from_Drop(Text3dPtr src, float world_scale); + from_Drop(Text3dPtr src); static std::string extract_Text(const TextWallList&, Wall::Role, double line_threshold = 0.3); @@ -24,6 +24,5 @@ struct TextWall static void show_TokenInfo(TextWallList& texts, - const std::string& token_text, - float world_scale); + const std::string& token_text); };