From 6d603f3064a7bd3e5bb13379c443de655fcbcaae Mon Sep 17 00:00:00 2001 From: CDevv Date: Wed, 18 Mar 2026 19:07:46 +0200 Subject: [PATCH 01/35] Lua API: UI and SoundsService Signed-off-by: CDevv --- include/game.hpp | 3 +++ include/lua/interfaceApi.hpp | 10 ++++++++ include/lua/soundsApi.hpp | 11 +++++++++ include/scriptService.hpp | 20 ++++++++++++++++ resources/interactables/dialogue.json | 2 +- resources/scripts/dialogue.lua | 5 ++++ resources/scripts/test.lua | 4 ---- src/game.cpp | 18 +++++--------- src/interactable.cpp | 21 +++++------------ src/lua/interfaceApi.cpp | 17 ++++++++++++++ src/lua/soundsApi.cpp | 19 +++++++++++++++ src/scriptService.cpp | 34 +++++++++++++++++++++++++++ src/soundService.cpp | 6 ++--- xmake.lua | 4 ++-- 14 files changed, 137 insertions(+), 37 deletions(-) create mode 100644 include/lua/interfaceApi.hpp create mode 100644 include/lua/soundsApi.hpp create mode 100644 include/scriptService.hpp create mode 100644 resources/scripts/dialogue.lua delete mode 100644 resources/scripts/test.lua create mode 100644 src/lua/interfaceApi.cpp create mode 100644 src/lua/soundsApi.cpp create mode 100644 src/scriptService.cpp diff --git a/include/game.hpp b/include/game.hpp index 339fcff..cac7081 100644 --- a/include/game.hpp +++ b/include/game.hpp @@ -1,6 +1,7 @@ #ifndef _RPGPP_GAME_H #define _RPGPP_GAME_H +#include "scriptService.hpp" #include "sol/state_view.hpp" class WorldService; @@ -25,6 +26,7 @@ class Game { static std::unique_ptr ui; static std::unique_ptr resources; static std::unique_ptr sounds; + static std::unique_ptr scripts; public: Game(); @@ -36,6 +38,7 @@ class Game { static InterfaceService &getUi(); static ResourceService &getResources(); static SoundService &getSounds(); + static ScriptService &getScripts(); static void init(); diff --git a/include/lua/interfaceApi.hpp b/include/lua/interfaceApi.hpp new file mode 100644 index 0000000..6fe5213 --- /dev/null +++ b/include/lua/interfaceApi.hpp @@ -0,0 +1,10 @@ +#ifndef _RPGPP_LUA_INTERFACEAPI_H +#define _RPGPP_LUA_INTERFACEAPI_H + +#include "sol/state_view.hpp" +#include + +void lua_ui_opendiag(const std::string &id); +void lua_ui_set(sol::state_view &lua); + +#endif \ No newline at end of file diff --git a/include/lua/soundsApi.hpp b/include/lua/soundsApi.hpp new file mode 100644 index 0000000..eafd4b5 --- /dev/null +++ b/include/lua/soundsApi.hpp @@ -0,0 +1,11 @@ +#ifndef _RPGPP_LUA_SOUNDSAPI_H +#define _RPGPP_LUA_SOUNDSAPI_H + +#include "sol/state_view.hpp" +#include + +void lua_sounds_loadMusic(const std::string &id); +void lua_sounds_playMusic(); +void lua_sounds_set(sol::state_view &lua); + +#endif \ No newline at end of file diff --git a/include/scriptService.hpp b/include/scriptService.hpp new file mode 100644 index 0000000..d1377e9 --- /dev/null +++ b/include/scriptService.hpp @@ -0,0 +1,20 @@ +#ifndef _RPGPP_SCRIPTSERVICE_H +#define _RPGPP_SCRIPTSERVICE_H + +#include "sol/state.hpp" +#include "sol/state_view.hpp" +#include +#include + +class ScriptService { + private: + sol::state state; + + public: + ScriptService(); + sol::state &getState(); + void setLua(sol::state_view lua); + void addToState(nlohmann::json &j); +}; + +#endif \ No newline at end of file diff --git a/resources/interactables/dialogue.json b/resources/interactables/dialogue.json index c3c6f13..b2cabeb 100644 --- a/resources/interactables/dialogue.json +++ b/resources/interactables/dialogue.json @@ -1,6 +1,6 @@ { "name": "Diag", - "script": "test.lua", + "script": "dialogue.lua", "onTouch": false, "props": { "dialogue": { diff --git a/resources/scripts/dialogue.lua b/resources/scripts/dialogue.lua new file mode 100644 index 0000000..720ffb3 --- /dev/null +++ b/resources/scripts/dialogue.lua @@ -0,0 +1,5 @@ +function interact() +Interface.OpenDialogue(props.dialogue) +Sounds.LoadMusic("PenumbraPhantasm") +Sounds.PlayMusic() +end diff --git a/resources/scripts/test.lua b/resources/scripts/test.lua deleted file mode 100644 index 2e9f11a..0000000 --- a/resources/scripts/test.lua +++ /dev/null @@ -1,4 +0,0 @@ -function interact() -print(dialogue) -opendiag(dialogue) -end diff --git a/src/game.cpp b/src/game.cpp index e49526c..01ff827 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1,5 +1,6 @@ #include "game.hpp" #include "gamedata.hpp" +#include "scriptService.hpp" #include "soundService.hpp" #include #include @@ -18,6 +19,7 @@ std::unique_ptr Game::ui = std::unique_ptr Game::resources = std::unique_ptr{}; std::unique_ptr Game::sounds = std::unique_ptr{}; +std::unique_ptr Game::scripts = std::unique_ptr{}; Game::Game() { if (instance_ == nullptr) { @@ -43,6 +45,7 @@ void Game::init() { world = std::make_unique(); ui = std::make_unique(); sounds = std::make_unique(); + scripts = std::make_unique(); } void Game::useBin(const std::string &filePath) { @@ -74,6 +77,8 @@ InterfaceService &Game::getUi() { return *ui; } SoundService &Game::getSounds() { return *sounds; } +ScriptService &Game::getScripts() { return *scripts; } + void Game::update() { sounds->update(); world->update(); @@ -91,15 +96,4 @@ void Game::unload() { ui->unload(); } -void lua_opendiag(const std::string &id) { - if (Game::getBin().dialogues.count(id) > 0) { - auto diag = Game::getBin().dialogues[id]; - Game::getUi().showDialogue(diag); - } else { - fprintf(stderr, "This dialogue does not exist: %s \n", id.c_str()); - } -} - -void Game::setLua(sol::state_view lua) { - lua.set_function("opendiag", &lua_opendiag); -} +void Game::setLua(sol::state_view lua) { scripts->setLua(lua); } diff --git a/src/interactable.cpp b/src/interactable.cpp index 25ce1b1..29187a2 100644 --- a/src/interactable.cpp +++ b/src/interactable.cpp @@ -109,30 +109,21 @@ void Interactable::setDisplayTitle(const std::string &newTitle) { std::string &Interactable::getDisplayTitle() { return displayTitle; } void Interactable::interact() { - sol::state lua; - lua.open_libraries(sol::lib::base); - for (auto prop : props->items()) { - if (prop.value().is_object()) { - lua[prop.key()] = prop.value().at("value").get(); - } else if (prop.value().is_string()) { - lua[prop.key()] = prop.value().get(); - } else if (prop.value().is_number()) { - lua[prop.key()] = prop.value().get(); - } - } - Game::setLua(lua); + auto &state = Game::getScripts().getState(); + + Game::getScripts().addToState(*props); auto intBin = Game::getBin().interactables.at(type); if (Game::getBin().scripts.count(intBin.scriptPath) != 0) { auto bc = Game::getBin().scripts[intBin.scriptPath].bytecode; - auto result = lua.safe_script(bc); + auto result = state.safe_script(bc); if (!result.valid()) { printf("uh oh. \n"); return; } - if (lua["interact"].valid()) { - lua["interact"].call(); + if (state["interact"].valid()) { + state["interact"].call(); } } } diff --git a/src/lua/interfaceApi.cpp b/src/lua/interfaceApi.cpp new file mode 100644 index 0000000..b5f8b80 --- /dev/null +++ b/src/lua/interfaceApi.cpp @@ -0,0 +1,17 @@ +#include "lua/interfaceApi.hpp" +#include "game.hpp" +#include "sol/table.hpp" + +void lua_opendiag(const std::string &id) { + if (Game::getBin().dialogues.count(id) > 0) { + auto diag = Game::getBin().dialogues[id]; + Game::getUi().showDialogue(diag); + } else { + fprintf(stderr, "This dialogue does not exist: %s \n", id.c_str()); + } +} + +void lua_ui_set(sol::state_view &lua) { + auto space = lua["Interface"].get_or_create(); + space.set_function("OpenDialogue", lua_opendiag); +} diff --git a/src/lua/soundsApi.cpp b/src/lua/soundsApi.cpp new file mode 100644 index 0000000..d15c7d7 --- /dev/null +++ b/src/lua/soundsApi.cpp @@ -0,0 +1,19 @@ +#include "lua/soundsApi.hpp" +#include "game.hpp" +#include "soundService.hpp" +#include + +void lua_sounds_loadMusic(const std::string &id) { + Game::getSounds().loadMusic(id); +} + +void lua_sounds_playMusic() { Game::getSounds().playMusic(); } + +void lua_sounds_set(sol::state_view &lua) { + printf("setting sounds api.. \n"); + auto space = lua["Sounds"].get_or_create(); + space.set_function("PlaySound", &SoundService::playSound, + Game::getSounds()); + space.set_function("LoadMusic", lua_sounds_loadMusic); + space.set_function("PlayMusic", lua_sounds_playMusic); +} \ No newline at end of file diff --git a/src/scriptService.cpp b/src/scriptService.cpp new file mode 100644 index 0000000..6264363 --- /dev/null +++ b/src/scriptService.cpp @@ -0,0 +1,34 @@ +#include "scriptService.hpp" +#include "lua/soundsApi.hpp" +#include "sol/state_view.hpp" + +#include "lua/interfaceApi.hpp" +#include "sol/table.hpp" +#include + +ScriptService::ScriptService() { + state.open_libraries(sol::lib::base); + setLua(state); +} + +void ScriptService::setLua(sol::state_view lua) { + lua_ui_set(lua); + lua_sounds_set(lua); +} + +sol::state &ScriptService::getState() { return state; } + +void ScriptService::addToState(nlohmann::json &j) { + auto props = state["props"].get_or_create(); + props.clear(); + + for (auto prop : j.items()) { + if (prop.value().is_object()) { + props[prop.key()] = prop.value().at("value").get(); + } else if (prop.value().is_string()) { + props[prop.key()] = prop.value().get(); + } else if (prop.value().is_number()) { + props[prop.key()] = prop.value().get(); + } + } +} \ No newline at end of file diff --git a/src/soundService.cpp b/src/soundService.cpp index 509e6ab..55ca5e7 100644 --- a/src/soundService.cpp +++ b/src/soundService.cpp @@ -15,9 +15,6 @@ bool SoundService::loadMusic(const std::string &id) { if (lastId == id) return true; - printf("%s \n", id.c_str()); - printf("%zu", gameData.music.count(id)); - if (gameData.music.count(id) != 0) { if (gameData.music[id].isSound) return musicLoaded; @@ -36,6 +33,8 @@ bool SoundService::loadMusic(const std::string &id) { this->music = newMusic; musicLoaded = true; } + } else { + printf("Music with such id does not exist: %s \n", id.c_str()); } return musicLoaded; @@ -67,6 +66,7 @@ void SoundService::update() const { void SoundService::unload() const { if (musicLoaded) { + printf("unloaded.. \n"); StopMusicStream(music); UnloadMusicStream(music); } diff --git a/xmake.lua b/xmake.lua index 0eb63d0..10f51af 100644 --- a/xmake.lua +++ b/xmake.lua @@ -77,8 +77,8 @@ target("rpgpp") set_kind("static") add_packages("raylib", "nlohmann_json", "luajit") set_languages("cxx17") -add_includedirs("include/") --, "libs/raylib/src") -add_files("src/*.cpp") +add_includedirs("include/", "include/lua/") +add_files("src/*.cpp", "src/lua/*.cpp") if is_plat("linux") then add_cxxflags("-fPIC") end From dc10850ef0f62cb63e3d522ec169002b786615e3 Mon Sep 17 00:00:00 2001 From: CDevv Date: Wed, 18 Mar 2026 19:12:34 +0200 Subject: [PATCH 02/35] PlaySound function for Sounds Signed-off-by: CDevv --- include/lua/soundsApi.hpp | 1 + src/lua/soundsApi.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/include/lua/soundsApi.hpp b/include/lua/soundsApi.hpp index eafd4b5..3127be9 100644 --- a/include/lua/soundsApi.hpp +++ b/include/lua/soundsApi.hpp @@ -6,6 +6,7 @@ void lua_sounds_loadMusic(const std::string &id); void lua_sounds_playMusic(); +void lua_sounds_playSound(const std::string &id); void lua_sounds_set(sol::state_view &lua); #endif \ No newline at end of file diff --git a/src/lua/soundsApi.cpp b/src/lua/soundsApi.cpp index d15c7d7..e9993f5 100644 --- a/src/lua/soundsApi.cpp +++ b/src/lua/soundsApi.cpp @@ -9,6 +9,10 @@ void lua_sounds_loadMusic(const std::string &id) { void lua_sounds_playMusic() { Game::getSounds().playMusic(); } +void lua_sounds_playSound(const std::string &id) { + Game::getSounds().playSound(id); +} + void lua_sounds_set(sol::state_view &lua) { printf("setting sounds api.. \n"); auto space = lua["Sounds"].get_or_create(); From 50ec6a5263ab874e571c6d37f7ae994d225d2098 Mon Sep 17 00:00:00 2001 From: CDevv Date: Thu, 19 Mar 2026 18:28:06 +0200 Subject: [PATCH 03/35] Lua API exceptions, StateService API and Vector2 type, Sounds exporting Signed-off-by: CDevv --- include/interactable.hpp | 11 ----------- include/interfaceService.hpp | 2 ++ include/lua/apiTypes.hpp | 21 +++++++++++++++++++++ include/lua/stateApi.hpp | 10 ++++++++++ include/stateService.hpp | 2 ++ resources/scripts/dialogue.lua | 15 +++++++++++++-- src/dialogueBalloon.cpp | 4 ++-- src/editor/main.cpp | 2 ++ src/editor/project.cpp | 17 ++++++++++++++++- src/editor/views/roomView.cpp | 5 +++-- src/interactable.cpp | 28 +++++++++++++++++----------- src/interfaceService.cpp | 13 ++++++++++++- src/lua/apiTypes.cpp | 9 +++++++++ src/lua/interfaceApi.cpp | 9 +-------- src/lua/stateApi.cpp | 18 ++++++++++++++++++ src/scriptService.cpp | 27 +++++++++++++++++++++++++-- src/soundService.cpp | 18 ++++++++++++++---- src/stateService.cpp | 9 +++++++++ xmake.lua | 1 + 19 files changed, 177 insertions(+), 44 deletions(-) create mode 100644 include/lua/apiTypes.hpp create mode 100644 include/lua/stateApi.hpp create mode 100644 src/lua/apiTypes.cpp create mode 100644 src/lua/stateApi.cpp diff --git a/include/interactable.hpp b/include/interactable.hpp index be7f0f1..7fe1ebd 100644 --- a/include/interactable.hpp +++ b/include/interactable.hpp @@ -11,11 +11,6 @@ #include #include -/** Enum for interactable types */ -enum InteractableType { INT_BLANK, INT_TWO, INT_WARPER }; - -#define INTTYPE_MAX (3) - /** Defines an object that is interactable in-game by a player's action */ class Interactable : public ISaveable { private: @@ -40,10 +35,6 @@ class Interactable : public ISaveable { /** Script file path */ std::string scriptPath; - protected: - /** Array for descriptive names of interactable types */ - static std::array interactableTypeNames; - public: /** Empty constructor */ Interactable(); @@ -56,8 +47,6 @@ class Interactable : public ISaveable { virtual ~Interactable() = default; /** Dump JSON data. */ nlohmann::json dumpJson(); - /** Get the array containing names of the Interactable names */ - static std::array &getTypeNames(); /** Whether this Interactable is valid */ bool isValid() const; /** Get the Rectangle of this Interactable */ diff --git a/include/interfaceService.hpp b/include/interfaceService.hpp index 298b724..6e45f61 100644 --- a/include/interfaceService.hpp +++ b/include/interfaceService.hpp @@ -29,6 +29,8 @@ class InterfaceService { Font getFont() const; /** Get the texture, used for UI nine-patch components. */ Texture getTexture() const; + /** Open a dialogue with a certain name */ + void showDialogue(const std::string &id); /** Open the dialogue with a Dialogue structure */ void showDialogue(const DialogueBin &dialogue); /** Update routine. */ diff --git a/include/lua/apiTypes.hpp b/include/lua/apiTypes.hpp new file mode 100644 index 0000000..9c71e50 --- /dev/null +++ b/include/lua/apiTypes.hpp @@ -0,0 +1,21 @@ +#ifndef _RPGPP_LUA_APITYPES_H +#define _RPGPP_LUA_APITYPES_H + +#include "sol/state_view.hpp" +struct lua_Vector2 { + float x, y; + + lua_Vector2() { + x = 0; + y = 0; + } + + lua_Vector2(float x, float y) { + this->x = x; + this->y = y; + } +}; + +void lua_types_set(sol::state_view lua); + +#endif \ No newline at end of file diff --git a/include/lua/stateApi.hpp b/include/lua/stateApi.hpp new file mode 100644 index 0000000..a227a97 --- /dev/null +++ b/include/lua/stateApi.hpp @@ -0,0 +1,10 @@ +#ifndef _RPGPP_LUA_STATEAPI_H +#define _RPGPP_LUA_STATEAPI_H + +#include "sol/state_view.hpp" + +void lua_gamestate_setval(const std::string &prop, bool value); +bool lua_gamestate_getval(const std::string &prop); +void lua_gamestate_set(sol::state_view lua); + +#endif \ No newline at end of file diff --git a/include/stateService.hpp b/include/stateService.hpp index ea04873..32d1709 100644 --- a/include/stateService.hpp +++ b/include/stateService.hpp @@ -14,6 +14,8 @@ class StateService { public: /** Empty constructor */ StateService(); + /** Set a property */ + void setProp(const std::string &prop, bool value); /** Get a boolean property from the container. */ bool getProp(const std::string &prop) const; /** Unload routine. */ diff --git a/resources/scripts/dialogue.lua b/resources/scripts/dialogue.lua index 720ffb3..dc508da 100644 --- a/resources/scripts/dialogue.lua +++ b/resources/scripts/dialogue.lua @@ -1,5 +1,16 @@ function interact() Interface.OpenDialogue(props.dialogue) -Sounds.LoadMusic("PenumbraPhantasm") -Sounds.PlayMusic() +GameState.Set("hi", false) + +print("hi: " .. tostring(GameState.Get("hi"))) +a = 5 / 0 +print(a) +--GameState.Get("nonexisty") +vec = Vector2.new(1, 2) +print(vec.x) +print(vec.y) + +vec2 = Vector2.new() +print(vec2.x) +print(vec2.y) end diff --git a/src/dialogueBalloon.cpp b/src/dialogueBalloon.cpp index ce35d08..83be1db 100644 --- a/src/dialogueBalloon.cpp +++ b/src/dialogueBalloon.cpp @@ -38,7 +38,7 @@ void DialogueBalloon::update() { if (active) { if (firstCharTyped == false) { firstCharTyped = true; - Game::getSounds().playSound("Text 1.wav"); + Game::getSounds().playSound("Text 1"); return; } @@ -79,7 +79,7 @@ void DialogueBalloon::update() { // play sound if (charIndex < text.size()) { if (text.at(charIndex) != ' ') { - Game::getSounds().playSound("Text 1.wav"); + Game::getSounds().playSound("Text 1"); } } } diff --git a/src/editor/main.cpp b/src/editor/main.cpp index f18ea4f..8f69c16 100644 --- a/src/editor/main.cpp +++ b/src/editor/main.cpp @@ -1,6 +1,8 @@ #include "editor.hpp" #include "services/editorGuiService.hpp" +#define SOL_EXCEPTIONS_SAFE_PROPAGATION + int main() { const auto editor = std::make_unique(); auto &gui = editor->getGui(); diff --git a/src/editor/project.cpp b/src/editor/project.cpp index 72792f0..47aef83 100644 --- a/src/editor/project.cpp +++ b/src/editor/project.cpp @@ -324,10 +324,23 @@ GameData Project::generateStruct() { } for (auto soundPath : getPaths(EngineFileType::FILE_SOUND)) { + MusicBin soundBin; + + int dataSize = 0; + auto fileData = LoadFileData(soundPath.c_str(), &dataSize); + + for (int i = 0; i < dataSize; i++) { + soundBin.fileData.push_back(fileData[i]); + } + + soundBin.fileExt = GetFileExtension(soundPath.c_str()); + soundBin.isSound = true; + data.music[GetFileNameWithoutExt(soundPath.c_str())] = soundBin; + + UnloadFileData(fileData); } for (auto musicPath : getPaths(EngineFileType::FILE_MUSIC)) { - // TODO: Unsafe memory handling (no Unload function) MusicBin musicBin; int dataSize = 0; @@ -340,6 +353,8 @@ GameData Project::generateStruct() { musicBin.fileExt = GetFileExtension(musicPath.c_str()); musicBin.isSound = false; data.music[GetFileNameWithoutExt(musicPath.c_str())] = musicBin; + + UnloadFileData(fileData); } for (auto propPath : getPaths(EngineFileType::FILE_PROP)) { diff --git a/src/editor/views/roomView.cpp b/src/editor/views/roomView.cpp index 0052c22..f8b7400 100644 --- a/src/editor/views/roomView.cpp +++ b/src/editor/views/roomView.cpp @@ -403,8 +403,9 @@ void RoomView::handleEditPress(tgui::Vector2f pos) { static_cast(newTile.y)}; data.worldTile = {static_cast(tileMouse.x), static_cast(tileMouse.y)}; - data.interactable = static_cast( - interactableChoose->getSelectedItemIndex() + 1); + data.interactable = + Editor::instance->getProject()->getInteractableNames() + [interactableChoose->getSelectedItemId().toStdString()]; std::unique_ptr act = std::make_unique(data); diff --git a/src/interactable.cpp b/src/interactable.cpp index 29187a2..b206ac8 100644 --- a/src/interactable.cpp +++ b/src/interactable.cpp @@ -3,18 +3,18 @@ #include "game.hpp" #include "gamedata.hpp" #include "interfaceService.hpp" +#include "sol/forward.hpp" #include "sol/state.hpp" +#include "sol/state_handling.hpp" #include "sol/types.hpp" #include "tilemap.hpp" +#include #include #include #include #include #include -std::array Interactable::interactableTypeNames = { - "Blank", "Dialogue", "Warper"}; - Interactable::Interactable() : type(), tilePos(), tileSize(0), absolutePos(), rect() { this->valid = false; @@ -73,10 +73,6 @@ json Interactable::dumpJson() { return j; } -std::array &Interactable::getTypeNames() { - return interactableTypeNames; -} - bool Interactable::isValid() const { return this->valid; } Rectangle Interactable::getRect() const { return this->rect; } @@ -116,14 +112,24 @@ void Interactable::interact() { auto intBin = Game::getBin().interactables.at(type); if (Game::getBin().scripts.count(intBin.scriptPath) != 0) { auto bc = Game::getBin().scripts[intBin.scriptPath].bytecode; - auto result = state.safe_script(bc); + auto result = state.safe_script(bc, &sol::script_pass_on_error); + // auto unsafe_result = state.unsafe_script(bc); + if (!result.valid()) { - printf("uh oh. \n"); - return; + sol::error error = result; + std::cout << error.what() << std::endl; + } + if (result.status() != sol::call_status::ok) { + printf("uh oh: %i \n", result.status()); } if (state["interact"].valid()) { - state["interact"].call(); + sol::protected_function f(state["interact"]); + auto func_result = f(); + if (!func_result.valid()) { + sol::error error = func_result; + std::cout << error.what() << std::endl; + } } } } diff --git a/src/interfaceService.cpp b/src/interfaceService.cpp index 5013514..8de9c8b 100644 --- a/src/interfaceService.cpp +++ b/src/interfaceService.cpp @@ -1,10 +1,11 @@ #include "interfaceService.hpp" #include "colorRect.hpp" +#include "game.hpp" #include "imageRect.hpp" #include "interfaceView.hpp" -#include "resourceService.hpp" #include "textArea.hpp" #include +#include InterfaceService::InterfaceService() { fpsVisible = false; @@ -52,6 +53,16 @@ Font InterfaceService::getFont() const { return font; } Texture InterfaceService::getTexture() const { return uiTexture; } +void InterfaceService::showDialogue(const std::string &id) { + if (Game::getBin().dialogues.count(id) > 0) { + auto diag = Game::getBin().dialogues[id]; + showDialogue(diag); + } else { + throw std::runtime_error( + TextFormat("This Dialogue does not exist: %s", id.c_str())); + } +} + void InterfaceService::showDialogue(const DialogueBin &dialogue) { this->dialogue.showDialogue(dialogue); } diff --git a/src/lua/apiTypes.cpp b/src/lua/apiTypes.cpp new file mode 100644 index 0000000..786b40a --- /dev/null +++ b/src/lua/apiTypes.cpp @@ -0,0 +1,9 @@ +#include "lua/apiTypes.hpp" +#include "sol/raii.hpp" + +void lua_types_set(sol::state_view lua) { + lua.new_usertype( + "Vector2", + sol::constructors(), "x", + &lua_Vector2::x, "y", &lua_Vector2::y); +} \ No newline at end of file diff --git a/src/lua/interfaceApi.cpp b/src/lua/interfaceApi.cpp index b5f8b80..317c1b5 100644 --- a/src/lua/interfaceApi.cpp +++ b/src/lua/interfaceApi.cpp @@ -2,14 +2,7 @@ #include "game.hpp" #include "sol/table.hpp" -void lua_opendiag(const std::string &id) { - if (Game::getBin().dialogues.count(id) > 0) { - auto diag = Game::getBin().dialogues[id]; - Game::getUi().showDialogue(diag); - } else { - fprintf(stderr, "This dialogue does not exist: %s \n", id.c_str()); - } -} +void lua_opendiag(const std::string &id) { Game::getUi().showDialogue(id); } void lua_ui_set(sol::state_view &lua) { auto space = lua["Interface"].get_or_create(); diff --git a/src/lua/stateApi.cpp b/src/lua/stateApi.cpp new file mode 100644 index 0000000..cab1b7d --- /dev/null +++ b/src/lua/stateApi.cpp @@ -0,0 +1,18 @@ +#include "lua/stateApi.hpp" +#include "game.hpp" +#include "sol/table.hpp" + +void lua_gamestate_setval(const std::string &prop, bool value) { + Game::getState().setProp(prop, value); +} + +bool lua_gamestate_getval(const std::string &prop) { + return Game::getState().getProp(prop); +} + +void lua_gamestate_set(sol::state_view lua) { + auto space = lua["GameState"].get_or_create(); + + space.set_function("Set", lua_gamestate_setval); + space.set_function("Get", lua_gamestate_getval); +} \ No newline at end of file diff --git a/src/scriptService.cpp b/src/scriptService.cpp index 6264363..b33ce42 100644 --- a/src/scriptService.cpp +++ b/src/scriptService.cpp @@ -1,19 +1,42 @@ #include "scriptService.hpp" +#include "lua/apiTypes.hpp" +#include "lua/interfaceApi.hpp" #include "lua/soundsApi.hpp" +#include "lua/stateApi.hpp" #include "sol/state_view.hpp" -#include "lua/interfaceApi.hpp" #include "sol/table.hpp" +#include #include +static int wrap_exceptions(lua_State *L, lua_CFunction f) { + try { + return f(L); // Call wrapped function and return result. + } catch (const char *s) { // Catch and convert exceptions. + lua_pushstring(L, s); + } catch (std::exception &e) { + lua_pushstring(L, e.what()); + } catch (...) { + lua_pushliteral(L, "caught (...)"); + } + return lua_error(L); // Rethrow as a Lua error. +} + ScriptService::ScriptService() { - state.open_libraries(sol::lib::base); + state.open_libraries(sol::lib::base, sol::lib::string, sol::lib::os, + sol::lib::table); + lua_pushlightuserdata(state.lua_state(), (void *)wrap_exceptions); + luaJIT_setmode(state.lua_state(), -1, + LUAJIT_MODE_WRAPCFUNC | LUAJIT_MODE_ON); + lua_pop(state.lua_state(), 1); setLua(state); } void ScriptService::setLua(sol::state_view lua) { + lua_types_set(lua); lua_ui_set(lua); lua_sounds_set(lua); + lua_gamestate_set(lua); } sol::state &ScriptService::getState() { return state; } diff --git a/src/soundService.cpp b/src/soundService.cpp index 55ca5e7..ce7f376 100644 --- a/src/soundService.cpp +++ b/src/soundService.cpp @@ -1,7 +1,9 @@ #include "soundService.hpp" #include "game.hpp" +#include "gamedata.hpp" #include #include +#include SoundService::SoundService() { music = {}; @@ -25,8 +27,6 @@ bool SoundService::loadMusic(const std::string &id) { musicBin.fileData.data(), musicBin.fileData.size()); - printf("loaded.. \n"); - if (IsMusicValid(newMusic)) { unload(); @@ -51,10 +51,21 @@ void SoundService::playSound(const std::string &id) const { if (gameData.music.count(id) != 0) { if (gameData.music[id].isSound) { - Sound sound = LoadSound(gameData.music[id].relativePath.c_str()); + MusicBin soundBin = gameData.music[id]; + Wave soundWave = LoadWaveFromMemory(soundBin.fileExt.c_str(), + soundBin.fileData.data(), + soundBin.fileData.size()); + Sound sound = LoadSoundFromWave(soundWave); PlaySound(sound); + + UnloadWave(soundWave); + + // UnloadSound(sound); } + } else { + throw std::runtime_error( + TextFormat("This Sound does not exist: %s", id.c_str())); } } @@ -66,7 +77,6 @@ void SoundService::update() const { void SoundService::unload() const { if (musicLoaded) { - printf("unloaded.. \n"); StopMusicStream(music); UnloadMusicStream(music); } diff --git a/src/stateService.cpp b/src/stateService.cpp index 3ee3f40..ea928f7 100644 --- a/src/stateService.cpp +++ b/src/stateService.cpp @@ -1,8 +1,17 @@ #include "stateService.hpp" +#include "sol/error.hpp" +#include StateService::StateService() { gameState.emplace("test", false); } +void StateService::setProp(const std::string &prop, bool value) { + gameState[prop] = value; +} + bool StateService::getProp(const std::string &prop) const { + if (gameState.count(prop) == 0) { + throw sol::error("This prop does not exist"); + } return gameState.at(prop); } diff --git a/xmake.lua b/xmake.lua index 10f51af..9aaad6b 100644 --- a/xmake.lua +++ b/xmake.lua @@ -79,6 +79,7 @@ add_packages("raylib", "nlohmann_json", "luajit") set_languages("cxx17") add_includedirs("include/", "include/lua/") add_files("src/*.cpp", "src/lua/*.cpp") +add_defines("SOL_LUAJIT") if is_plat("linux") then add_cxxflags("-fPIC") end From 285f46af172678472b67b613932b7a5cd7b44984 Mon Sep 17 00:00:00 2001 From: CDevv Date: Fri, 20 Mar 2026 18:43:57 +0200 Subject: [PATCH 04/35] Start of WorldService, basic Room type, Player type Signed-off-by: CDevv --- include/lua/worldApi.hpp | 10 ++++++++++ include/player.hpp | 8 ++++++++ resources/scripts/dialogue.lua | 9 +++++++++ src/actor.cpp | 1 + src/lua/apiTypes.cpp | 17 ++++++++++++++--- src/lua/worldApi.cpp | 15 +++++++++++++++ src/player.cpp | 25 +++++++++++++++++++++++++ src/room.cpp | 2 +- src/scriptService.cpp | 2 ++ 9 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 include/lua/worldApi.hpp create mode 100644 src/lua/worldApi.cpp diff --git a/include/lua/worldApi.hpp b/include/lua/worldApi.hpp new file mode 100644 index 0000000..2c8b5b1 --- /dev/null +++ b/include/lua/worldApi.hpp @@ -0,0 +1,10 @@ +#ifndef _RPGPP_LUA_WORLDAPI_H +#define _RPGPP_LUA_WORLDAPI_H + +#include "room.hpp" +#include "sol/state_view.hpp" + +sol::object lua_world_getroom(sol::this_state lua); +void lua_world_set(sol::state_view lua); + +#endif \ No newline at end of file diff --git a/include/player.hpp b/include/player.hpp index f07b9ce..3f9c4e5 100644 --- a/include/player.hpp +++ b/include/player.hpp @@ -46,6 +46,14 @@ class Player { void moveByVelocity(Vector2 velocity); /** Get the player's position. */ Vector2 getPosition() const; + /** Set the player's position */ + void setPosition(Vector2 pos); + /** Get the player's position with an 'anchor' in the center */ + Vector2 getCenterPosition() const; + /** Get the player's tile position */ + Vector2 getTilePosition() const; + /** Set the player's tile position */ + void setTilePosition(Vector2 tilePos); Vector2 getCollisionPos() const; }; diff --git a/resources/scripts/dialogue.lua b/resources/scripts/dialogue.lua index dc508da..ddd701a 100644 --- a/resources/scripts/dialogue.lua +++ b/resources/scripts/dialogue.lua @@ -13,4 +13,13 @@ print(vec.y) vec2 = Vector2.new() print(vec2.x) print(vec2.y) + +room = World.GetRoom() +player = room:GetPlayer() +pos = player:GetPosition() + +print(pos.x) +print(pos.y) + +player:SetTilePosition(Vector2.new(2, 1)) end diff --git a/src/actor.cpp b/src/actor.cpp index 8561b89..86ea1e8 100644 --- a/src/actor.cpp +++ b/src/actor.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include using json = nlohmann::json; diff --git a/src/lua/apiTypes.cpp b/src/lua/apiTypes.cpp index 786b40a..de13090 100644 --- a/src/lua/apiTypes.cpp +++ b/src/lua/apiTypes.cpp @@ -1,9 +1,20 @@ #include "lua/apiTypes.hpp" +#include "player.hpp" +#include "raylib.h" #include "sol/raii.hpp" +#include "sol/types.hpp" void lua_types_set(sol::state_view lua) { - lua.new_usertype( + lua.new_usertype( "Vector2", - sol::constructors(), "x", - &lua_Vector2::x, "y", &lua_Vector2::y); + sol::factories([]() { return Vector2(); }, + [](float a, float b) { return Vector2{a, b}; }), + "x", &Vector2::x, "y", &Vector2::y); + lua.new_usertype(sol::no_construction(), "GetPlayer", + &Room::getPlayer); + lua.new_usertype( + sol::no_construction(), "GetPosition", &Player::getPosition, + "SetPosition", &Player::setPosition, "GetTilePosition", + &Player::getTilePosition, "SetTilePosition", &Player::setTilePosition, + "MoveByVelocity", &Player::moveByVelocity); } \ No newline at end of file diff --git a/src/lua/worldApi.cpp b/src/lua/worldApi.cpp new file mode 100644 index 0000000..2a3ea73 --- /dev/null +++ b/src/lua/worldApi.cpp @@ -0,0 +1,15 @@ +#include "lua/worldApi.hpp" +#include "game.hpp" +#include "sol/object.hpp" +#include "sol/table.hpp" +#include "sol/types.hpp" + +sol::object lua_world_getroom(sol::this_state lua) { + return sol::make_object(lua, &Game::getWorld().getRoom()); +} + +void lua_world_set(sol::state_view lua) { + auto space = lua["World"].get_or_create(); + + space.set_function("GetRoom", lua_world_getroom); +} \ No newline at end of file diff --git a/src/player.cpp b/src/player.cpp index 577d60f..026aa1a 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -2,6 +2,7 @@ #include "interactable.hpp" #include #include +#include Player::Player(std::unique_ptr actor, Room &room) : room(room) { this->lock = false; @@ -187,11 +188,35 @@ Vector2 Player::getPosition() const { if (actor == nullptr) return Vector2{0, 0}; + return actor->getPosition(); +} + +void Player::setPosition(Vector2 pos) { + actor->setPosition(pos); + position = pos; +} + +Vector2 Player::getCenterPosition() const { + if (actor == nullptr) + return Vector2{0, 0}; + Rectangle actorRect = actor->getRect(); return Vector2{actorRect.x + (actorRect.width / 2), actorRect.y + (actorRect.height / 2)}; } +Vector2 Player::getTilePosition() const { return actor->getTilePosition(); } + +void Player::setTilePosition(Vector2 tilePos) { + if (!room.getTileMap()->worldPosIsValid(tilePos)) { + throw std::runtime_error(TextFormat( + "This world tile position does not exist: %i, %i", + static_cast(tilePos.x), static_cast(tilePos.y))); + } + actor->setTilePosition(tilePos, + room.getTileMap()->getTileSet()->getTileSize()); +} + Vector2 Player::getCollisionPos() const { if (actor == nullptr) return Vector2{0, 0}; diff --git a/src/room.cpp b/src/room.cpp index bdf2683..fa8a53b 100644 --- a/src/room.cpp +++ b/src/room.cpp @@ -271,7 +271,7 @@ void Room::update() { } void Room::updateCamera() { - Vector2 playerPos = player->getPosition(); + Vector2 playerPos = player->getCenterPosition(); Vector2 cameraOffset = {0, 0}; Vector2 cameraTarget = {0, 0}; diff --git a/src/scriptService.cpp b/src/scriptService.cpp index b33ce42..1f6ae0e 100644 --- a/src/scriptService.cpp +++ b/src/scriptService.cpp @@ -3,6 +3,7 @@ #include "lua/interfaceApi.hpp" #include "lua/soundsApi.hpp" #include "lua/stateApi.hpp" +#include "lua/worldApi.hpp" #include "sol/state_view.hpp" #include "sol/table.hpp" @@ -37,6 +38,7 @@ void ScriptService::setLua(sol::state_view lua) { lua_ui_set(lua); lua_sounds_set(lua); lua_gamestate_set(lua); + lua_world_set(lua); } sol::state &ScriptService::getState() { return state; } From 640abb6509cba40f6374eea611c1636472da32a3 Mon Sep 17 00:00:00 2001 From: CDevv Date: Sat, 21 Mar 2026 13:27:34 +0200 Subject: [PATCH 05/35] Actor usertype and Direction enum in Lua Signed-off-by: CDevv --- include/actor.hpp | 6 ++++++ include/player.hpp | 3 +++ resources/scripts/dialogue.lua | 25 +++++++++---------------- src/actor.cpp | 31 +++++++++++++++++++++++++++++-- src/lua/apiTypes.cpp | 20 +++++++++++++++++++- src/player.cpp | 12 ++++++++---- 6 files changed, 74 insertions(+), 23 deletions(-) diff --git a/include/actor.hpp b/include/actor.hpp index f68be71..e806baf 100644 --- a/include/actor.hpp +++ b/include/actor.hpp @@ -53,6 +53,8 @@ class Actor : public ISaveable { std::array, 8> animations; /** A Direction enum, showing the current animation that is being played. */ Direction currentAnimation; + Direction lastAnimation; + bool tempAnimIsPlayed = false; public: /** Empty constructor. */ @@ -125,6 +127,10 @@ class Actor : public ISaveable { /** Add multiple frames to the chosen animation. */ void addAnimationFrames(Direction id, const std::vector> &frames); + /** Temporarily play an animation */ + void playAnimation(Direction id); + /** Check whether a temporary animation is playing */ + bool isTempAnimationPlaying(); /** Change the Actor's current animation and play it. */ void changeAnimation(Direction id); /** Get the path of the used TileSet. */ diff --git a/include/player.hpp b/include/player.hpp index 3f9c4e5..4dde0cd 100644 --- a/include/player.hpp +++ b/include/player.hpp @@ -42,6 +42,8 @@ class Player { void draw() const; /** Set the Player's current room. */ void setRoom(Room &room) const; + /** Get the player's actor */ + Actor &getActor() const; /** Move the player by a certain velocity. */ void moveByVelocity(Vector2 velocity); /** Get the player's position. */ @@ -54,6 +56,7 @@ class Player { Vector2 getTilePosition() const; /** Set the player's tile position */ void setTilePosition(Vector2 tilePos); + /** Get the position of the collision */ Vector2 getCollisionPos() const; }; diff --git a/resources/scripts/dialogue.lua b/resources/scripts/dialogue.lua index ddd701a..8bae6ce 100644 --- a/resources/scripts/dialogue.lua +++ b/resources/scripts/dialogue.lua @@ -1,25 +1,18 @@ function interact() Interface.OpenDialogue(props.dialogue) -GameState.Set("hi", false) - -print("hi: " .. tostring(GameState.Get("hi"))) -a = 5 / 0 -print(a) ---GameState.Get("nonexisty") -vec = Vector2.new(1, 2) -print(vec.x) -print(vec.y) - -vec2 = Vector2.new() -print(vec2.x) -print(vec2.y) room = World.GetRoom() player = room:GetPlayer() pos = player:GetPosition() -print(pos.x) -print(pos.y) +actor = player:GetActor() + +actorpos = actor:GetPosition() + +print(actorpos.x) +print(actorpos.y) + +print(Direction.LEFT) +actor:PlayAnimation(Direction.LEFT) -player:SetTilePosition(Vector2.new(2, 1)) end diff --git a/src/actor.cpp b/src/actor.cpp index 86ea1e8..4ca152a 100644 --- a/src/actor.cpp +++ b/src/actor.cpp @@ -3,6 +3,7 @@ #include "gamedata.hpp" #include #include +#include #include #include #include @@ -262,8 +263,20 @@ void Actor::update() { frameCounter = 0; currentFrame++; - if (currentFrame >= this->getAnimationCount()) - currentFrame = 0; + if (tempAnimIsPlayed) { + printf("%i \n", currentFrame); + } + + if (currentFrame >= this->getAnimationCount()) { + if (currentFrame >= this->getAnimationCount()) { + + currentFrame = 0; + if (tempAnimIsPlayed) { + currentAnimation = lastAnimation; + tempAnimIsPlayed = false; + } + } + } this->setAnimationFrame(currentFrame); } @@ -390,6 +403,20 @@ void Actor::addAnimationFrames(const Direction id, } } +void Actor::playAnimation(Direction id) { + printf("playing.. %i\n", static_cast(id)); + + this->lastAnimation = currentAnimation; + this->tempAnimIsPlayed = true; + + this->currentAnimation = id; + this->currentFrame = -1; + + frameCounter = (60 / frameSpeed); +} + +bool Actor::isTempAnimationPlaying() { return tempAnimIsPlayed; } + void Actor::changeAnimation(Direction id) { if (this->currentAnimation != id) { this->currentFrame = 0; diff --git a/src/lua/apiTypes.cpp b/src/lua/apiTypes.cpp index de13090..049c4dc 100644 --- a/src/lua/apiTypes.cpp +++ b/src/lua/apiTypes.cpp @@ -1,4 +1,5 @@ #include "lua/apiTypes.hpp" +#include "actor.hpp" #include "player.hpp" #include "raylib.h" #include "sol/raii.hpp" @@ -10,11 +11,28 @@ void lua_types_set(sol::state_view lua) { sol::factories([]() { return Vector2(); }, [](float a, float b) { return Vector2{a, b}; }), "x", &Vector2::x, "y", &Vector2::y); + + lua.new_enum("Direction", "DOWN_IDLE", Direction::RPGPP_DOWN_IDLE, "DOWN", + Direction::RPGPP_DOWN, "UP_IDLE", Direction::RPGPP_UP_IDLE, + "UP", Direction::RPGPP_UP, "LEFT_IDLE", + Direction::RPGPP_LEFT_IDLE, "LEFT", Direction::RPGPP_LEFT, + "RIGHT_IDLE", Direction::RPGPP_RIGHT_IDLE, "RIGHT", + Direction::RPGPP_RIGHT); + lua.new_usertype(sol::no_construction(), "GetPlayer", &Room::getPlayer); + + lua.new_usertype( + sol::no_construction(), "GetPosition", &Actor::getPosition, + "SetPosition", &Actor::setPosition, "GetTilePosition", + &Actor::getTilePosition, "SetTilePosition", &Actor::setTilePosition, + "MoveByVelocity", &Actor::moveByVelocity, "ChangeAnimation", + &Actor::changeAnimation, "PlayAnimation", &Actor::playAnimation); + lua.new_usertype( sol::no_construction(), "GetPosition", &Player::getPosition, "SetPosition", &Player::setPosition, "GetTilePosition", &Player::getTilePosition, "SetTilePosition", &Player::setTilePosition, - "MoveByVelocity", &Player::moveByVelocity); + "MoveByVelocity", &Player::moveByVelocity, "GetActor", + &Player::getActor); } \ No newline at end of file diff --git a/src/player.cpp b/src/player.cpp index 026aa1a..cc1e808 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -63,10 +63,12 @@ void Player::update() { if (lock) return; - if (Vector2Equals(velocity, Vector2{0, 0})) { - actor->changeAnimation(idleDirection); - } else { - actor->changeAnimation(currentDirection); + if (!actor->isTempAnimationPlaying()) { + if (Vector2Equals(velocity, Vector2{0, 0})) { + actor->changeAnimation(idleDirection); + } else { + actor->changeAnimation(currentDirection); + } } this->moveByVelocity(velocity); @@ -177,6 +179,8 @@ void Player::setRoom(Room &room) const { // this->room = Room(room); } +Actor &Player::getActor() const { return *actor; } + void Player::moveByVelocity(Vector2 velocity) { Vector2 resultVector = Vector2Add(position, velocity); position = resultVector; From f7c0c427158863553b5caf9d0579d8b691b740aa Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Sat, 21 Mar 2026 20:24:58 +0700 Subject: [PATCH 06/35] Added support for nil, bool, number, string, table and function types --- include/lua/stateApi.hpp | 8 ++++--- include/stateService.hpp | 11 +++++++--- resources/scripts/dialogue.lua | 39 +++++++++++++++++++++++++--------- src/lua/stateApi.cpp | 23 ++++++++++++++++---- src/stateService.cpp | 4 ++-- 5 files changed, 63 insertions(+), 22 deletions(-) diff --git a/include/lua/stateApi.hpp b/include/lua/stateApi.hpp index a227a97..129ecc0 100644 --- a/include/lua/stateApi.hpp +++ b/include/lua/stateApi.hpp @@ -1,10 +1,12 @@ #ifndef _RPGPP_LUA_STATEAPI_H #define _RPGPP_LUA_STATEAPI_H +#include "sol/forward.hpp" #include "sol/state_view.hpp" +#include "stateService.hpp" -void lua_gamestate_setval(const std::string &prop, bool value); -bool lua_gamestate_getval(const std::string &prop); +void lua_gamestate_setval(const std::string &prop, sol::object value); +Value lua_gamestate_getval(const std::string &prop); void lua_gamestate_set(sol::state_view lua); -#endif \ No newline at end of file +#endif diff --git a/include/stateService.hpp b/include/stateService.hpp index 32d1709..031a4a2 100644 --- a/include/stateService.hpp +++ b/include/stateService.hpp @@ -1,23 +1,28 @@ #ifndef _RPGPP_STATESERVICE_H #define _RPGPP_STATESERVICE_H +#include "sol/table.hpp" +#include "sol/types.hpp" +#include #include #include +#include /** The StateService is responsible for storing gameplay-related variables * that make up the state of the game. */ +using Value = std::variant; class StateService { private: /** A pair of string keys and boolean values. */ - std::map gameState; + std::map gameState; public: /** Empty constructor */ StateService(); /** Set a property */ - void setProp(const std::string &prop, bool value); + void setProp(const std::string &prop, Value value); /** Get a boolean property from the container. */ - bool getProp(const std::string &prop) const; + Value getProp(const std::string &prop) const; /** Unload routine. */ void unload() const; }; diff --git a/resources/scripts/dialogue.lua b/resources/scripts/dialogue.lua index 8bae6ce..2fa7597 100644 --- a/resources/scripts/dialogue.lua +++ b/resources/scripts/dialogue.lua @@ -1,18 +1,37 @@ function interact() -Interface.OpenDialogue(props.dialogue) + Interface.OpenDialogue(props.dialogue) -room = World.GetRoom() -player = room:GetPlayer() -pos = player:GetPosition() + room = World.GetRoom() + player = room:GetPlayer() + pos = player:GetPosition() -actor = player:GetActor() + actor = player:GetActor() -actorpos = actor:GetPosition() + actorpos = actor:GetPosition() -print(actorpos.x) -print(actorpos.y) + print(actorpos.x) + print(actorpos.y) -print(Direction.LEFT) -actor:PlayAnimation(Direction.LEFT) + GameState.Set("test_nil", nil); + GameState.Set("test_bool", true); + GameState.Set("test_int1", 50); + GameState.Set("test_int2", 4611686018427387904); + GameState.Set("test_double", 3.14); + GameState.Set("test_string", "hello"); + GameState.Set("test_table1", { 1, 2, "test" }); + GameState.Set("test_table2", { key = "test" }); + GameState.Set("test_table3", { key = "test", 2, 4 }); + print(GameState.Get("test_nil"), type(GameState.Get("test_nil"))) + print(GameState.Get("test_bool"), type(GameState.Get("test_bool"))) + print(GameState.Get("test_int1"), type(GameState.Get("test_int1"))) + print(GameState.Get("test_int2"), type(GameState.Get("test_int2"))) + print(GameState.Get("test_double"), type(GameState.Get("test_double"))) + print(GameState.Get("test_string"), type(GameState.Get("test_string"))) + print(GameState.Get("test_table1"), type(GameState.Get("test_table1"))) + print(GameState.Get("test_table2"), type(GameState.Get("test_table2"))) + print(GameState.Get("test_table3"), type(GameState.Get("test_table3"))) + + print(Direction.LEFT) + actor:PlayAnimation(Direction.LEFT) end diff --git a/src/lua/stateApi.cpp b/src/lua/stateApi.cpp index cab1b7d..09daa7c 100644 --- a/src/lua/stateApi.cpp +++ b/src/lua/stateApi.cpp @@ -1,12 +1,27 @@ #include "lua/stateApi.hpp" #include "game.hpp" +#include "sol/forward.hpp" #include "sol/table.hpp" +#include "sol/types.hpp" +#include "stateService.hpp" -void lua_gamestate_setval(const std::string &prop, bool value) { - Game::getState().setProp(prop, value); +void lua_gamestate_setval(const std::string &prop, sol::object value) { + if (value.is()) { + Game::getState().setProp(prop, sol::nil); + } else if (value.is()) { + Game::getState().setProp(prop, value.as()); + } else if (value.is()) { + Game::getState().setProp(prop, value.as()); + } else if (value.is()) { + Game::getState().setProp(prop, value.as()); + } else if (value.is()) { + Game::getState().setProp(prop, value.as()); + } else if (value.is()) { + Game::getState().setProp(prop, value.as()); + } } -bool lua_gamestate_getval(const std::string &prop) { +Value lua_gamestate_getval(const std::string &prop) { return Game::getState().getProp(prop); } @@ -15,4 +30,4 @@ void lua_gamestate_set(sol::state_view lua) { space.set_function("Set", lua_gamestate_setval); space.set_function("Get", lua_gamestate_getval); -} \ No newline at end of file +} diff --git a/src/stateService.cpp b/src/stateService.cpp index ea928f7..85ad172 100644 --- a/src/stateService.cpp +++ b/src/stateService.cpp @@ -4,11 +4,11 @@ StateService::StateService() { gameState.emplace("test", false); } -void StateService::setProp(const std::string &prop, bool value) { +void StateService::setProp(const std::string &prop, Value value) { gameState[prop] = value; } -bool StateService::getProp(const std::string &prop) const { +Value StateService::getProp(const std::string &prop) const { if (gameState.count(prop) == 0) { throw sol::error("This prop does not exist"); } From ec200a4b48f2ce4bc5cea494e789b27feb7915ac Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Sat, 21 Mar 2026 20:45:56 +0700 Subject: [PATCH 07/35] Fix bug with editor crashing when saving room --- src/room.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/room.cpp b/src/room.cpp index fa8a53b..8786d07 100644 --- a/src/room.cpp +++ b/src/room.cpp @@ -226,6 +226,8 @@ json Room::dumpJson() { if (p.getHasInteractable()) { propJson["props"] = p.getInteractable()->getProps(); + } else { + propJson["props"] = json::object(); } propsMap[key] = propJson; From 80919eaa04d6c5a7e5a4f07b8a8c24e65d91af91 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Sat, 21 Mar 2026 20:50:42 +0700 Subject: [PATCH 08/35] Allow overlay on hovering tiles to show up above all other tiles --- src/editor/views/roomView.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/editor/views/roomView.cpp b/src/editor/views/roomView.cpp index f8b7400..dd31efb 100644 --- a/src/editor/views/roomView.cpp +++ b/src/editor/views/roomView.cpp @@ -110,6 +110,7 @@ void RoomView::drawCanvas() { static_cast(tileMap->getAtlasTileSize())}; int worldWidth = static_cast(worldSize.x); int worldHeight = static_cast(worldSize.y); + Rectangle overlayRect{0, 0, 0, 0}; for (int tileX = 0; tileX < worldWidth; tileX++) { for (int tileY = 0; tileY < worldHeight; tileY++) { auto tile = tileMap->getTile(tileX, tileY); @@ -132,7 +133,7 @@ void RoomView::drawCanvas() { // Draw tile border DrawRectangleLinesEx(destRect, 1.0f, Fade(GRAY, 0.5f)); if (CheckCollisionPointRec(mouseWorldPos, destRect)) { - DrawRectangleLinesEx(destRect, 2.0f, Fade(GRAY, 0.5f)); + overlayRect = destRect; } } } @@ -186,6 +187,7 @@ void RoomView::drawCanvas() { prop.draw(); } + DrawRectangleLinesEx(overlayRect, 2.0f, Fade(GRAY, 0.5f)); DrawCircleV(getMouseWorldPos(), 1.0f, MAROON); } From 3fd02bb6c090e3b8a072b73d6a3dde403593e255 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Sat, 21 Mar 2026 21:46:39 +0700 Subject: [PATCH 09/35] Force overriding the compiled binary --- src/editor/project.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/project.cpp b/src/editor/project.cpp index 47aef83..064a3a1 100644 --- a/src/editor/project.cpp +++ b/src/editor/project.cpp @@ -548,7 +548,7 @@ void Project::buildProject() { #endif std::filesystem::copy(baseGamePath, resultPath, - std::filesystem::copy_options::update_existing); + std::filesystem::copy_options::overwrite_existing); #ifdef _WIN64 From 6d00258c76e09a99119059aa9f63697616cffbe4 Mon Sep 17 00:00:00 2001 From: thefirey33 Date: Sat, 21 Mar 2026 21:49:56 +0300 Subject: [PATCH 10/35] add shared container vector holding baseContainer. --- include/actorContainer.hpp | 12 ++++ include/baseContainer.hpp | 42 ++++++++++++ include/collisionsContainer.hpp | 11 +--- include/conversion.hpp | 9 +++ include/gamedata.hpp | 8 +++ include/interactable.hpp | 2 +- include/interactablesContainer.hpp | 18 ++--- include/room.hpp | 14 ++-- src/collisionsContainer.cpp | 26 -------- src/conversion.cpp | 11 ++++ src/editor/actions/eraseTileAction.cpp | 15 ++--- src/editor/actions/placeTileAction.cpp | 15 +++-- src/editor/views/roomView.cpp | 11 ++-- src/interactable.cpp | 11 ++-- src/interactablesContainer.cpp | 91 ++++++++------------------ src/room.cpp | 67 ++++++++++--------- 16 files changed, 191 insertions(+), 172 deletions(-) create mode 100644 include/actorContainer.hpp create mode 100644 include/baseContainer.hpp create mode 100644 include/conversion.hpp create mode 100644 src/conversion.cpp diff --git a/include/actorContainer.hpp b/include/actorContainer.hpp new file mode 100644 index 0000000..2a901c7 --- /dev/null +++ b/include/actorContainer.hpp @@ -0,0 +1,12 @@ +#ifndef _RPGPP_ACTORCONTAINER_H +#define _RPGPP_ACTORCONTAINER_H + +#include "actor.hpp" +#include "baseContainer.hpp" +class ActorContainer : public BaseContainer { + public: + /** Construct the actor container. */ + ActorContainer() = default; +}; + +#endif // !_RPGPP_ACTORCONTAINER_H diff --git a/include/baseContainer.hpp b/include/baseContainer.hpp new file mode 100644 index 0000000..0592dfa --- /dev/null +++ b/include/baseContainer.hpp @@ -0,0 +1,42 @@ +#ifndef _RPGPP_BASECONTAINER_H +#define _RPGPP_BASECONTAINER_H + +#include "gamedata.hpp" +#include +#include + +template class BaseContainer { + + protected: + /** The list of objects kept inside of this container. */ + std::map objects = {}; + + public: + BaseContainer() {} + + /** Pushes an object to the map. */ + void pushObject(IVector pos, T obj) { + this->objects.try_emplace(pos, std::move(obj)); + } + /** Remove an object existing at a position. */ + void removeObject(IVector pos) { + auto key = this->objects.find(pos); + if (key != this->objects.end()) { + this->objects.erase(key); + } + } + /** Check if an object exists at specified position. */ + bool objectExistsAtPosition(IVector pos) { + return this->objects.find(pos) != this->objects.end(); + } + /** Gets the object at a specified IVector Position. */ + T &getObjectAtPosition(IVector pos) { + if (this->objects.find(pos) != this->objects.end()) + return this->objects[pos]; + throw std::out_of_range("key not found!"); + } + /** Get a reference to the whole object map. */ + std::map &getObjects() { return this->objects; } +}; + +#endif // !_RPGPP_BASECONTAINER_H diff --git a/include/collisionsContainer.hpp b/include/collisionsContainer.hpp index 6d38a4b..bed04d8 100644 --- a/include/collisionsContainer.hpp +++ b/include/collisionsContainer.hpp @@ -1,25 +1,18 @@ #ifndef _RPGPP_COLLISIONSCONTAINER_H #define _RPGPP_COLLISIONSCONTAINER_H +#include "baseContainer.hpp" #include #include /** A container of collision tiles to be used by a Room */ -class CollisionsContainer { +class CollisionsContainer : public BaseContainer { private: /** A vector that contains the tile positions of the collision tiles */ - std::vector vec; public: /** Empty constructor */ CollisionsContainer(); - /** Add a collision tile to this container */ - void addCollisionTile(int x, int y); - /** Remove a collision tile by its tile position */ - void removeCollisionTile(int x, int y); - /** Get a reference to this container's vector. Items are tile positions of - * the collision tiles. */ - const std::vector &getVector() const; }; #endif \ No newline at end of file diff --git a/include/conversion.hpp b/include/conversion.hpp new file mode 100644 index 0000000..14339e5 --- /dev/null +++ b/include/conversion.hpp @@ -0,0 +1,9 @@ +#ifndef _RPGPP_CONVERSION_H +#define _RPGPP_CONVERSION_H + +#include "gamedata.hpp" +#include "raylib.h" +Vector2 fromIVector(const IVector &other); +IVector fromVector2(const Vector2 &other); + +#endif // !_RPGPP_CONVERSION_H \ No newline at end of file diff --git a/include/gamedata.hpp b/include/gamedata.hpp index 7a3f600..333cdcc 100644 --- a/include/gamedata.hpp +++ b/include/gamedata.hpp @@ -15,6 +15,14 @@ struct IVector { int x; int y; + + bool operator==(const IVector &other) const { + return x == other.x && y == other.y; + } + + bool operator<(const IVector &other) const { + return x < other.x || (x == other.x && y < other.y); + } }; struct IRect { diff --git a/include/interactable.hpp b/include/interactable.hpp index 7fe1ebd..63db89a 100644 --- a/include/interactable.hpp +++ b/include/interactable.hpp @@ -31,7 +31,7 @@ class Interactable : public ISaveable { /** Whether this interactable will be interacted with on touch. */ bool onTouch; /** A json object describing the properties of an interactable. */ - std::unique_ptr props; + nlohmann::json *props; /** Script file path */ std::string scriptPath; diff --git a/include/interactablesContainer.hpp b/include/interactablesContainer.hpp index 32f18f2..7afcee1 100644 --- a/include/interactablesContainer.hpp +++ b/include/interactablesContainer.hpp @@ -1,6 +1,7 @@ #ifndef _RPGPP_INTERACTABLESCONTAINER_H #define _RPGPP_INTERACTABLESCONTAINER_H +#include "baseContainer.hpp" #include "gamedata.hpp" #include "interactable.hpp" #include @@ -9,27 +10,22 @@ using json = nlohmann::json; /** Container of Interactables that is to be used by a [Room](Room.md) */ -class InteractablesContainer { - private: - /** The vector, containing all Interactables */ - std::vector> vec; - bool interactableExists(int x, int y); +class InteractablesContainer + : public BaseContainer> { public: /** Empty constructor */ InteractablesContainer(); /** Add a new Interactable with tile position and type */ - Interactable *add(int x, int y, const std::string &type); + Interactable *add(IVector pos, const std::string &type); /** Add a new Interactable using a bin structure */ void addBin(InteractableInRoomBin bin); /** Get an Interactable by its tile position */ - Interactable *getInt(int x, int y) const; - /** Remove an Interactable by its tile position */ - void removeInteractable(int x, int y); + Interactable *getInt(IVector pos); /** Change the Interactable's type at the specified tile position */ - void setInteractableType(int x, int y, const std::string &type); + void setInteractableType(IVector pos, const std::string &type); /** Get the vector that contains all Interactables */ - std::vector getList() const; + std::vector getList(); /** Add interactables from binary structures. */ void addBinVector(const std::vector &bin); /** Add interactables from a Room json object. Must contain 'interactables' diff --git a/include/room.hpp b/include/room.hpp index 7d6b4d5..2bbe294 100644 --- a/include/room.hpp +++ b/include/room.hpp @@ -15,6 +15,7 @@ class TileMap; using json = nlohmann::json; #include "actor.hpp" +#include "actorContainer.hpp" #include "collisionsContainer.hpp" #include "interactablesContainer.hpp" #include "player.hpp" @@ -39,12 +40,15 @@ class Room : public ISaveable { std::unique_ptr interactables; /** Container of the collision tiles */ std::unique_ptr collisions; - /** A collection of the Props in this Room. */ + /** A collection of the Props in this Room. + FIXME: Change to Container system, due to segmentation issues, couldn't + be changed! + */ std::unique_ptr> props; /** This Room's TileMap, which contains all placed tiles. */ std::unique_ptr tileMap; /** A collection of all Actors in this Room */ - std::unique_ptr> actors; + std::unique_ptr actors; /** This Room's only Player. */ std::unique_ptr player; void updateCamera(); @@ -111,16 +115,16 @@ class Room : public ISaveable { /** Remove a prop from this room using a world tile position. */ void removeProp(Vector2 worldPos) const; /** Get the Prop at the specified tile position. */ - Prop *getPropAt(Vector2 worldPos) const; + Prop *getPropAt(IVector worldPos) const; /** Get a refernece to the collection of Actors. */ - std::vector &getActors() const; + std::vector &getActors(); /** Add an actor to this Room * @param actor The actor to be added to the Room's collection. */ void addActor(Actor actor) const; /** Remove an Actor using a tilePosition. */ - void removeActor(Vector2 tilePosition) const; + void removeActor(IVector tilePosition) const; }; #endif diff --git a/src/collisionsContainer.cpp b/src/collisionsContainer.cpp index 6b88b64..4d140d1 100644 --- a/src/collisionsContainer.cpp +++ b/src/collisionsContainer.cpp @@ -1,30 +1,4 @@ #include "collisionsContainer.hpp" #include -#include CollisionsContainer::CollisionsContainer() = default; - -void CollisionsContainer::addCollisionTile(int x, int y) { - for (auto item : vec) { - if (item.x == static_cast(x) && - item.y == static_cast(y)) { - return; - } - } - vec.push_back(Vector2{static_cast(x), static_cast(y)}); -} - -void CollisionsContainer::removeCollisionTile(int x, int y) { - int idx = 0; - for (Vector2 item : vec) { - if (item.x == static_cast(x) && - item.y == static_cast(y)) { - vec.erase(vec.begin() + idx); - } - idx++; - } -} - -const std::vector &CollisionsContainer::getVector() const { - return vec; -} \ No newline at end of file diff --git a/src/conversion.cpp b/src/conversion.cpp new file mode 100644 index 0000000..a41701a --- /dev/null +++ b/src/conversion.cpp @@ -0,0 +1,11 @@ +#include "conversion.hpp" +#include "gamedata.hpp" +#include "raylib.h" + +Vector2 fromIVector(const IVector &other) { + return {static_cast(other.x), static_cast(other.y)}; +} + +IVector fromVector2(const Vector2 &other) { + return {static_cast(other.x), static_cast(other.y)}; +} \ No newline at end of file diff --git a/src/editor/actions/eraseTileAction.cpp b/src/editor/actions/eraseTileAction.cpp index b6381b9..2922704 100644 --- a/src/editor/actions/eraseTileAction.cpp +++ b/src/editor/actions/eraseTileAction.cpp @@ -1,6 +1,7 @@ #include "actions/eraseTileAction.hpp" #include "actions/mapAction.hpp" +#include "conversion.hpp" #include "views/worldView.hpp" EraseTileAction::EraseTileAction(MapActionData a) : MapAction(a) {} @@ -11,14 +12,11 @@ void EraseTileAction::execute() { data.room->getTileMap()->setEmptyTile(data.worldTile); } break; case RoomLayer::LAYER_COLLISION: { - data.room->getCollisions().removeCollisionTile( - static_cast(data.worldTile.x), - static_cast(data.worldTile.y)); + auto conv = fromVector2(data.worldTile); + data.room->getCollisions().removeObject(fromVector2(data.worldTile)); } break; case RoomLayer::LAYER_INTERACTABLES: { - data.room->getInteractables().removeInteractable( - static_cast(data.worldTile.x), - static_cast(data.worldTile.y)); + data.room->getInteractables().removeObject(fromVector2(data.worldTile)); } break; case RoomLayer::LAYER_PROPS: { data.room->removeProp({static_cast(data.worldTile.x), @@ -35,9 +33,8 @@ void EraseTileAction::undo() { data.room->getTileMap()->setTile(data.worldTile, data.prevTile); } break; case RoomLayer::LAYER_COLLISION: { - data.room->getCollisions().addCollisionTile( - static_cast(data.worldTile.x), - static_cast(data.worldTile.y)); + data.room->getCollisions().pushObject(fromVector2(data.worldTile), + false); } break; case RoomLayer::LAYER_INTERACTABLES: { diff --git a/src/editor/actions/placeTileAction.cpp b/src/editor/actions/placeTileAction.cpp index 2f1520c..5aaa42c 100644 --- a/src/editor/actions/placeTileAction.cpp +++ b/src/editor/actions/placeTileAction.cpp @@ -1,10 +1,12 @@ #include "actions/placeTileAction.hpp" #include "actions/mapAction.hpp" +#include "conversion.hpp" #include "editor.hpp" #include "prop.hpp" #include "raylib.h" #include "room.hpp" #include "views/worldView.hpp" +#include #include PlaceTileAction::PlaceTileAction(MapActionData a) : MapAction(a) {} @@ -19,12 +21,13 @@ void PlaceTileAction::execute() { } } break; case RoomLayer::LAYER_COLLISION: { - data.room->getCollisions().addCollisionTile(data.worldTile.x, - data.worldTile.y); + auto conv = fromVector2(data.worldTile); + data.room->getCollisions().pushObject(fromVector2(data.worldTile), + false); } break; case RoomLayer::LAYER_INTERACTABLES: { auto inter = data.room->getInteractables().add( - data.worldTile.x, data.worldTile.y, data.interactable); + fromVector2(data.worldTile), data.interactable); if (inter != nullptr) { char *txt = LoadFileText(data.interactableFullPath.c_str()); nlohmann::json interJson = json::parse(txt); @@ -69,12 +72,10 @@ void PlaceTileAction::undo() { data.room->getTileMap()->setTile(data.worldTile, data.prevTile); } break; case RoomLayer::LAYER_COLLISION: { - data.room->getCollisions().removeCollisionTile(data.worldTile.x, - data.worldTile.y); + data.room->getCollisions().removeObject(fromVector2(data.worldTile)); } break; case RoomLayer::LAYER_INTERACTABLES: { - data.room->getInteractables().removeInteractable(data.worldTile.x, - data.worldTile.y); + data.room->getInteractables().removeObject(fromVector2(data.worldTile)); } break; case RoomLayer::LAYER_PROPS: { data.room->removeProp({static_cast(data.worldTile.x), diff --git a/src/editor/views/roomView.cpp b/src/editor/views/roomView.cpp index dd31efb..cae51c0 100644 --- a/src/editor/views/roomView.cpp +++ b/src/editor/views/roomView.cpp @@ -146,9 +146,9 @@ void RoomView::drawCanvas() { DrawRectangleLinesEx(startTileDestRect, 2.0f, Fade(GREEN, 0.5f)); // collisions - for (auto collision : room->getCollisions().getVector()) { - int tileX = static_cast(collision.x); - int tileY = static_cast(collision.y); + for (auto &[vect, value] : room->getCollisions().getObjects()) { + int tileX = static_cast(vect.x); + int tileY = static_cast(vect.y); Rectangle destRect = getDestRect(tileMap, tileX, tileY); @@ -417,14 +417,13 @@ void RoomView::handleEditPress(tgui::Vector2f pos) { case RoomLayer::LAYER_INTERACTABLES: { IVector tileMouse = getTileAtMouse(); layerVisitor->inter = - room->getInteractables().getInt(tileMouse.x, tileMouse.y); + room->getInteractables().getInt({tileMouse.x, tileMouse.y}); layerVisitor->group->removeAllWidgets(); mj::visit(*layerVisitor, layer); } break; case RoomLayer::LAYER_PROPS: { IVector tileMouse = getTileAtMouse(); - layerVisitor->prop = room->getPropAt( - {static_cast(tileMouse.x), static_cast(tileMouse.y)}); + layerVisitor->prop = room->getPropAt(tileMouse); layerVisitor->group->removeAllWidgets(); mj::visit(*layerVisitor, layer); } diff --git a/src/interactable.cpp b/src/interactable.cpp index b206ac8..843153c 100644 --- a/src/interactable.cpp +++ b/src/interactable.cpp @@ -27,14 +27,14 @@ Interactable::Interactable(const std::string &path) { type = GetFileNameWithoutExt(path.c_str()); displayTitle = intJson.at("name"); - props = std::make_unique(intJson.at("props")); + props = new nlohmann::json(intJson.at("props")); scriptPath = intJson.at("script"); } Interactable::Interactable(const std::string &type, Vector2 tilePos, int tileSize) { this->type = type; - this->props = std::make_unique(); + this->props = new nlohmann::json(); this->valid = true; this->type = type; @@ -49,8 +49,7 @@ Interactable::Interactable(const std::string &type, Vector2 tilePos, Interactable::Interactable(InteractableInRoomBin bin) { this->type = bin.type; - this->props = - std::make_unique(json::from_cbor(bin.propsCbor)); + this->props = new nlohmann::json(json::from_cbor(bin.propsCbor)); Vector2 tilePos = {static_cast(bin.x), static_cast(bin.y)}; @@ -87,11 +86,11 @@ const std::string &Interactable::getType() const { return this->type; } void Interactable::setType(const std::string &type) { this->type = type; - this->props = std::make_unique(); + this->props = new nlohmann::json(); } void Interactable::setProps(nlohmann::json j) { - this->props = std::make_unique(j); + this->props = new nlohmann::json(j); } nlohmann::json &Interactable::getProps() { return *props; } diff --git a/src/interactablesContainer.cpp b/src/interactablesContainer.cpp index 819bb52..1a20ca7 100644 --- a/src/interactablesContainer.cpp +++ b/src/interactablesContainer.cpp @@ -1,4 +1,5 @@ #include "interactablesContainer.hpp" +#include "conversion.hpp" #include "gamedata.hpp" #include "interactable.hpp" #include "tilemap.hpp" @@ -11,84 +12,51 @@ InteractablesContainer::InteractablesContainer() {} -bool InteractablesContainer::interactableExists(int x, int y) { - for (auto &&i : vec) { - auto worldPos = i->getWorldPos(); - if (worldPos.x == static_cast(x) && - worldPos.y == static_cast(y)) { - return true; - } - } - return false; -} - -Interactable *InteractablesContainer::add(int x, int y, +Interactable *InteractablesContainer::add(IVector pos, const std::string &type) { - if (interactableExists(x, y)) { + if (this->objectExistsAtPosition(pos)) { return nullptr; } printf("%s \n", type.c_str()); - Vector2 tilePos = {static_cast(x), static_cast(y)}; + Vector2 tilePos = fromIVector(pos); + this->pushObject(pos, std::move(std::make_unique( + type, tilePos, _RPGPP_TILESIZE))); - std::unique_ptr inter = - std::make_unique(type, tilePos, _RPGPP_TILESIZE); - - vec.push_back(std::move(inter)); - - return getInt(x, y); + return this->getObjectAtPosition(pos).get(); } void InteractablesContainer::addBin(InteractableInRoomBin bin) { - if (interactableExists(bin.x, bin.y)) { + IVector pos = {bin.x, bin.y}; + if (this->objectExistsAtPosition(pos)) { return; } - vec.push_back(std::make_unique(bin)); -} - -Interactable *InteractablesContainer::getInt(int x, int y) const { - Interactable *res = nullptr; - for (auto &&i : vec) { - auto worldPos = i->getWorldPos(); - if (worldPos.x == static_cast(x) && - worldPos.y == static_cast(y)) { - res = i.get(); - } - } - - return res; + this->pushObject(pos, std::move(std::make_unique(bin))); } -void InteractablesContainer::removeInteractable(int x, int y) { - int idx = 0; - for (auto &&interactable : this->vec) { - Interactable *i = interactable.get(); - if (i != nullptr) { - auto worldPos = i->getWorldPos(); - if (worldPos.x == static_cast(x) && - worldPos.y == static_cast(y)) { - vec.erase(vec.begin() + idx); - } - } - idx++; - } +Interactable *InteractablesContainer::getInt(IVector pos) { + if (!this->objectExistsAtPosition(pos)) + return nullptr; + return this->getObjectAtPosition(pos).get(); } -void InteractablesContainer::setInteractableType(int x, int y, +void InteractablesContainer::setInteractableType(IVector pos, const std::string &type) { - if (getInt(x, y)->getType() == type) { + auto &obj = this->getObjectAtPosition(pos); + + if (obj->getType() == type) { return; } - getInt(x, y)->setType(type); + obj->setType(type); } -std::vector InteractablesContainer::getList() const { +std::vector InteractablesContainer::getList() { std::vector result; - for (auto &&in : this->vec) { - result.push_back(in.get()); + for (auto &[vect, interactb] : this->getObjects()) { + result.push_back(interactb.get()); } return result; } @@ -114,7 +82,7 @@ void InteractablesContainer::addJsonData(json roomJson) { std::string src = inter.at("src"); auto props = inter.at("props"); - auto newInter = add(x, y, src); + auto newInter = add({x, y}, src); newInter->setProps(props); newInter->setOnTouch(inter.at("onTouch")); } @@ -122,19 +90,18 @@ void InteractablesContainer::addJsonData(json roomJson) { json InteractablesContainer::dumpJson() { json j = json::object(); - for (auto &&i : vec) { - auto *inter = i.get(); - int tileX = static_cast(inter->getWorldPos().x); - int tileY = static_cast(inter->getWorldPos().y); + for (auto &[vect, interactible] : this->getObjects()) { + int tileX = static_cast(interactible->getWorldPos().x); + int tileY = static_cast(interactible->getWorldPos().y); auto key = TextFormat("%i;%i", tileX, tileY); - auto interProps = inter->getProps(); + auto interProps = interactible->getProps(); json interJson = json::object(); - interJson.push_back({"src", inter->getType()}); + interJson.push_back({"src", interactible->getType()}); interJson.push_back({"props", interProps}); - interJson.push_back({"onTouch", inter->isOnTouch()}); + interJson.push_back({"onTouch", interactible->isOnTouch()}); j.push_back({key, interJson}); } diff --git a/src/room.cpp b/src/room.cpp index 8786d07..d53419e 100644 --- a/src/room.cpp +++ b/src/room.cpp @@ -1,6 +1,8 @@ #include "room.hpp" #include "actor.hpp" +#include "actorContainer.hpp" #include "collisionsContainer.hpp" +#include "conversion.hpp" #include "game.hpp" #include "gamedata.hpp" #include "interactable.hpp" @@ -32,8 +34,7 @@ Room::Room() { this->collisions = std::make_unique(); this->props = std::make_unique>(); this->tileMap = std::unique_ptr{}; - this->actors = std::make_unique>(); - this->props = std::make_unique>(); + this->actors = std::make_unique(); this->player = std::unique_ptr{}; } @@ -60,7 +61,7 @@ Room::Room(const std::string &fileName, int tileSize) { this->interactables = std::make_unique(); this->collisions = std::make_unique(); - this->actors = std::make_unique>(); + this->actors = std::make_unique(); this->props = std::make_unique>(); this->tileMap = std::make_unique(fileName); @@ -76,7 +77,7 @@ Room::Room(const std::string &fileName, int tileSize) { int x = v[0]; int y = v[1]; - collisions->addCollisionTile(x, y); + collisions->pushObject({x, y}, false); } std::map> propsVec = @@ -129,7 +130,7 @@ Room::Room(const RoomBin &bin) : Room() { this->interactables = std::make_unique(); this->collisions = std::make_unique(); - this->actors = std::make_unique>(); + this->actors = std::make_unique(); this->props = std::make_unique>(); this->tileMap = std::make_unique(bin); @@ -146,7 +147,7 @@ Room::Room(const RoomBin &bin) : Room() { interactables->addBinVector(bin.interactables); for (auto collisionBin : bin.collisions) { - collisions->addCollisionTile(collisionBin.x, collisionBin.y); + collisions->pushObject(collisionBin, false); } for (auto const &propSource : bin.props) { @@ -201,10 +202,10 @@ json Room::dumpJson() { // Vector for collisions auto collisionsVector = std::vector>(); - for (Vector2 collisionPos : collisions->getVector()) { + for (auto &[vect, value] : collisions->getObjects()) { std::vector collision; - collision.push_back(static_cast(collisionPos.x)); - collision.push_back(static_cast(collisionPos.y)); + collision.push_back(static_cast(vect.x)); + collision.push_back(static_cast(vect.y)); collisionsVector.push_back(collision); } @@ -234,12 +235,12 @@ json Room::dumpJson() { } auto actorsMap = std::map{}; - for (auto &&a : *actors) { + for (auto &[vect, obj] : actors->getObjects()) { std::string key = - TextFormat("%i;%i", static_cast(a.getTilePosition().x), - static_cast(a.getTilePosition().y)); + TextFormat("%i;%i", static_cast(obj.getTilePosition().x), + static_cast(obj.getTilePosition().y)); - actorsMap[key] = a.getSourcePath(); + actorsMap[key] = obj.getSourcePath(); } roomJson.push_back({"interactables", interactableProps}); @@ -256,7 +257,7 @@ json Room::dumpJson() { void Room::unload() const { tileMap->unload(); - for (auto &&actor : *actors) { + for (auto &[vect, actor] : actors->getObjects()) { actor.unload(); } @@ -264,7 +265,7 @@ void Room::unload() const { } void Room::update() { - for (auto &&actor : *actors) { + for (auto &[vect, actor] : actors->getObjects()) { actor.update(); } player->update(); @@ -301,9 +302,9 @@ void Room::draw() const { static_cast(getWorldTileSize())}; DrawRectangleRec(rect, Fade(YELLOW, 0.5f)); } - for (auto c : collisions->getVector()) { - auto rect = Rectangle{c.x * static_cast(worldTileSize), - c.y * static_cast(worldTileSize), + for (auto &[vect, value] : collisions->getObjects()) { + auto rect = Rectangle{vect.x * static_cast(worldTileSize), + vect.y * static_cast(worldTileSize), static_cast(worldTileSize), static_cast(worldTileSize)}; DrawRectangleRec(rect, Fade(RED, 0.5f)); @@ -315,7 +316,7 @@ void Room::draw() const { } } - for (auto &&actor : *actors) { + for (auto &[vect, actor] : actors->getObjects()) { actor.draw(); } player->draw(); @@ -346,7 +347,11 @@ TileMap *Room::getTileMap() const { return this->tileMap.get(); } void Room::setTileMap(TileMap *newTileMap) { tileMap.reset(newTileMap); } std::vector Room::getCollisionTiles() const { - return this->collisions->getVector(); + static std::vector result; + for (auto &[vect, obj] : this->collisions->getObjects()) { + result.push_back(std::move(fromIVector(vect))); + } + return result; } std::string Room::getMusicSource() const { return musicSource; } @@ -380,7 +385,7 @@ void Room::removeProp(Vector2 worldPos) const { } } -Prop *Room::getPropAt(Vector2 worldPos) const { +Prop *Room::getPropAt(IVector worldPos) const { Prop *p = nullptr; int idx = 0; for (auto it = props->begin(); it < props->end(); ++it, idx++) { @@ -392,17 +397,19 @@ Prop *Room::getPropAt(Vector2 worldPos) const { return p; } -std::vector &Room::getActors() const { return *actors; } +std::vector &Room::getActors() { + static std::vector result; + for (auto &[vect, actor] : this->actors->getObjects()) { + result.push_back(std::move(actor)); + } + return result; +} void Room::addActor(Actor actor) const { - this->actors->push_back(std::move(actor)); + this->actors->pushObject(fromVector2(actor.getTilePosition()), + std::move(actor)); } -void Room::removeActor(Vector2 tilePosition) const { - for (auto it = actors->begin(); it < actors->end(); ++it) { - if (it->getTilePosition().x == tilePosition.x && - it->getTilePosition().y == tilePosition.y) { - actors->erase(it); - } - } +void Room::removeActor(IVector tilePosition) const { + this->actors->removeObject(tilePosition); } From 34aae6e1c7b15be31d828b12a264ddbe1fca5bd2 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Sun, 22 Mar 2026 02:00:00 +0700 Subject: [PATCH 11/35] Implement hotkey services --- include/editor/defaultHotkey.hpp | 12 ++++++ include/editor/editor.hpp | 3 ++ .../editor/services/configurationService.hpp | 2 + include/editor/services/hotkeyService.hpp | 26 ++++++++++++ include/editor/widgets/fileTab.hpp | 2 + src/editor/childWindows/settingsWindow.cpp | 2 +- src/editor/editor.cpp | 8 +++- src/editor/screens/projectScreen.cpp | 37 +++++++++++----- src/editor/screens/welcomeScreen.cpp | 4 ++ src/editor/services/configurationService.cpp | 17 +++++--- src/editor/services/editorGuiService.cpp | 5 ++- src/editor/services/hotkeyService.cpp | 42 +++++++++++++++++++ src/editor/widgets/fileTab.cpp | 25 +++++++---- 13 files changed, 157 insertions(+), 28 deletions(-) create mode 100644 include/editor/defaultHotkey.hpp create mode 100644 include/editor/services/hotkeyService.hpp create mode 100644 src/editor/services/hotkeyService.cpp diff --git a/include/editor/defaultHotkey.hpp b/include/editor/defaultHotkey.hpp new file mode 100644 index 0000000..1b4c780 --- /dev/null +++ b/include/editor/defaultHotkey.hpp @@ -0,0 +1,12 @@ +#include "raylib.h" +#include +#include +#include +const std::vector>> DEFAULT_HOTKEYS = { + {"toggle_debug", std::vector{KEY_F3}}, + {"close_tab", std::vector{KEY_LEFT_CONTROL, KEY_W}}, + {"open_project", std::vector{KEY_LEFT_CONTROL, KEY_O}}, + {"save_file", std::vector{KEY_LEFT_CONTROL, KEY_S}}, + {"undo", std::vector{KEY_LEFT_CONTROL, KEY_Z}}, + {"redo", std::vector{KEY_LEFT_CONTROL, KEY_Y}}, +}; diff --git a/include/editor/editor.hpp b/include/editor/editor.hpp index 8cb5010..d0c1588 100644 --- a/include/editor/editor.hpp +++ b/include/editor/editor.hpp @@ -7,6 +7,7 @@ #include "services/fileSystemService.hpp" #include "services/themeService.hpp" #include "services/translationService.hpp" +#include "services/hotkeyService.hpp" #include #include @@ -29,6 +30,7 @@ class Editor { // the current editor gui service, responsible for managing the gui. ThemeService themeService; EditorGuiService guiService; + HotkeyService hotkeyService; public: Editor(); @@ -44,6 +46,7 @@ class Editor { ThemeService &getThemeService(); FileSystemService &getFs(); ConfigurationService &getConfiguration(); + HotkeyService &getHotkeyService(); Project *getProject() const; void setProject(const std::string &path); // this sets the icon of the editor. diff --git a/include/editor/services/configurationService.hpp b/include/editor/services/configurationService.hpp index 0476af9..63545dc 100644 --- a/include/editor/services/configurationService.hpp +++ b/include/editor/services/configurationService.hpp @@ -13,7 +13,9 @@ class ConfigurationService { public: ConfigurationService(); std::string getStringValue(const std::string &key); + std::string getStringValue(const std::string& field, const std::string &key); void setStringValue(const std::string &key, const std::string &value); + void setStringValue(const std::string& field, const std::string &key, const std::string &value); void saveConfiguration(); }; diff --git a/include/editor/services/hotkeyService.hpp b/include/editor/services/hotkeyService.hpp new file mode 100644 index 0000000..94befdc --- /dev/null +++ b/include/editor/services/hotkeyService.hpp @@ -0,0 +1,26 @@ + +#include "raylib.h" +#include +#include +#include + +#ifndef RPGPP_HOTKEYSERVICE_H +#define RPGPP_HOTKEYSERVICE_H + +class Editor; + +class HotkeyService { + private: + std::multimap> hotkeysCb; + std::unordered_map> hotkeyMap; + std::unordered_map activatedHotkey; + public: + HotkeyService(); + void registerHotkeyCallback(std::string keyId, std::function cb); + void unregisterHotkeyCallback(std::string keyId); + void addHotkey(std::string keyId, std::vector keys); + void removeHotkey(std::string keyId); + void fire(); +}; + +#endif diff --git a/include/editor/widgets/fileTab.hpp b/include/editor/widgets/fileTab.hpp index 71b0d05..cf364ce 100644 --- a/include/editor/widgets/fileTab.hpp +++ b/include/editor/widgets/fileTab.hpp @@ -31,6 +31,7 @@ class FileTab : public tgui::Tabs { tgui::RenderStates &states, int idx, bool roundedCorners, float borderWidth, float usableHeight, tgui::Sprite &close) const; + void closeAndOpenNextTab(std::size_t idx); public: bool useExternalMouseEvent = false; @@ -63,6 +64,7 @@ class FileTab : public tgui::Tabs { bool select(std::size_t i); size_t addFileTab(const std::string &path, const std::string &fileName); + void closeCurrentTab(); protected: Widget::Ptr clone() const override; diff --git a/src/editor/childWindows/settingsWindow.cpp b/src/editor/childWindows/settingsWindow.cpp index 3867912..587f212 100644 --- a/src/editor/childWindows/settingsWindow.cpp +++ b/src/editor/childWindows/settingsWindow.cpp @@ -46,10 +46,10 @@ SettingsWindow::SettingsWindow(const std::string &title) : PopupWindow(title) { languageSelector->onItemSelect.connect([&](const tgui::String &item) { ConfigurationService &configService = Editor::instance->getConfiguration(); + ts.setLanguage(ts.getLanguageIdentifierByKey(item.toStdString())); configService.setStringValue("language", ts.getCurrentLanguage()); configService.saveConfiguration(); - ts.setLanguage(ts.getLanguageIdentifierByKey(item.toStdString())); // Can't think of a way to reload the menu bar without recreating it Editor::instance->getGui().initMenuBar(); if (auto ptr = Editor::instance->getGui().menuBar.lock()) { diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 7c7434b..a317323 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -4,14 +4,18 @@ #include "services/editorGuiService.hpp" #include "services/fileSystemService.hpp" #include "services/translationService.hpp" +#include "defaultHotkey.hpp" #include Editor *Editor::instance; Editor::Editor() : configurationService(), translationService(this), themeService(this), - project{nullptr} { + hotkeyService(), project{nullptr} { instance = this; + for (const auto &[name, keys] : DEFAULT_HOTKEYS) { + hotkeyService.addHotkey(name, keys); + } } void Editor::setAppIcon(const std::string &icon_path) { @@ -29,6 +33,8 @@ ThemeService &Editor::getThemeService() { return themeService; } FileSystemService &Editor::getFs() { return fileSystem; } +HotkeyService &Editor::getHotkeyService() { return hotkeyService; } + Project *Editor::getProject() const { return project.get(); } ConfigurationService &Editor::getConfiguration() { diff --git a/src/editor/screens/projectScreen.cpp b/src/editor/screens/projectScreen.cpp index 3fe28e7..3bd890f 100644 --- a/src/editor/screens/projectScreen.cpp +++ b/src/editor/screens/projectScreen.cpp @@ -62,6 +62,23 @@ void ProjectScreen::leftMouseReleased(int x, int y) { void ProjectScreen::bindMenuBar(tgui::MenuBar::Ptr menuBarPtr) { auto &ts = Editor::instance->getTranslations(); + + auto saveAction = [this] { + if (!openedFiles.empty()) { + tgui::String currentFile = fileTabs->getSelectedId(); + auto &projectFile = openedFiles.at(currentFile); + projectFile->saveFile(projectFile->getFilePath()); + } + }; + + auto undoAction = [this] { + getCurrentFile().getView().undoAction(); + }; + + auto redoAction = [this] { + getCurrentFile().getView().redoAction(); + }; + std::vector saveFileHierarchy = { ts.getKey("menu.file._label"), ts.getKey("menu.file.save_file")}; std::vector undoHierarchy = {ts.getKey("menu.edit._label"), @@ -69,21 +86,17 @@ void ProjectScreen::bindMenuBar(tgui::MenuBar::Ptr menuBarPtr) { std::vector redoHierarchy = {ts.getKey("menu.edit._label"), ts.getKey("menu.edit.redo")}; menuBarPtr->setMenuItemEnabled(saveFileHierarchy, true); - menuBarPtr->connectMenuItem(saveFileHierarchy, [this] { - if (!openedFiles.empty()) { - tgui::String currentFile = fileTabs->getSelectedId(); - auto &projectFile = openedFiles.at(currentFile); - projectFile->saveFile(projectFile->getFilePath()); - } - }); + menuBarPtr->connectMenuItem(saveFileHierarchy, saveAction); menuBarPtr->setMenuItemEnabled(undoHierarchy, true); - menuBarPtr->connectMenuItem( - undoHierarchy, [this] { getCurrentFile().getView().undoAction(); }); + menuBarPtr->connectMenuItem(undoHierarchy, undoAction); menuBarPtr->setMenuItemEnabled(redoHierarchy, true); - menuBarPtr->connectMenuItem( - redoHierarchy, [this] { getCurrentFile().getView().redoAction(); }); + menuBarPtr->connectMenuItem(redoHierarchy, redoAction); + + Editor::instance->getHotkeyService().registerHotkeyCallback("save_file", saveAction); + Editor::instance->getHotkeyService().registerHotkeyCallback("undo", undoAction); + Editor::instance->getHotkeyService().registerHotkeyCallback("redo", redoAction); } void ProjectScreen::initItems(tgui::Group::Ptr layout) { @@ -166,6 +179,8 @@ void ProjectScreen::initItems(tgui::Group::Ptr layout) { } }); + Editor::instance->getHotkeyService().registerHotkeyCallback("close_tab", [this] { fileTabs->closeCurrentTab(); }); + tabsContainer->add(fileTabs); layout->add(tabsContainer); diff --git a/src/editor/screens/welcomeScreen.cpp b/src/editor/screens/welcomeScreen.cpp index a33f282..755e37e 100644 --- a/src/editor/screens/welcomeScreen.cpp +++ b/src/editor/screens/welcomeScreen.cpp @@ -78,6 +78,10 @@ void screens::WelcomeScreen::initItems(tgui::Group::Ptr layout) { }); }); + Editor::instance->getHotkeyService().registerHotkeyCallback("open_project", [] { + Editor::instance->getFs().promptOpenProject(); + }); + openProjButton->onPress( [] { Editor::instance->getFs().promptOpenProject(); }); diff --git a/src/editor/services/configurationService.cpp b/src/editor/services/configurationService.cpp index c11d111..d9c36f6 100644 --- a/src/editor/services/configurationService.cpp +++ b/src/editor/services/configurationService.cpp @@ -12,19 +12,26 @@ ConfigurationService::ConfigurationService() { } }; -std::string ConfigurationService::getStringValue(const std::string &key) { - if (this->iniStructure[GENERAL_CONF_FIELD].has(key)) - return this->iniStructure[GENERAL_CONF_FIELD][key]; +std::string ConfigurationService::getStringValue(const std::string& field, const std::string &key) { + if (this->iniStructure[field].has(key)) + return this->iniStructure[field][key]; std::stringstream ss; ss << "configuration key doesn't exist" << key; throw std::runtime_error(ss.str()); } +std::string ConfigurationService::getStringValue(const std::string &key) { + return this->getStringValue(GENERAL_CONF_FIELD, key); +} + +void ConfigurationService::setStringValue(const std::string& field, const std::string &key, const std::string &value) { + this->iniStructure[field].set(key, value); +} + void ConfigurationService::setStringValue(const std::string &key, const std::string &value) { - this->iniStructure[GENERAL_CONF_FIELD].set(key, value); + this->setStringValue(GENERAL_CONF_FIELD, key, value); } - void ConfigurationService::saveConfiguration() { this->iniFile->write(this->iniStructure); } diff --git a/src/editor/services/editorGuiService.cpp b/src/editor/services/editorGuiService.cpp index 00be0d0..136a95d 100644 --- a/src/editor/services/editorGuiService.cpp +++ b/src/editor/services/editorGuiService.cpp @@ -39,6 +39,8 @@ void EditorGuiService::init() { InitWindow(BASE_WINDOW_WIDTH, BASE_WINDOW_HEIGHT, "RPG++ Editor"); InitAudioDevice(); + Editor::instance->getHotkeyService().registerHotkeyCallback("toggle_debug", [this]() { perfOverlay.Toggle(); }); + this->resetUi(); } @@ -91,8 +93,7 @@ void EditorGuiService::uiLoop() { tgui::Theme::addRendererInheritanceParent("RoomToolbox", "Tabs"); // main loop. while (!WindowShouldClose()) { - if (IsKeyPressed(KEY_F3)) - perfOverlay.Toggle(); + Editor::instance->getHotkeyService().fire(); perfOverlay.Update(); cg->handleEvents(); diff --git a/src/editor/services/hotkeyService.cpp b/src/editor/services/hotkeyService.cpp new file mode 100644 index 0000000..e3380b6 --- /dev/null +++ b/src/editor/services/hotkeyService.cpp @@ -0,0 +1,42 @@ +#include "services/hotkeyService.hpp" +#include "raylib.h" + +HotkeyService::HotkeyService() {} + +void HotkeyService::registerHotkeyCallback(std::string keyId, std::function cb) { + activatedHotkey[keyId] = false; + hotkeysCb.insert({keyId, cb}); +} + +void HotkeyService::unregisterHotkeyCallback(std::string keyId) { + hotkeysCb.erase(keyId); +} + +void HotkeyService::addHotkey(std::string keyId, std::vector keys) { + activatedHotkey[keyId] = false; + hotkeyMap[keyId] = keys; +} + +void HotkeyService::removeHotkey(std::string keyId) { + hotkeyMap.erase(keyId); +} + +void HotkeyService::fire() { + for (auto &[keyId, keys] : hotkeyMap) { + bool allDown = true; + for (KeyboardKey k : keys) { + if (!IsKeyDown(k)) { + allDown = false; + activatedHotkey[keyId] = false; + break; + } + } + if (allDown && !activatedHotkey[keyId]) { + auto range = hotkeysCb.equal_range(keyId); + for (auto it = range.first; it != range.second; ++it) { + it->second(); + } + activatedHotkey[keyId] = true; + } + } +} diff --git a/src/editor/widgets/fileTab.cpp b/src/editor/widgets/fileTab.cpp index 94bbe04..3b60264 100644 --- a/src/editor/widgets/fileTab.cpp +++ b/src/editor/widgets/fileTab.cpp @@ -44,6 +44,16 @@ FileTab::Ptr FileTab::copy(FileTab::ConstPtr widget) { return nullptr; } +void FileTab::closeAndOpenNextTab(std::size_t i) { + int prevSelected = m_selectedTab; + tgui::String prevId = m_tabs[i].id; + remove(i); + onTabClose.emit(this, prevId); + if (prevSelected == i && m_tabs.size() > 0) { + select(std::min(i, m_tabs.size() - 1)); + } +} + bool FileTab::leftMousePressed(Vector2f pos) { pos -= getPosition(); @@ -68,14 +78,7 @@ bool FileTab::leftMousePressed(Vector2f pos) { } else if (pos.x >= tabStart + (m_tabs[i].width - MARGIN_LR - CLOSE_BUTTON_SIZE) && pos.x < tabEnd - MARGIN_LR) { - // Handle close button click - int prevSelected = m_selectedTab; - tgui::String prevId = m_tabs[i].id; - remove(i); - onTabClose.emit(this, prevId); - if (prevSelected == i && m_tabs.size() > 0) { - select(std::min(i, m_tabs.size() - 1)); - } + closeAndOpenNextTab(i); break; } @@ -158,6 +161,12 @@ void FileTab::leftMouseReleased(tgui::Vector2f pos) { } } +void FileTab::closeCurrentTab() { + if (m_selectedTab == -1) + return; + closeAndOpenNextTab(m_selectedTab); +} + bool FileTab::select(std::size_t index) { // Don't select a tab that is already selected if (m_selectedTab == static_cast(index)) From 328df6a95cd90c1c4a5e07cb2d973b68ba9c1481 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Sun, 22 Mar 2026 10:27:51 +0700 Subject: [PATCH 12/35] bind "New Project" in menu bar --- include/editor/defaultHotkey.hpp | 1 + include/editor/screens/guiScreen.hpp | 2 +- include/editor/screens/projectScreen.hpp | 2 +- include/editor/services/fileSystemService.hpp | 1 + src/editor/childWindows/settingsWindow.cpp | 2 +- src/editor/project.cpp | 2 +- src/editor/screens/projectScreen.cpp | 11 ++++---- src/editor/screens/welcomeScreen.cpp | 20 +------------- src/editor/services/editorGuiService.cpp | 26 +++++++++++++------ src/editor/services/fileSystemService.cpp | 18 +++++++++++++ 10 files changed, 49 insertions(+), 36 deletions(-) diff --git a/include/editor/defaultHotkey.hpp b/include/editor/defaultHotkey.hpp index 1b4c780..ee1b592 100644 --- a/include/editor/defaultHotkey.hpp +++ b/include/editor/defaultHotkey.hpp @@ -5,6 +5,7 @@ const std::vector>> DEFAULT_HOTKEYS = { {"toggle_debug", std::vector{KEY_F3}}, {"close_tab", std::vector{KEY_LEFT_CONTROL, KEY_W}}, + {"new_project", std::vector{KEY_LEFT_CONTROL, KEY_N}}, {"open_project", std::vector{KEY_LEFT_CONTROL, KEY_O}}, {"save_file", std::vector{KEY_LEFT_CONTROL, KEY_S}}, {"undo", std::vector{KEY_LEFT_CONTROL, KEY_Z}}, diff --git a/include/editor/screens/guiScreen.hpp b/include/editor/screens/guiScreen.hpp index aae5f3d..2d7d891 100644 --- a/include/editor/screens/guiScreen.hpp +++ b/include/editor/screens/guiScreen.hpp @@ -17,7 +17,7 @@ class UIScreen { // to create widgets. } - virtual void bindMenuBar(tgui::MenuBar::Ptr menubar) {} + virtual void bindMenuBarAndHK(tgui::MenuBar::Ptr menubar) {} virtual void mouseMove(int x, int y) {} virtual void leftMouseReleased(int x, int y) {} virtual void unloadScreen() { diff --git a/include/editor/screens/projectScreen.hpp b/include/editor/screens/projectScreen.hpp index 00d2247..d7ec927 100644 --- a/include/editor/screens/projectScreen.hpp +++ b/include/editor/screens/projectScreen.hpp @@ -58,7 +58,7 @@ class ProjectScreen : public UIScreen { void addResourceButtons(EngineFileType fileType); void mouseMove(int x, int y) override; void leftMouseReleased(int x, int y) override; - void bindMenuBar(tgui::MenuBar::Ptr menubar) override; + void bindMenuBarAndHK(tgui::MenuBar::Ptr menubar) override; void layoutReload(); ProjectFile &getCurrentFile(); void initItems(tgui::Group::Ptr layout) override; diff --git a/include/editor/services/fileSystemService.hpp b/include/editor/services/fileSystemService.hpp index 87a6d5a..87be816 100644 --- a/include/editor/services/fileSystemService.hpp +++ b/include/editor/services/fileSystemService.hpp @@ -32,6 +32,7 @@ class FileSystemService { public: FileSystemService(); void unload(); + void promptNewProject(); void promptOpenProject(); std::string &getTypeName(EngineFileType fileType); std::array &getTypeNames(); diff --git a/src/editor/childWindows/settingsWindow.cpp b/src/editor/childWindows/settingsWindow.cpp index 587f212..0eb210e 100644 --- a/src/editor/childWindows/settingsWindow.cpp +++ b/src/editor/childWindows/settingsWindow.cpp @@ -53,7 +53,7 @@ SettingsWindow::SettingsWindow(const std::string &title) : PopupWindow(title) { // Can't think of a way to reload the menu bar without recreating it Editor::instance->getGui().initMenuBar(); if (auto ptr = Editor::instance->getGui().menuBar.lock()) { - Editor::instance->getGui().currentScreen->bindMenuBar(ptr); + Editor::instance->getGui().currentScreen->bindMenuBarAndHK(ptr); } }); diff --git a/src/editor/project.cpp b/src/editor/project.cpp index 064a3a1..4b0ed63 100644 --- a/src/editor/project.cpp +++ b/src/editor/project.cpp @@ -78,7 +78,7 @@ void Project::create(const std::string &dirPath, const std::string &title) { Editor::instance->setProject(filePath.u8string()); Editor::instance->getGui().setScreen( - std::make_unique(), false); + std::make_unique(), true); } json Project::toJson() { diff --git a/src/editor/screens/projectScreen.cpp b/src/editor/screens/projectScreen.cpp index 3bd890f..e464920 100644 --- a/src/editor/screens/projectScreen.cpp +++ b/src/editor/screens/projectScreen.cpp @@ -60,8 +60,9 @@ void ProjectScreen::leftMouseReleased(int x, int y) { static_cast(y - tabsContainer->getPosition().y)}); } -void ProjectScreen::bindMenuBar(tgui::MenuBar::Ptr menuBarPtr) { +void ProjectScreen::bindMenuBarAndHK(tgui::MenuBar::Ptr menuBarPtr) { auto &ts = Editor::instance->getTranslations(); + auto &hks = Editor::instance->getHotkeyService(); auto saveAction = [this] { if (!openedFiles.empty()) { @@ -94,14 +95,14 @@ void ProjectScreen::bindMenuBar(tgui::MenuBar::Ptr menuBarPtr) { menuBarPtr->setMenuItemEnabled(redoHierarchy, true); menuBarPtr->connectMenuItem(redoHierarchy, redoAction); - Editor::instance->getHotkeyService().registerHotkeyCallback("save_file", saveAction); - Editor::instance->getHotkeyService().registerHotkeyCallback("undo", undoAction); - Editor::instance->getHotkeyService().registerHotkeyCallback("redo", redoAction); + hks.registerHotkeyCallback("save_file", saveAction); + hks.registerHotkeyCallback("undo", undoAction); + hks.registerHotkeyCallback("redo", redoAction); } void ProjectScreen::initItems(tgui::Group::Ptr layout) { if (auto ptr = Editor::instance->getGui().menuBar.lock()) { - bindMenuBar(ptr); + bindMenuBarAndHK(ptr); } // Commentary: diff --git a/src/editor/screens/welcomeScreen.cpp b/src/editor/screens/welcomeScreen.cpp index 755e37e..0521915 100644 --- a/src/editor/screens/welcomeScreen.cpp +++ b/src/editor/screens/welcomeScreen.cpp @@ -59,28 +59,10 @@ void screens::WelcomeScreen::initItems(tgui::Group::Ptr layout) { &tgui::Button::setText); openProjButton->setTextSize(ACTION_BUTTON_SIZE); - - newProjectDialog = NewProjectWindow::create(); - newProjButton->onPress([this] { - auto childDialog = tgui::ChildWindow::create(); - - newProjectDialog->init(Editor::instance->getGui().gui.get()); - newProjectDialog->fileField->setSelectingDirectory(true); - newProjectDialog->confirmButton->onPress([this] { - std::string title = - newProjectDialog->titleField->getText().toStdString(); - std::string dirPath = - newProjectDialog->fileField->getChosenPath().toStdString(); - if (!title.empty() && !dirPath.empty()) { - Project::create(dirPath, title); - } - }); + Editor::instance->getFs().promptNewProject(); }); - Editor::instance->getHotkeyService().registerHotkeyCallback("open_project", [] { - Editor::instance->getFs().promptOpenProject(); - }); openProjButton->onPress( [] { Editor::instance->getFs().promptOpenProject(); }); diff --git a/src/editor/services/editorGuiService.cpp b/src/editor/services/editorGuiService.cpp index 136a95d..f31fffc 100644 --- a/src/editor/services/editorGuiService.cpp +++ b/src/editor/services/editorGuiService.cpp @@ -39,9 +39,15 @@ void EditorGuiService::init() { InitWindow(BASE_WINDOW_WIDTH, BASE_WINDOW_HEIGHT, "RPG++ Editor"); InitAudioDevice(); - Editor::instance->getHotkeyService().registerHotkeyCallback("toggle_debug", [this]() { perfOverlay.Toggle(); }); - + auto &hks = Editor::instance->getHotkeyService(); this->resetUi(); + hks.registerHotkeyCallback("toggle_debug", [this]() { perfOverlay.Toggle(); }); + hks.registerHotkeyCallback("new_project", [] { + Editor::instance->getFs().promptNewProject(); + }); + hks.registerHotkeyCallback("open_project", [] { + Editor::instance->getFs().promptOpenProject(); + }); } void EditorGuiService::resetUi() { @@ -241,16 +247,20 @@ void EditorGuiService::initMenuBar() { this->menuBar = menuBarPtr; auto &ts = Editor::instance->getTranslations(); - const auto &fileOptionsTranslation = ts.getKey("menu.file._label"); - const auto &fileOpenProjectTranslation = + const auto &fileT = ts.getKey("menu.file._label"); + const auto &fileOpenProjectT = ts.getKey("menu.file.open_project"); + const auto &fileNewProjectT = ts.getKey("menu.file.new_project"); - menuBarPtr->addMenu(fileOptionsTranslation); - menuBarPtr->addMenuItem(ts.getKey("menu.file.new_project")); - menuBarPtr->addMenuItem(fileOpenProjectTranslation); + menuBarPtr->addMenu(fileT); + menuBarPtr->addMenuItem(fileNewProjectT); + menuBarPtr->connectMenuItem( + {fileT, fileNewProjectT}, + [] { Editor::instance->getFs().promptNewProject(); }); + menuBarPtr->addMenuItem(fileOpenProjectT); menuBarPtr->addMenuItem(ts.getKey("menu.file.save_file")); menuBarPtr->connectMenuItem( - {fileOptionsTranslation, fileOpenProjectTranslation}, + {fileT, fileOpenProjectT}, [] { Editor::instance->getFs().promptOpenProject(); }); menuBarPtr->addMenu(ts.getKey("menu.edit._label")); diff --git a/src/editor/services/fileSystemService.cpp b/src/editor/services/fileSystemService.cpp index 27ce788..0b3288a 100644 --- a/src/editor/services/fileSystemService.cpp +++ b/src/editor/services/fileSystemService.cpp @@ -1,5 +1,6 @@ #include "services/fileSystemService.hpp" #include "TGUI/Widgets/FileDialog.hpp" +#include "widgets/newProjectWindow.hpp" #include #include #include @@ -40,6 +41,23 @@ FileSystemService::FileSystemService() { void FileSystemService::unload() { NFD_Quit(); } +void FileSystemService::promptNewProject() { + auto newProjectDialog = NewProjectWindow::create(); + + newProjectDialog->init(Editor::instance->getGui().gui.get()); + newProjectDialog->fileField->setSelectingDirectory(true); + newProjectDialog->confirmButton->onPress([newProjectDialog] { + std::string title = + newProjectDialog->titleField->getText().toStdString(); + std::string dirPath = + newProjectDialog->fileField->getChosenPath().toStdString(); + if (!title.empty() && !dirPath.empty()) { + Project::create(dirPath, title); + } + newProjectDialog->window->close(); + }); +} + void FileSystemService::promptOpenProject() { auto files = tgui::FileDialog::create(); files->setFileTypeFilters({{"RPG++ Project", {"*.rpgpp"}}}); From afe7216a2b6169b0b4c7e9a64ff38948fa2947b4 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Sun, 22 Mar 2026 10:48:27 +0700 Subject: [PATCH 13/35] Small change to HKS --- include/editor/services/hotkeyService.hpp | 8 ++++---- src/editor/services/hotkeyService.cpp | 13 +++++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/include/editor/services/hotkeyService.hpp b/include/editor/services/hotkeyService.hpp index 94befdc..f47883d 100644 --- a/include/editor/services/hotkeyService.hpp +++ b/include/editor/services/hotkeyService.hpp @@ -16,10 +16,10 @@ class HotkeyService { std::unordered_map activatedHotkey; public: HotkeyService(); - void registerHotkeyCallback(std::string keyId, std::function cb); - void unregisterHotkeyCallback(std::string keyId); - void addHotkey(std::string keyId, std::vector keys); - void removeHotkey(std::string keyId); + void registerHotkeyCallback(const std::string &keyId, std::function cb); + void unregisterHotkeyCallback(const std::string &keyId); + void addHotkey(const std::string &keyId, std::vector keys); + void removeHotkey(const std::string &keyId); void fire(); }; diff --git a/src/editor/services/hotkeyService.cpp b/src/editor/services/hotkeyService.cpp index e3380b6..8e68714 100644 --- a/src/editor/services/hotkeyService.cpp +++ b/src/editor/services/hotkeyService.cpp @@ -3,26 +3,31 @@ HotkeyService::HotkeyService() {} -void HotkeyService::registerHotkeyCallback(std::string keyId, std::function cb) { +void HotkeyService::registerHotkeyCallback(const std::string& keyId, std::function cb) { activatedHotkey[keyId] = false; hotkeysCb.insert({keyId, cb}); } -void HotkeyService::unregisterHotkeyCallback(std::string keyId) { +void HotkeyService::unregisterHotkeyCallback(const std::string& keyId) { hotkeysCb.erase(keyId); + if (hotkeyMap.find(keyId) == hotkeyMap.end()) + activatedHotkey.erase(keyId); } -void HotkeyService::addHotkey(std::string keyId, std::vector keys) { +void HotkeyService::addHotkey(const std::string& keyId, std::vector keys) { activatedHotkey[keyId] = false; hotkeyMap[keyId] = keys; } -void HotkeyService::removeHotkey(std::string keyId) { +void HotkeyService::removeHotkey(const std::string& keyId) { hotkeyMap.erase(keyId); + if (hotkeysCb.find(keyId) == hotkeysCb.end()) + activatedHotkey.erase(keyId); } void HotkeyService::fire() { for (auto &[keyId, keys] : hotkeyMap) { + if (keys.empty()) continue; bool allDown = true; for (KeyboardKey k : keys) { if (!IsKeyDown(k)) { From c2b5500feb9b652b87fc95d179c0e06c77f92742 Mon Sep 17 00:00:00 2001 From: CDevv Date: Sun, 22 Mar 2026 11:24:24 +0200 Subject: [PATCH 14/35] Let interactable properties be owned by a unique_ptr + Introduce PropsContainer and remove unused methods Signed-off-by: CDevv --- include/actor.hpp | 2 +- include/collisionsContainer.hpp | 4 - include/gamedata.hpp | 3 +- include/interactable.hpp | 2 +- include/propsContainer.hpp | 13 +++ include/room.hpp | 29 ++----- src/actor.cpp | 2 +- src/editor/actions/eraseTileAction.cpp | 3 +- src/editor/actions/placeTileAction.cpp | 21 ++--- src/editor/project.cpp | 23 +++-- src/editor/views/roomView.cpp | 12 ++- src/game.cpp | 4 +- src/interactable.cpp | 11 +-- src/player.cpp | 17 ++-- src/room.cpp | 114 ++++++++----------------- 15 files changed, 110 insertions(+), 150 deletions(-) create mode 100644 include/propsContainer.hpp diff --git a/include/actor.hpp b/include/actor.hpp index e806baf..86e5f17 100644 --- a/include/actor.hpp +++ b/include/actor.hpp @@ -66,7 +66,7 @@ class Actor : public ISaveable { Actor(std::unique_ptr tileSet, Vector2 atlasPos, std::string tileSetSource); /** Constructor that takes an ActorBin binary structure */ - Actor(ActorBin bin); + Actor(const ActorBin &bin); /** Dump this Actor's data to a nlohmann::json object. */ json dumpJson() override; /** Unload routine. The UnloadTexture function will called here. */ diff --git a/include/collisionsContainer.hpp b/include/collisionsContainer.hpp index bed04d8..8618de2 100644 --- a/include/collisionsContainer.hpp +++ b/include/collisionsContainer.hpp @@ -3,13 +3,9 @@ #include "baseContainer.hpp" #include -#include /** A container of collision tiles to be used by a Room */ class CollisionsContainer : public BaseContainer { - private: - /** A vector that contains the tile positions of the collision tiles */ - public: /** Empty constructor */ CollisionsContainer(); diff --git a/include/gamedata.hpp b/include/gamedata.hpp index 333cdcc..8caf0ba 100644 --- a/include/gamedata.hpp +++ b/include/gamedata.hpp @@ -85,6 +85,7 @@ struct TileSetBin { struct ImageBin { std::vector data; int dataSize; + std::string ext; }; struct MusicBin { @@ -127,7 +128,7 @@ struct GameData { std::map tilesets; std::map interactables; std::vector rooms; - std::vector actors; + std::map actors; std::vector props; std::map dialogues; std::map music; diff --git a/include/interactable.hpp b/include/interactable.hpp index 63db89a..7fe1ebd 100644 --- a/include/interactable.hpp +++ b/include/interactable.hpp @@ -31,7 +31,7 @@ class Interactable : public ISaveable { /** Whether this interactable will be interacted with on touch. */ bool onTouch; /** A json object describing the properties of an interactable. */ - nlohmann::json *props; + std::unique_ptr props; /** Script file path */ std::string scriptPath; diff --git a/include/propsContainer.hpp b/include/propsContainer.hpp new file mode 100644 index 0000000..4181ae6 --- /dev/null +++ b/include/propsContainer.hpp @@ -0,0 +1,13 @@ +#ifndef _RPGPP_PROPSCONTAINER_H +#define _RPGPP_PROPSCONTAINER_H + +#include "baseContainer.hpp" +#include "prop.hpp" +#include + +class PropsContainer : public BaseContainer> { + public: + PropsContainer() = default; +}; + +#endif \ No newline at end of file diff --git a/include/room.hpp b/include/room.hpp index 2bbe294..c978932 100644 --- a/include/room.hpp +++ b/include/room.hpp @@ -1,6 +1,7 @@ #ifndef _RPGPP_ROOM_H #define _RPGPP_ROOM_H +#include "propsContainer.hpp" constexpr const char *DEFAULT_PLAYER_PATH = "actors/playerActor.ractor"; class Player; @@ -40,11 +41,8 @@ class Room : public ISaveable { std::unique_ptr interactables; /** Container of the collision tiles */ std::unique_ptr collisions; - /** A collection of the Props in this Room. - FIXME: Change to Container system, due to segmentation issues, couldn't - be changed! - */ - std::unique_ptr> props; + /** Container of the props */ + std::unique_ptr props; /** This Room's TileMap, which contains all placed tiles. */ std::unique_ptr tileMap; /** A collection of all Actors in this Room */ @@ -102,29 +100,14 @@ class Room : public ISaveable { /** Set the start tile position. */ void setStartTile(Vector2 newStartTile); - std::vector getCollisionTiles() const; /** Get a reference to the CollisionsContainer of this Room. */ CollisionsContainer &getCollisions() const; /** Get a reference to the InteractablesContainer of this Room. */ InteractablesContainer &getInteractables() const; - - /** Get a reference to the Props container (vector). */ - std::vector &getProps() const; - /** Add a Prop to this Room. */ - void addProp(Prop prop) const; - /** Remove a prop from this room using a world tile position. */ - void removeProp(Vector2 worldPos) const; - /** Get the Prop at the specified tile position. */ - Prop *getPropAt(IVector worldPos) const; - + /** Get a reference to the PropsContainer of this Room. */ + PropsContainer &getProps() const; /** Get a refernece to the collection of Actors. */ - std::vector &getActors(); - /** Add an actor to this Room - * @param actor The actor to be added to the Room's collection. - */ - void addActor(Actor actor) const; - /** Remove an Actor using a tilePosition. */ - void removeActor(IVector tilePosition) const; + ActorContainer &getActors() const; }; #endif diff --git a/src/actor.cpp b/src/actor.cpp index 4ca152a..3b828a7 100644 --- a/src/actor.cpp +++ b/src/actor.cpp @@ -119,7 +119,7 @@ Actor::Actor(std::unique_ptr tileSet, Vector2 atlasPos, (atlasTileSize.y * RPGPP_DRAW_MULTIPLIER) / 2}; } -Actor::Actor(ActorBin bin) { +Actor::Actor(const ActorBin &bin) { this->sourcePath = bin.name; this->position = Vector2{0, 0}; diff --git a/src/editor/actions/eraseTileAction.cpp b/src/editor/actions/eraseTileAction.cpp index 2922704..1206888 100644 --- a/src/editor/actions/eraseTileAction.cpp +++ b/src/editor/actions/eraseTileAction.cpp @@ -19,8 +19,7 @@ void EraseTileAction::execute() { data.room->getInteractables().removeObject(fromVector2(data.worldTile)); } break; case RoomLayer::LAYER_PROPS: { - data.room->removeProp({static_cast(data.worldTile.x), - static_cast(data.worldTile.y)}); + data.room->getProps().removeObject(fromVector2(data.worldTile)); } break; default: break; diff --git a/src/editor/actions/placeTileAction.cpp b/src/editor/actions/placeTileAction.cpp index 5aaa42c..404d4cb 100644 --- a/src/editor/actions/placeTileAction.cpp +++ b/src/editor/actions/placeTileAction.cpp @@ -6,7 +6,7 @@ #include "raylib.h" #include "room.hpp" #include "views/worldView.hpp" -#include +#include #include PlaceTileAction::PlaceTileAction(MapActionData a) : MapAction(a) {} @@ -37,11 +37,11 @@ void PlaceTileAction::execute() { } } break; case RoomLayer::LAYER_PROPS: { - Prop p(data.interactableFullPath); - p.setWorldTilePos({data.worldTile.x, data.worldTile.y}, - data.room->getWorldTileSize()); - if (p.getInteractable() != nullptr && p.getHasInteractable()) { - auto interType = p.getInteractable()->getType(); + auto p = std::make_unique(data.interactableFullPath); + p->setWorldTilePos({data.worldTile.x, data.worldTile.y}, + data.room->getWorldTileSize()); + if (p->getInteractable() != nullptr && p->getHasInteractable()) { + auto interType = p->getInteractable()->getType(); auto interNames = Editor::instance->getProject()->getInteractableNames(); std::string interFileName; @@ -57,9 +57,11 @@ void PlaceTileAction::execute() { char *txt = LoadFileText(interFileName.c_str()); nlohmann::json propJson = json::parse(txt); UnloadFileText(txt); - p.getInteractable()->setProps(propJson.at("props")); + p->getInteractable()->setProps(propJson.at("props")); } - data.room->addProp(std::move(p)); + // data.room->addProp(std::move(p)); + data.room->getProps().pushObject(fromVector2(data.worldTile), + std::move(p)); } break; default: break; @@ -78,8 +80,7 @@ void PlaceTileAction::undo() { data.room->getInteractables().removeObject(fromVector2(data.worldTile)); } break; case RoomLayer::LAYER_PROPS: { - data.room->removeProp({static_cast(data.worldTile.x), - static_cast(data.worldTile.y)}); + data.room->getProps().removeObject(fromVector2(data.worldTile)); } break; default: break; diff --git a/src/editor/project.cpp b/src/editor/project.cpp index 064a3a1..7aa6bca 100644 --- a/src/editor/project.cpp +++ b/src/editor/project.cpp @@ -1,4 +1,5 @@ #include "project.hpp" +#include "conversion.hpp" #include "dialogue.hpp" #include "dialogueParser.hpp" #include "editor.hpp" @@ -13,12 +14,10 @@ #include #include #include -#include #include #include #include #include -#include #include #include @@ -224,10 +223,10 @@ GameData Project::generateStruct() { std::unique_ptr room = std::make_unique(roomPath); roomBin.startPoint = IVector{static_cast(room->getStartTile().x), static_cast(room->getStartTile().y)}; - for (auto collisionVec : room->getCollisionTiles()) { + for (auto [pos, obj] : room->getCollisions().getObjects()) { IVector intVec; - intVec.x = static_cast(collisionVec.x); - intVec.y = static_cast(collisionVec.y); + intVec.x = static_cast(pos.x); + intVec.y = static_cast(pos.y); roomBin.collisions.push_back(intVec); } for (auto interactable : room->getInteractables().getList()) { @@ -242,17 +241,16 @@ GameData Project::generateStruct() { roomBin.interactables.push_back(intBin); } - for (auto &&prop : room->getProps()) { + for (auto &[pos, prop] : room->getProps().getObjects()) { PropInRoomBin pBin; - pBin.name = prop.getSourcePath(); - pBin.tilePos = IVector{static_cast(prop.getWorldTilePos().x), - static_cast(prop.getWorldTilePos().y)}; + pBin.name = prop->getSourcePath(); + pBin.tilePos = fromVector2(prop->getWorldTilePos()); pBin.propsCbor = - nlohmann::json::to_cbor(prop.getInteractable()->getProps()); + nlohmann::json::to_cbor(prop->getInteractable()->getProps()); roomBin.props.push_back(pBin); } - for (auto &&actor : room->getActors()) { + for (auto &[pos, actor] : room->getActors().getObjects()) { ActorInRoomBin aBin; aBin.name = actor.getSourcePath(); aBin.tilePos = IVector{static_cast(actor.getTilePosition().x), @@ -290,7 +288,7 @@ GameData Project::generateStruct() { } } - data.actors.push_back(actorBin); + data.actors[GetFileNameWithoutExt(actorPath.c_str())] = actorBin; } for (auto diagPath : getPaths(EngineFileType::FILE_DIALOGUE)) { @@ -304,6 +302,7 @@ GameData Project::generateStruct() { for (auto imagePath : getPaths(EngineFileType::FILE_IMAGE)) { Image img = LoadImage(imagePath.c_str()); ImageBin bin; + bin.ext = GetFileExtension(imagePath.c_str()); int fileSize = 0; diff --git a/src/editor/views/roomView.cpp b/src/editor/views/roomView.cpp index cae51c0..957b8e8 100644 --- a/src/editor/views/roomView.cpp +++ b/src/editor/views/roomView.cpp @@ -183,8 +183,8 @@ void RoomView::drawCanvas() { } // props - for (auto &&prop : room->getProps()) { - prop.draw(); + for (auto &[pos, prop] : room->getProps().getObjects()) { + prop->draw(); } DrawRectangleLinesEx(overlayRect, 2.0f, Fade(GRAY, 0.5f)); @@ -423,7 +423,13 @@ void RoomView::handleEditPress(tgui::Vector2f pos) { } break; case RoomLayer::LAYER_PROPS: { IVector tileMouse = getTileAtMouse(); - layerVisitor->prop = room->getPropAt(tileMouse); + if (room->getProps().objectExistsAtPosition(tileMouse)) { + layerVisitor->prop = + room->getProps().getObjectAtPosition(tileMouse).get(); + } else { + layerVisitor->prop = nullptr; + } + layerVisitor->group->removeAllWidgets(); mj::visit(*layerVisitor, layer); } diff --git a/src/game.cpp b/src/game.cpp index 01ff827..c5a9967 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -53,8 +53,8 @@ void Game::useBin(const std::string &filePath) { usesBin = true; for (const auto &[name, data] : gameData->images) { - Image image = - LoadImageFromMemory(".png", data.data.data(), data.dataSize); + Image image = LoadImageFromMemory(data.ext.c_str(), data.data.data(), + data.dataSize); Texture2D texture = LoadTextureFromImage(image); resources->addTexture(name, texture); UnloadImage(image); diff --git a/src/interactable.cpp b/src/interactable.cpp index 843153c..ca4186e 100644 --- a/src/interactable.cpp +++ b/src/interactable.cpp @@ -27,14 +27,14 @@ Interactable::Interactable(const std::string &path) { type = GetFileNameWithoutExt(path.c_str()); displayTitle = intJson.at("name"); - props = new nlohmann::json(intJson.at("props")); + props = std::make_unique(intJson.at("props")); scriptPath = intJson.at("script"); } Interactable::Interactable(const std::string &type, Vector2 tilePos, int tileSize) { this->type = type; - this->props = new nlohmann::json(); + this->props = std::make_unique(json::object()); this->valid = true; this->type = type; @@ -49,7 +49,8 @@ Interactable::Interactable(const std::string &type, Vector2 tilePos, Interactable::Interactable(InteractableInRoomBin bin) { this->type = bin.type; - this->props = new nlohmann::json(json::from_cbor(bin.propsCbor)); + this->props = + std::make_unique(json::from_cbor(bin.propsCbor)); Vector2 tilePos = {static_cast(bin.x), static_cast(bin.y)}; @@ -86,11 +87,11 @@ const std::string &Interactable::getType() const { return this->type; } void Interactable::setType(const std::string &type) { this->type = type; - this->props = new nlohmann::json(); + this->props = std::make_unique(json::object()); } void Interactable::setProps(nlohmann::json j) { - this->props = new nlohmann::json(j); + this->props = std::make_unique(j); } nlohmann::json &Interactable::getProps() { return *props; } diff --git a/src/player.cpp b/src/player.cpp index cc1e808..e99d4fa 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -1,4 +1,5 @@ #include "player.hpp" +#include "conversion.hpp" #include "interactable.hpp" #include #include @@ -96,8 +97,8 @@ void Player::handleCollision() { int worldTileSize = tileMap->getWorldTileSize(); // collision tiles - std::vector collisionTiles = this->room.getCollisionTiles(); - for (Vector2 v : collisionTiles) { + for (auto &[pos, obj] : room.getCollisions().getObjects()) { + Vector2 v = fromIVector(pos); Rectangle tileRect = Rectangle{v.x * worldTileSize, v.y * worldTileSize, static_cast(worldTileSize), static_cast(worldTileSize)}; @@ -109,8 +110,8 @@ void Player::handleCollision() { } // props - for (auto &&p : room.getProps()) { - if (CheckCollisionRecs(playerRect, p.getWorldCollisionRect())) { + for (auto &[pos, prop] : room.getProps().getObjects()) { + if (CheckCollisionRecs(playerRect, prop->getWorldCollisionRect())) { velocity = Vector2{0, 0}; break; } @@ -164,11 +165,11 @@ void Player::handleInteraction() { } } - for (auto &&p : room.getProps()) { - if (p.getHasInteractable()) { + for (auto &[pos, prop] : room.getProps().getObjects()) { + if (prop->getHasInteractable()) { if (CheckCollisionRecs(interactableArea, - p.getWorldCollisionRect())) { - p.getInteractable()->interact(); + prop->getWorldCollisionRect())) { + prop->getInteractable()->interact(); break; } } diff --git a/src/room.cpp b/src/room.cpp index d53419e..e8ce053 100644 --- a/src/room.cpp +++ b/src/room.cpp @@ -8,12 +8,14 @@ #include "interactable.hpp" #include "interactablesContainer.hpp" #include "prop.hpp" +#include "propsContainer.hpp" #include "tilemap.hpp" #include #include #include #include #include +#include #include using json = nlohmann::json; @@ -32,7 +34,7 @@ Room::Room() { this->musicSource = ""; this->interactables = std::make_unique(); this->collisions = std::make_unique(); - this->props = std::make_unique>(); + this->props = std::make_unique(); this->tileMap = std::unique_ptr{}; this->actors = std::make_unique(); this->player = std::unique_ptr{}; @@ -62,7 +64,7 @@ Room::Room(const std::string &fileName, int tileSize) { this->interactables = std::make_unique(); this->collisions = std::make_unique(); this->actors = std::make_unique(); - this->props = std::make_unique>(); + this->props = std::make_unique(); this->tileMap = std::make_unique(fileName); auto actor = std::make_unique(DEFAULT_PLAYER_PATH); @@ -91,13 +93,14 @@ Room::Room(const std::string &fileName, int tileSize) { int y = std::stoi(std::string(textSplit[1])); auto propVec = value; - auto p = Prop(propVec.at("src")); - p.setWorldTilePos(Vector2{static_cast(x), static_cast(y)}, - worldTileSize); + auto p = std::make_unique(propVec.at("src")); + p->setWorldTilePos( + Vector2{static_cast(x), static_cast(y)}, + worldTileSize); - p.getInteractable()->setProps(propVec.at("props")); + p->getInteractable()->setProps(propVec.at("props")); - addProp(std::move(p)); + props->pushObject({x, y}, std::move(p)); } std::map actorsVec = roomJson.at("actors"); @@ -113,7 +116,7 @@ Room::Room(const std::string &fileName, int tileSize) { a.setTilePosition(Vector2{static_cast(x), static_cast(y)}, Vector2{static_cast(worldTileSize), static_cast(worldTileSize)}); - addActor(std::move(a)); + actors->pushObject({x, y}, std::move(a)); } interactables->addJsonData(roomJson.at("interactables")); @@ -131,11 +134,13 @@ Room::Room(const RoomBin &bin) : Room() { this->interactables = std::make_unique(); this->collisions = std::make_unique(); this->actors = std::make_unique(); - this->props = std::make_unique>(); + this->props = std::make_unique(); this->tileMap = std::make_unique(bin); - auto actor = std::make_unique("actors/playerActor.ractor"); + auto &actorBin = Game::getBin().actors["playerActor"]; + + auto actor = std::make_unique(actorBin); actor->setTilePosition(Vector2{static_cast(bin.startPoint.x), static_cast(bin.startPoint.y)}, tileMap->getTileSet()->getTileSize()); @@ -151,29 +156,30 @@ Room::Room(const RoomBin &bin) : Room() { } for (auto const &propSource : bin.props) { - printf("a \n"); for (auto const &propBin : Game::getBin().props) { std::string actualSource = GetFileNameWithoutExt(propSource.name.c_str()); printf("%s ; %s \n", propBin.name.c_str(), actualSource.c_str()); if (propBin.name == actualSource) { printf("c \n"); - auto p = Prop(propBin); - p.setWorldTilePos( + auto p = std::make_unique(propBin); + p->setWorldTilePos( Vector2{static_cast(propSource.tilePos.x), static_cast(propSource.tilePos.y)}, worldTileSize); - p.getInteractable()->setProps( + p->getInteractable()->setProps( nlohmann::json::from_cbor(propSource.propsCbor)); - addProp(std::move(p)); + // addProp(std::move(*p)); + + props->pushObject(propSource.tilePos, std::move(p)); break; } } } for (const auto &actorSource : bin.actors) { - for (const auto &actorBin : Game::getBin().actors) { + for (const auto [name, actorBin] : Game::getBin().actors) { if (actorBin.name == actorSource.name) { auto a = Actor(actorBin); a.setTilePosition( @@ -181,7 +187,7 @@ Room::Room(const RoomBin &bin) : Room() { static_cast(actorSource.tilePos.y)}, Vector2{static_cast(worldTileSize), static_cast(worldTileSize)}); - addActor(std::move(a)); + actors->pushObject(actorSource.tilePos, std::move(a)); break; } } @@ -216,17 +222,17 @@ json Room::dumpJson() { auto propsMap = std::map{}; - for (auto &&p : *props) { + for (auto &[pos, prop] : props->getObjects()) { std::string key = - TextFormat("%i;%i", static_cast(p.getWorldTilePos().x), - static_cast(p.getWorldTilePos().y)); + TextFormat("%i;%i", static_cast(prop->getWorldTilePos().x), + static_cast(prop->getWorldTilePos().y)); auto propJson = json::object(); propJson["src"] = - TextFormat("props/%s", GetFileName(p.getSourcePath().c_str())); + TextFormat("props/%s", GetFileName(prop->getSourcePath().c_str())); - if (p.getHasInteractable()) { - propJson["props"] = p.getInteractable()->getProps(); + if (prop->getHasInteractable()) { + propJson["props"] = prop->getInteractable()->getProps(); } else { propJson["props"] = json::object(); } @@ -310,9 +316,9 @@ void Room::draw() const { DrawRectangleRec(rect, Fade(RED, 0.5f)); } - for (auto &&p : *props) { - if (p.getCollisionCenter().y <= player->getCollisionPos().y) { - p.draw(); + for (auto &[pos, prop] : props->getObjects()) { + if (prop->getCollisionCenter().y <= player->getCollisionPos().y) { + prop->draw(); } } @@ -321,9 +327,9 @@ void Room::draw() const { } player->draw(); - for (auto &&p : *props) { - if (p.getCollisionCenter().y > player->getCollisionPos().y) { - p.draw(); + for (auto &[pos, prop] : props->getObjects()) { + if (prop->getCollisionCenter().y > player->getCollisionPos().y) { + prop->draw(); } } @@ -346,14 +352,6 @@ TileMap *Room::getTileMap() const { return this->tileMap.get(); } void Room::setTileMap(TileMap *newTileMap) { tileMap.reset(newTileMap); } -std::vector Room::getCollisionTiles() const { - static std::vector result; - for (auto &[vect, obj] : this->collisions->getObjects()) { - result.push_back(std::move(fromIVector(vect))); - } - return result; -} - std::string Room::getMusicSource() const { return musicSource; } void Room::setMusicSource(const std::string_view &newMusicSource) { @@ -372,44 +370,6 @@ InteractablesContainer &Room::getInteractables() const { return *this->interactables; } -std::vector &Room::getProps() const { return *props; } - -void Room::addProp(Prop prop) const { props->push_back(std::move(prop)); } - -void Room::removeProp(Vector2 worldPos) const { - for (auto it = props->begin(); it < props->end(); ++it) { - if (it->getWorldTilePos().x == worldPos.x && - it->getWorldTilePos().y == worldPos.y) { - props->erase(it); - } - } -} - -Prop *Room::getPropAt(IVector worldPos) const { - Prop *p = nullptr; - int idx = 0; - for (auto it = props->begin(); it < props->end(); ++it, idx++) { - if (it->getWorldTilePos().x == worldPos.x && - it->getWorldTilePos().y == worldPos.y) { - p = &props->at(idx); - } - } - return p; -} - -std::vector &Room::getActors() { - static std::vector result; - for (auto &[vect, actor] : this->actors->getObjects()) { - result.push_back(std::move(actor)); - } - return result; -} +PropsContainer &Room::getProps() const { return *this->props; } -void Room::addActor(Actor actor) const { - this->actors->pushObject(fromVector2(actor.getTilePosition()), - std::move(actor)); -} - -void Room::removeActor(IVector tilePosition) const { - this->actors->removeObject(tilePosition); -} +ActorContainer &Room::getActors() const { return *this->actors; } From a2e8983bd30e611390a26cb3eef5209ebd52d2af Mon Sep 17 00:00:00 2001 From: CDevv Date: Sun, 22 Mar 2026 12:03:30 +0200 Subject: [PATCH 15/35] Properly set onTouch + export the image's extension Signed-off-by: CDevv --- src/gamedata.cpp | 2 +- src/interactable.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/gamedata.cpp b/src/gamedata.cpp index a4e2402..86cb466 100644 --- a/src/gamedata.cpp +++ b/src/gamedata.cpp @@ -44,7 +44,7 @@ template void serialize(Archive &a, TileSetBin &b) { } template void serialize(Archive &a, ImageBin &b) { - a(b.data, b.dataSize); + a(b.data, b.dataSize, b.ext); } template void serialize(Archive &a, MusicBin &b) { diff --git a/src/interactable.cpp b/src/interactable.cpp index ca4186e..a260c09 100644 --- a/src/interactable.cpp +++ b/src/interactable.cpp @@ -18,6 +18,7 @@ Interactable::Interactable() : type(), tilePos(), tileSize(0), absolutePos(), rect() { this->valid = false; + this->onTouch = false; } Interactable::Interactable(const std::string &path) { @@ -29,6 +30,7 @@ Interactable::Interactable(const std::string &path) { displayTitle = intJson.at("name"); props = std::make_unique(intJson.at("props")); scriptPath = intJson.at("script"); + onTouch = false; } Interactable::Interactable(const std::string &type, Vector2 tilePos, @@ -37,6 +39,7 @@ Interactable::Interactable(const std::string &type, Vector2 tilePos, this->props = std::make_unique(json::object()); this->valid = true; + this->onTouch = false; this->type = type; this->tilePos = tilePos; this->tileSize = tileSize; @@ -55,6 +58,7 @@ Interactable::Interactable(InteractableInRoomBin bin) { Vector2 tilePos = {static_cast(bin.x), static_cast(bin.y)}; this->valid = true; + this->onTouch = bin.onTouch; this->tilePos = tilePos; this->tileSize = _RPGPP_TILESIZE; this->absolutePos = Vector2{0, 0}; From 0afe1b050aa4dbd545bcc8585b2eec39cf0ea827 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:30:00 +0700 Subject: [PATCH 16/35] Added badge for buidling of rpg++ --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 43d060b..1d5196c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ ![logo](docs/logo.png) --- +[![Build RPG++](https://github.com/rpgppengine/rpgpp/actions/workflows/build.yml/badge.svg)](https://github.com/rpgppengine/rpgpp/actions/workflows/build.yml) RPG++ is an experimental 2D RPG game engine written in C++. It is currently in early development, but contributions are welcome! From decebe3401160579ff33f0e5cf6a2cbb025ab6b1 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Sun, 22 Mar 2026 19:49:29 +0700 Subject: [PATCH 17/35] Allow for regeneration of config when missing. Hotkeys stored in a sepearate file --- include/editor/defaultConfig.hpp | 26 +++++++++++ include/editor/defaultHotkey.hpp | 13 ------ .../editor/services/configurationService.hpp | 22 +++++---- include/editor/services/hotkeyService.hpp | 5 ++ rpgpp.ini | 9 ++++ src/editor/editor.cpp | 4 -- src/editor/services/configurationService.cpp | 46 ++++++++++++++----- src/editor/services/editorGuiService.cpp | 8 ++++ src/editor/services/hotkeyService.cpp | 37 +++++++++++++++ xmake.lua | 20 ++++---- 10 files changed, 141 insertions(+), 49 deletions(-) create mode 100644 include/editor/defaultConfig.hpp delete mode 100644 include/editor/defaultHotkey.hpp diff --git a/include/editor/defaultConfig.hpp b/include/editor/defaultConfig.hpp new file mode 100644 index 0000000..a5160c1 --- /dev/null +++ b/include/editor/defaultConfig.hpp @@ -0,0 +1,26 @@ +#include "raylib.h" +#include +#include +#include +#include + +// key-value +using ConfigEntry = std::map; +// [field] +using Config = std::map; + +const Config BASE_CONFIG = { + {"rpgpp", { + {"language", "en_us"}, + {"theme", "Dark"}, + }}, + {"hotkeys", { + {"close_tab", "341,87,"}, + {"new_project", "341,78,"}, + {"open_project", "341,79,"}, + {"redo", "341,89,"}, + {"save_file", "341,83,"}, + {"toggle_debug", "292,"}, + {"undo", "341,90,"}, + }}, +}; diff --git a/include/editor/defaultHotkey.hpp b/include/editor/defaultHotkey.hpp deleted file mode 100644 index ee1b592..0000000 --- a/include/editor/defaultHotkey.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "raylib.h" -#include -#include -#include -const std::vector>> DEFAULT_HOTKEYS = { - {"toggle_debug", std::vector{KEY_F3}}, - {"close_tab", std::vector{KEY_LEFT_CONTROL, KEY_W}}, - {"new_project", std::vector{KEY_LEFT_CONTROL, KEY_N}}, - {"open_project", std::vector{KEY_LEFT_CONTROL, KEY_O}}, - {"save_file", std::vector{KEY_LEFT_CONTROL, KEY_S}}, - {"undo", std::vector{KEY_LEFT_CONTROL, KEY_Z}}, - {"redo", std::vector{KEY_LEFT_CONTROL, KEY_Y}}, -}; diff --git a/include/editor/services/configurationService.hpp b/include/editor/services/configurationService.hpp index 63545dc..1ab810b 100644 --- a/include/editor/services/configurationService.hpp +++ b/include/editor/services/configurationService.hpp @@ -7,16 +7,18 @@ constexpr auto GENERAL_CONF_FIELD = "rpgpp"; #define RPGPP_CONFIG_FILE "rpgpp.ini" class ConfigurationService { - std::unique_ptr iniFile; - mINI::INIStructure iniStructure; - - public: - ConfigurationService(); - std::string getStringValue(const std::string &key); - std::string getStringValue(const std::string& field, const std::string &key); - void setStringValue(const std::string &key, const std::string &value); - void setStringValue(const std::string& field, const std::string &key, const std::string &value); - void saveConfiguration(); + private: + std::unique_ptr iniFile; + mINI::INIStructure iniStructure; + void regenerate(); + public: + ConfigurationService(); + mINI::INIMap> getField(const std::string &field); + std::string getStringValue(const std::string &key); + std::string getStringValue(const std::string& field, const std::string &key); + void setStringValue(const std::string &key, const std::string &value); + void setStringValue(const std::string& field, const std::string &key, const std::string &value); + void saveConfiguration(); }; #endif // RPGPP_CONFIGURATIONSERVICE_H diff --git a/include/editor/services/hotkeyService.hpp b/include/editor/services/hotkeyService.hpp index f47883d..4a8da80 100644 --- a/include/editor/services/hotkeyService.hpp +++ b/include/editor/services/hotkeyService.hpp @@ -3,6 +3,7 @@ #include #include #include +#include "ini.h" #ifndef RPGPP_HOTKEYSERVICE_H #define RPGPP_HOTKEYSERVICE_H @@ -14,12 +15,16 @@ class HotkeyService { std::multimap> hotkeysCb; std::unordered_map> hotkeyMap; std::unordered_map activatedHotkey; + void write(const std::string& keyId, const std::string& keyStr); public: HotkeyService(); void registerHotkeyCallback(const std::string &keyId, std::function cb); void unregisterHotkeyCallback(const std::string &keyId); void addHotkey(const std::string &keyId, std::vector keys); void removeHotkey(const std::string &keyId); + std::map serialize(); + void deserialize(const std::map &serialized); + void deserialize(mINI::INIMap> iniSerialized); void fire(); }; diff --git a/rpgpp.ini b/rpgpp.ini index ac4abc5..28508bb 100644 --- a/rpgpp.ini +++ b/rpgpp.ini @@ -1,3 +1,12 @@ [rpgpp] language=en_us theme=Dark + +[hotkeys] +close_tab=341,87, +new_project=341,78, +open_project=341,79, +redo=341,89, +save_file=341,83, +toggle_debug=292, +undo=341,90, diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index a317323..b3265e8 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -4,7 +4,6 @@ #include "services/editorGuiService.hpp" #include "services/fileSystemService.hpp" #include "services/translationService.hpp" -#include "defaultHotkey.hpp" #include Editor *Editor::instance; @@ -13,9 +12,6 @@ Editor::Editor() : configurationService(), translationService(this), themeService(this), hotkeyService(), project{nullptr} { instance = this; - for (const auto &[name, keys] : DEFAULT_HOTKEYS) { - hotkeyService.addHotkey(name, keys); - } } void Editor::setAppIcon(const std::string &icon_path) { diff --git a/src/editor/services/configurationService.cpp b/src/editor/services/configurationService.cpp index d9c36f6..62fe55d 100644 --- a/src/editor/services/configurationService.cpp +++ b/src/editor/services/configurationService.cpp @@ -1,23 +1,45 @@ #include "services/configurationService.hpp" +#include "ini.h" #include "raylib.h" +#include +#include +#include +#include +#include "defaultConfig.hpp" + +void ConfigurationService::regenerate() { + assert(this->iniFile && "iniFile is not initialized"); + for (auto &[field, kv] : BASE_CONFIG) { + for (auto &[k, v] : kv) { + if (!this->iniStructure.has(field)) { + this->iniStructure.set(field, mINI::INIMap()); + } + if (this->iniStructure[field].has(k)) continue; + this->iniStructure[field].set(k, v); + } + } + this->iniFile->write(this->iniStructure); +} ConfigurationService::ConfigurationService() { - std::filesystem::path baseDir = GetWorkingDirectory(); - baseDir /= RPGPP_CONFIG_FILE; - if (std::filesystem::exists(baseDir)) { - this->iniFile = std::make_unique(baseDir.string()); - this->iniFile->read(this->iniStructure); - } else { - throw std::runtime_error("configuration file doesn't exist."); + std::filesystem::path path = GetWorkingDirectory(); + path /= RPGPP_CONFIG_FILE; + bool notExist = !std::filesystem::exists(path); + if (notExist) { + std::ofstream output(path); + output.close(); } + this->iniFile = std::make_unique(path.string()); + this->iniFile->read(this->iniStructure); + this->regenerate(); }; +mINI::INIMap> ConfigurationService::getField(const std::string &field) { + return this->iniStructure[field]; +} + std::string ConfigurationService::getStringValue(const std::string& field, const std::string &key) { - if (this->iniStructure[field].has(key)) - return this->iniStructure[field][key]; - std::stringstream ss; - ss << "configuration key doesn't exist" << key; - throw std::runtime_error(ss.str()); + return this->iniStructure[field][key]; } std::string ConfigurationService::getStringValue(const std::string &key) { diff --git a/src/editor/services/editorGuiService.cpp b/src/editor/services/editorGuiService.cpp index f31fffc..d709b61 100644 --- a/src/editor/services/editorGuiService.cpp +++ b/src/editor/services/editorGuiService.cpp @@ -39,7 +39,15 @@ void EditorGuiService::init() { InitWindow(BASE_WINDOW_WIDTH, BASE_WINDOW_HEIGHT, "RPG++ Editor"); InitAudioDevice(); + auto &cfgs = Editor::instance->getConfiguration(); auto &hks = Editor::instance->getHotkeyService(); + + hks.deserialize(cfgs.getField("hotkeys")); + auto serialized = hks.serialize(); + for (auto &[keyId, keyStr]: serialized) { + std::cout << keyId << ": " << keyStr << std::endl; + } + this->resetUi(); hks.registerHotkeyCallback("toggle_debug", [this]() { perfOverlay.Toggle(); }); hks.registerHotkeyCallback("new_project", [] { diff --git a/src/editor/services/hotkeyService.cpp b/src/editor/services/hotkeyService.cpp index 8e68714..e83b40a 100644 --- a/src/editor/services/hotkeyService.cpp +++ b/src/editor/services/hotkeyService.cpp @@ -3,6 +3,43 @@ HotkeyService::HotkeyService() {} +std::map HotkeyService::serialize() { + std::map serialized{}; + for (auto &[keyId, keys]: hotkeyMap) { + std::string keyStr{}; + for (KeyboardKey k : keys) { + keyStr += std::to_string(k) + ","; + } + serialized[keyId] = keyStr; + } + return serialized; +} + +void HotkeyService::write(const std::string& keyId, const std::string& keyStr) { + std::vector keys{}; + std::stringstream ss(keyStr); + std::string token; + while (std::getline(ss, token, ',')) { + if (!token.empty()) + keys.push_back(static_cast(std::stoi(token))); + } + hotkeyMap[keyId] = keys; +} + +void HotkeyService::deserialize(const std::map &serialized) { + hotkeyMap.clear(); + for (auto &[keyId, keyStr]: serialized) { + write(keyId, keyStr); + } +} + +void HotkeyService::deserialize(mINI::INIMap> iniSerialized) { + hotkeyMap.clear(); + for (auto &[keyId, keyStr]: iniSerialized) { + write(keyId, keyStr); + } +} + void HotkeyService::registerHotkeyCallback(const std::string& keyId, std::function cb) { activatedHotkey[keyId] = false; hotkeysCb.insert({keyId, cb}); diff --git a/xmake.lua b/xmake.lua index 9aaad6b..8540870 100644 --- a/xmake.lua +++ b/xmake.lua @@ -14,9 +14,9 @@ elseif is_plat("mingw", "windows") then add_syslinks("gdi32", "opengl32", "winmm", "shell32", "user32") end -on_install("linux", "macosx", "mingw", "windows", function(package) +on_install("linux", "macosx", "mingw", "windows", function (package) -- os.cd(path.join(os.scriptdir(), "libs/raylib/")) - import("package.tools.cmake").install(package, {}) + import("package.tools.cmake").install(package, { }) end) package_end() @@ -24,8 +24,8 @@ package("noop") set_sourcedir(path.join(os.scriptdir(), "libs/noop")) add_deps("cmake") set_license("MIT") -on_install("linux", "macosx", "mingw", "windows", function(package) - import("package.tools.cmake").install(package, {}) +on_install("linux", "macosx", "mingw", "windows", function (package) + import("package.tools.cmake").install(package, { }) end) package_end() @@ -41,9 +41,9 @@ set_license("MIT") -- called "noop" so at least you have something to run! -- -- NOTE: This only works because the grammar file has already been generated. -on_install("mingw", "windows", "linux", "macosx", function(package) +on_install("mingw", "windows", "linux", "macosx", function (package) local noop = package:dep("noop") - local config = {} + local config = { } table.insert(config, "-DCMAKE_BUILD_TYPE=" .. (is_mode("debug") and "Debug" or "Release")) table.insert(config, "-DBUILD_SHARED_LIBS=OFF") table.insert(config, "-DTREE_SITTER_CLI=" .. path.join(noop:installdir(), "bin/noop")) @@ -58,8 +58,8 @@ add_versions("1.12.99", "c6d138509f2aae33cbabb09a91c5857eba990657") add_deps("cmake", "raylib") set_license("Zlib") add_extsources("raylib") -on_install("linux", "macosx", "mingw", "windows", function(package) - local configs = {} +on_install("linux", "macosx", "mingw", "windows", function (package) + local configs = { } table.insert(configs, "-DCMAKE_BUILD_TYPE=" .. (is_mode("debug") and "Debug" or "Release")) table.insert(configs, "-DBUILD_SHARED_LIBS=OFF") table.insert(configs, "-DTGUI_BACKEND=RAYLIB") @@ -107,7 +107,7 @@ add_includedirs("include/") add_files("src/game/main.cpp") task("check_translation") -on_run(function() +on_run( function () import("tools.translation_checker.checker") checker.Main() end) @@ -140,7 +140,7 @@ add_files("src/editor/**.cpp") add_deps("rpgpp") add_packages("raylib", "tgui", "nlohmann_json", "nativefiledialog-extended", "reproc", "luajit", "noop", "tree-sitter", "tree-sitter-lua") -after_build(function(target) +after_build( function (target) os.cp("$(curdir)/resources", "$(builddir)/$(plat)/$(arch)/$(mode)/", { async = true }) if is_plat("linux", "macosx") then os.cp("$(builddir)/$(plat)/$(arch)/$(mode)/librpgpp.a", "$(curdir)/game-src/lib/librpgpp.a", { async = true }) From 8e1afb42d04ece5050563cf559879e3c0d259078 Mon Sep 17 00:00:00 2001 From: CDevv Date: Sun, 22 Mar 2026 18:53:46 +0200 Subject: [PATCH 18/35] Use a map for Actors in Room + Actor layer Signed-off-by: CDevv --- include/actor.hpp | 3 ++ include/constants/room.hpp | 3 +- include/editor/actions/mapAction.hpp | 5 +- include/editor/roomLayerViewVisitor.hpp | 15 ++++-- include/gamedata.hpp | 1 + include/room.hpp | 4 +- src/actor.cpp | 33 ++++++++++++- src/editor/actions/eraseTileAction.cpp | 9 ++++ src/editor/actions/placeTileAction.cpp | 17 +++++++ src/editor/fileViews/roomFileView.cpp | 2 + src/editor/project.cpp | 10 ++-- src/editor/roomLayerViewVisitor.cpp | 48 +++++++++++++++++++ src/editor/views/roomView.cpp | 61 ++++++++++++++++++++--- src/editor/widgets/frameEditor.cpp | 10 +++- src/gamedata.cpp | 2 +- src/room.cpp | 64 ++++++++++++++----------- 16 files changed, 237 insertions(+), 50 deletions(-) diff --git a/include/actor.hpp b/include/actor.hpp index 86e5f17..d6c5d5b 100644 --- a/include/actor.hpp +++ b/include/actor.hpp @@ -145,4 +145,7 @@ class Actor : public ISaveable { void setCollisionRect(Rectangle rect); }; +Vector2 calcActorTilePos(Vector2 newPosition, Vector2 worldTileSize, + TileSet *tileSet); + #endif diff --git a/include/constants/room.hpp b/include/constants/room.hpp index f30b48b..a3cfae5 100644 --- a/include/constants/room.hpp +++ b/include/constants/room.hpp @@ -2,7 +2,8 @@ enum class RoomLayer { LAYER_TILES, LAYER_COLLISION, LAYER_INTERACTABLES, - LAYER_PROPS + LAYER_PROPS, + LAYER_ACTORS }; enum class RoomTool { TOOL_NONE, diff --git a/include/editor/actions/mapAction.hpp b/include/editor/actions/mapAction.hpp index 5436041..fa99dca 100644 --- a/include/editor/actions/mapAction.hpp +++ b/include/editor/actions/mapAction.hpp @@ -12,11 +12,12 @@ struct MapActionData { RoomView *view; Room *room; RoomLayer layer; - std::string interactable; - std::string interactableFullPath; Vector2 worldTile; Vector2 tile; Vector2 prevTile; + std::string interactable; + std::string interactableFullPath; + std::string actorName; }; class MapAction : public Action { diff --git a/include/editor/roomLayerViewVisitor.hpp b/include/editor/roomLayerViewVisitor.hpp index e9826ad..7780295 100644 --- a/include/editor/roomLayerViewVisitor.hpp +++ b/include/editor/roomLayerViewVisitor.hpp @@ -2,18 +2,22 @@ #define _RPGPP_ROOMLAYERVIEWVISITOR_H #include "TGUI/Widgets/ComboBox.hpp" +#include "TGUI/Widgets/EditBox.hpp" #include "TGUI/Widgets/Group.hpp" +#include "actor.hpp" #include "interactable.hpp" #include "prop.hpp" #include "views/tileSetView.hpp" #include "views/worldView.hpp" #include +#include #include class RoomLayerViewVisitor - : public mj::enum_visitor< - RoomLayer, RoomLayer::LAYER_TILES, RoomLayer::LAYER_COLLISION, - RoomLayer::LAYER_INTERACTABLES, RoomLayer::LAYER_PROPS> { + : public mj::enum_visitor { public: RoomLayerViewVisitor(); tgui::Group::Ptr group{nullptr}; @@ -21,14 +25,19 @@ class RoomLayerViewVisitor Interactable *inter{nullptr}; Prop *prop{nullptr}; Texture2D propTexture{}; + Texture2D actorTexture{}; + std::unique_ptr chosenActor; void operator()(enum_v); void operator()(enum_v); void operator()(enum_v); void operator()(enum_v); + void operator()(enum_v); TileSetView::Ptr tileSetView; tgui::ComboBox::Ptr interactableChoose; tgui::ComboBox::Ptr propChoose; + tgui::ComboBox::Ptr actorChoose; + tgui::EditBox::Ptr actorNameInput; ~RoomLayerViewVisitor(); }; diff --git a/include/gamedata.hpp b/include/gamedata.hpp index 8caf0ba..7b38cb5 100644 --- a/include/gamedata.hpp +++ b/include/gamedata.hpp @@ -41,6 +41,7 @@ struct ActorBin { struct ActorInRoomBin { std::string name; + std::string source; IVector tilePos; }; diff --git a/include/room.hpp b/include/room.hpp index c978932..733bb96 100644 --- a/include/room.hpp +++ b/include/room.hpp @@ -46,7 +46,7 @@ class Room : public ISaveable { /** This Room's TileMap, which contains all placed tiles. */ std::unique_ptr tileMap; /** A collection of all Actors in this Room */ - std::unique_ptr actors; + std::map> actors; /** This Room's only Player. */ std::unique_ptr player; void updateCamera(); @@ -107,7 +107,7 @@ class Room : public ISaveable { /** Get a reference to the PropsContainer of this Room. */ PropsContainer &getProps() const; /** Get a refernece to the collection of Actors. */ - ActorContainer &getActors() const; + std::map> &getActors(); }; #endif diff --git a/src/actor.cpp b/src/actor.cpp index 3b828a7..4bb39e9 100644 --- a/src/actor.cpp +++ b/src/actor.cpp @@ -1,6 +1,7 @@ #include "actor.hpp" #include "game.hpp" #include "gamedata.hpp" +#include "tileset.hpp" #include #include #include @@ -342,8 +343,16 @@ void Actor::setTilePosition(Vector2 newPosition, Vector2 tileSize) { Vector2{newPosition.x * tileSize.x * RPGPP_DRAW_MULTIPLIER, newPosition.y * tileSize.y * RPGPP_DRAW_MULTIPLIER}; + float xDiff = 0; + if ((tileSet->getTileWidth() * RPGPP_DRAW_MULTIPLIER) > + (tileSize.x * RPGPP_DRAW_MULTIPLIER)) { + xDiff = ((tileSet->getTileWidth() * RPGPP_DRAW_MULTIPLIER) - + (tileSize.x * RPGPP_DRAW_MULTIPLIER)) / + 2; + } + auto resultVector = - Vector2{absolutePos.x, + Vector2{absolutePos.x - xDiff, absolutePos.y - ((actorTileSize.y * RPGPP_DRAW_MULTIPLIER) - (tileSize.y * RPGPP_DRAW_MULTIPLIER))}; this->position = resultVector; @@ -449,3 +458,25 @@ std::string Actor::getTileSetSource() const { return tileSetSource; } Rectangle Actor::getCollisionRect() const { return collisionRect; } void Actor::setCollisionRect(Rectangle rect) { this->collisionRect = rect; } + +Vector2 calcActorTilePos(Vector2 newPosition, Vector2 worldTileSize, + TileSet *tileSet) { + Vector2 actorTileSize = tileSet->getTileSize(); + auto absolutePos = + Vector2{newPosition.x * worldTileSize.x * RPGPP_DRAW_MULTIPLIER, + newPosition.y * worldTileSize.y * RPGPP_DRAW_MULTIPLIER}; + + float xDiff = 0; + if ((tileSet->getTileWidth() * RPGPP_DRAW_MULTIPLIER) > + (worldTileSize.x * RPGPP_DRAW_MULTIPLIER)) { + xDiff = ((tileSet->getTileWidth() * RPGPP_DRAW_MULTIPLIER) - + (worldTileSize.x * RPGPP_DRAW_MULTIPLIER)) / + 2; + } + + auto resultVector = + Vector2{absolutePos.x - xDiff, + absolutePos.y - ((actorTileSize.y * RPGPP_DRAW_MULTIPLIER) - + (worldTileSize.y * RPGPP_DRAW_MULTIPLIER))}; + return resultVector; +} diff --git a/src/editor/actions/eraseTileAction.cpp b/src/editor/actions/eraseTileAction.cpp index 1206888..a589f3d 100644 --- a/src/editor/actions/eraseTileAction.cpp +++ b/src/editor/actions/eraseTileAction.cpp @@ -2,6 +2,7 @@ #include "actions/eraseTileAction.hpp" #include "actions/mapAction.hpp" #include "conversion.hpp" +#include "raymath.h" #include "views/worldView.hpp" EraseTileAction::EraseTileAction(MapActionData a) : MapAction(a) {} @@ -21,6 +22,14 @@ void EraseTileAction::execute() { case RoomLayer::LAYER_PROPS: { data.room->getProps().removeObject(fromVector2(data.worldTile)); } break; + case RoomLayer::LAYER_ACTORS: { + for (auto &&a : data.room->getActors()) { + if (Vector2Equals(a.second->getTilePosition(), data.worldTile)) { + data.room->getActors().erase(a.first); + break; + } + } + } break; default: break; } diff --git a/src/editor/actions/placeTileAction.cpp b/src/editor/actions/placeTileAction.cpp index 404d4cb..857d22d 100644 --- a/src/editor/actions/placeTileAction.cpp +++ b/src/editor/actions/placeTileAction.cpp @@ -1,9 +1,11 @@ #include "actions/placeTileAction.hpp" #include "actions/mapAction.hpp" +#include "actor.hpp" #include "conversion.hpp" #include "editor.hpp" #include "prop.hpp" #include "raylib.h" +#include "raymath.h" #include "room.hpp" #include "views/worldView.hpp" #include @@ -63,6 +65,13 @@ void PlaceTileAction::execute() { data.room->getProps().pushObject(fromVector2(data.worldTile), std::move(p)); } break; + case RoomLayer::LAYER_ACTORS: { + auto a = std::make_unique(data.interactableFullPath); + a->setTilePosition( + data.worldTile, + data.room->getTileMap()->getTileSet()->getTileSize()); + data.room->getActors()[data.actorName] = std::move(a); + } break; default: break; } @@ -82,6 +91,14 @@ void PlaceTileAction::undo() { case RoomLayer::LAYER_PROPS: { data.room->getProps().removeObject(fromVector2(data.worldTile)); } break; + case RoomLayer::LAYER_ACTORS: { + for (auto &&a : data.room->getActors()) { + if (Vector2Equals(a.second->getTilePosition(), data.worldTile)) { + data.room->getActors().erase(a.first); + break; + } + } + } break; default: break; } diff --git a/src/editor/fileViews/roomFileView.cpp b/src/editor/fileViews/roomFileView.cpp index aaaf606..a694ee2 100644 --- a/src/editor/fileViews/roomFileView.cpp +++ b/src/editor/fileViews/roomFileView.cpp @@ -18,6 +18,7 @@ #include "widgets/propertyFields/fileField.hpp" #include "widgets/toolbox.hpp" #include + RoomFileView::RoomFileView() { RoomTool a; TranslationService &ts = Editor::instance->getTranslations(); @@ -57,6 +58,7 @@ RoomFileView::RoomFileView() { layerChoose->addItem("Collisions"); layerChoose->addItem("Interactables"); layerChoose->addItem("Props"); + layerChoose->addItem("Actors"); layerChoose->setSelectedItemByIndex(0); widgetContainer.push_back(layerChoose); diff --git a/src/editor/project.cpp b/src/editor/project.cpp index d4ee159..23d7ebb 100644 --- a/src/editor/project.cpp +++ b/src/editor/project.cpp @@ -250,11 +250,13 @@ GameData Project::generateStruct() { nlohmann::json::to_cbor(prop->getInteractable()->getProps()); roomBin.props.push_back(pBin); } - for (auto &[pos, actor] : room->getActors().getObjects()) { + for (auto &[aName, actor] : room->getActors()) { ActorInRoomBin aBin; - aBin.name = actor.getSourcePath(); - aBin.tilePos = IVector{static_cast(actor.getTilePosition().x), - static_cast(actor.getTilePosition().y)}; + aBin.name = aName; + aBin.source = actor->getSourcePath(); + aBin.tilePos = + IVector{static_cast(actor->getTilePosition().x), + static_cast(actor->getTilePosition().y)}; roomBin.actors.push_back(aBin); } roomBin.musicSource = room->getMusicSource(); diff --git a/src/editor/roomLayerViewVisitor.cpp b/src/editor/roomLayerViewVisitor.cpp index 52e9db9..7ce2cd8 100644 --- a/src/editor/roomLayerViewVisitor.cpp +++ b/src/editor/roomLayerViewVisitor.cpp @@ -1,10 +1,14 @@ #include "roomLayerViewVisitor.hpp" #include "TGUI/Widgets/CheckBox.hpp" #include "TGUI/Widgets/ComboBox.hpp" +#include "TGUI/Widgets/EditBox.hpp" #include "TGUI/Widgets/Label.hpp" +#include "actor.hpp" #include "editor.hpp" +#include "services/fileSystemService.hpp" #include "views/worldView.hpp" #include "widgets/propertiesBox.hpp" +#include #include #include @@ -27,6 +31,23 @@ RoomLayerViewVisitor::RoomLayerViewVisitor() { propTexture = p.getTexture(); }); + actorNameInput = tgui::EditBox::create(); + actorNameInput->setPosition(0, 32); + + actorChoose = tgui::ComboBox::create(); + actorChoose->setPosition(0, 64); + actorChoose->onItemSelect([this](int index) { + auto id = actorChoose->getIdByIndex(index); + std::unique_ptr a = std::make_unique(id.toStdString()); + + if (IsTextureValid(actorTexture)) { + UnloadTexture(actorTexture); + } + + actorTexture = a->getTileSet().getTexture(); + chosenActor = std::move(a); + }); + auto map = Editor::instance->getProject()->getInteractableNames(); interactableChoose->setSelectedItemByIndex(0); } @@ -113,3 +134,30 @@ void RoomLayerViewVisitor::operator()(enum_v) { } } } + +void RoomLayerViewVisitor::operator()(enum_v) { + if (tool == RoomTool::TOOL_PLACE) { + group->add(tgui::Label::create("Actors")); + + group->add(actorNameInput); + + actorChoose->removeAllItems(); + auto vec = Editor::instance->getProject()->getPaths( + EngineFileType::FILE_ACTOR); + for (auto actorPath : vec) { + actorChoose->addItem(GetFileNameWithoutExt(actorPath.c_str()), + actorPath); + } + actorChoose->setSelectedItemByIndex(0); + + std::unique_ptr a = std::make_unique( + actorChoose->getSelectedItemId().toStdString()); + + actorTexture = a->getTileSet().getTexture(); + chosenActor = std::move(a); + + group->add(actorChoose); + } else if (tool == RoomTool::TOOL_ERASE) { + group->add(tgui::Label::create("Erase an Actor..")); + } +} diff --git a/src/editor/views/roomView.cpp b/src/editor/views/roomView.cpp index 957b8e8..7ed1edd 100644 --- a/src/editor/views/roomView.cpp +++ b/src/editor/views/roomView.cpp @@ -8,6 +8,8 @@ #include "actions/mapAction.hpp" #include "actions/placeTileAction.hpp" #include "actions/startPointAction.hpp" +#include "actor.hpp" +#include "conversion.hpp" #include "editor.hpp" #include "enum_visitor/enum_visitor.hpp" #include "gamedata.hpp" @@ -126,10 +128,6 @@ void RoomView::drawCanvas() { 0.0f, WHITE); } - if (tileSetView != nullptr) { - handleMode(tileX, tileY); - } - // Draw tile border DrawRectangleLinesEx(destRect, 1.0f, Fade(GRAY, 0.5f)); if (CheckCollisionPointRec(mouseWorldPos, destRect)) { @@ -187,6 +185,20 @@ void RoomView::drawCanvas() { prop->draw(); } + // actors + for (auto &[name, actor] : room->getActors()) { + actor->draw(); + } + + // handle hovering + for (int tileX = 0; tileX < worldWidth; tileX++) { + for (int tileY = 0; tileY < worldHeight; tileY++) { + if (tileSetView != nullptr) { + handleMode(tileX, tileY); + } + } + } + DrawRectangleLinesEx(overlayRect, 2.0f, Fade(GRAY, 0.5f)); DrawCircleV(getMouseWorldPos(), 1.0f, MAROON); } @@ -290,6 +302,29 @@ void RoomView::handlePlaceMode(int x, int y) { {0.0f, 0.0f}, 0.0f, Fade(WHITE, 0.7f)); } } break; + case RoomLayer::LAYER_ACTORS: { + IVector tileMouse = getTileAtMouse(); + + if (IsTextureValid(layerVisitor->actorTexture)) { + auto actorTilePos = calcActorTilePos( + fromIVector(tileMouse), + room->getTileMap()->getTileSet()->getTileSize(), + &layerVisitor->chosenActor->getTileSet()); + + auto &actorTileSet = layerVisitor->chosenActor->getTileSet(); + auto actorTileSize = actorTileSet.getTileSize(); + + Rectangle source = {0, 0, actorTileSize.x, actorTileSize.y}; + Rectangle dest = { + actorTilePos.x, actorTilePos.y, + static_cast(actorTileSize.x * RPGPP_DRAW_MULTIPLIER), + static_cast(actorTileSize.y * + RPGPP_DRAW_MULTIPLIER)}; + + DrawTexturePro(layerVisitor->actorTexture, source, dest, + Vector2{0.0f, 0.0f}, 0.0f, WHITE); + } + } break; default: break; } @@ -337,16 +372,30 @@ void RoomView::handleModePress(tgui::Vector2f pos) { static_cast(atlasTilePos.y)}; data.worldTile = {static_cast(tileMouse.x), static_cast(tileMouse.y)}; - if (layer == RoomLayer::LAYER_INTERACTABLES) { + switch (layer) { + case RoomLayer::LAYER_INTERACTABLES: { data.interactable = GetFileNameWithoutExt( interactableChoose->getSelectedItemId().toStdString().c_str()); data.interactableFullPath = interactableChoose->getSelectedItemId().toStdString(); - } else { + } break; + case RoomLayer::LAYER_PROPS: { data.interactable = GetFileNameWithoutExt( propChoose->getSelectedItemId().toStdString().c_str()); data.interactableFullPath = propChoose->getSelectedItemId().toStdString(); + } break; + case RoomLayer::LAYER_ACTORS: { + data.actorName = layerVisitor->actorNameInput->getText().toStdString(); + data.interactable = + GetFileNameWithoutExt(layerVisitor->actorChoose->getSelectedItemId() + .toStdString() + .c_str()); + data.interactableFullPath = + layerVisitor->actorChoose->getSelectedItemId().toStdString(); + } break; + default: + break; } switch (tool) { diff --git a/src/editor/widgets/frameEditor.cpp b/src/editor/widgets/frameEditor.cpp index 68cfa42..a9c0f1c 100644 --- a/src/editor/widgets/frameEditor.cpp +++ b/src/editor/widgets/frameEditor.cpp @@ -50,9 +50,15 @@ void FrameEditor::init() { box->setSelectedItemByIndex(0); }); - directionChooser->onItemSelect.connect([this](const size_t &index) { + directionChooser->onItemSelect.connect([this, &actor](const size_t &index) { this->changeFrameState(index); - this->updateFrameButtons(); + + for (auto btn : frameButtons) { + frameLayout->remove(btn); + } + frameButtons.clear(); + for (int i = 0; i < actor->getAnimationCount(); i++) + this->addFrameButton(i); }); topBarLayout->add(directionChooser); diff --git a/src/gamedata.cpp b/src/gamedata.cpp index 86cb466..21de483 100644 --- a/src/gamedata.cpp +++ b/src/gamedata.cpp @@ -20,7 +20,7 @@ template void serialize(Archive &a, ActorBin &b) { } template void serialize(Archive &a, ActorInRoomBin &b) { - a(b.name, b.tilePos); + a(b.name, b.source, b.tilePos); } template void serialize(Archive &a, TileBin &b) { diff --git a/src/room.cpp b/src/room.cpp index e8ce053..1567582 100644 --- a/src/room.cpp +++ b/src/room.cpp @@ -36,7 +36,7 @@ Room::Room() { this->collisions = std::make_unique(); this->props = std::make_unique(); this->tileMap = std::unique_ptr{}; - this->actors = std::make_unique(); + this->actors = std::map>{}; this->player = std::unique_ptr{}; } @@ -63,7 +63,7 @@ Room::Room(const std::string &fileName, int tileSize) { this->interactables = std::make_unique(); this->collisions = std::make_unique(); - this->actors = std::make_unique(); + this->actors = std::map>{}; this->props = std::make_unique(); this->tileMap = std::make_unique(fileName); @@ -103,7 +103,7 @@ Room::Room(const std::string &fileName, int tileSize) { props->pushObject({x, y}, std::move(p)); } - std::map actorsVec = roomJson.at("actors"); + std::map actorsVec = roomJson.at("actors"); for (auto const &[key, value] : actorsVec) { int count = 0; char **textSplit = TextSplit(key.c_str(), ';', &count); @@ -112,11 +112,11 @@ Room::Room(const std::string &fileName, int tileSize) { int x = std::stoi(std::string(textSplit[0])); int y = std::stoi(std::string(textSplit[1])); - auto a = Actor(value); - a.setTilePosition(Vector2{static_cast(x), static_cast(y)}, - Vector2{static_cast(worldTileSize), - static_cast(worldTileSize)}); - actors->pushObject({x, y}, std::move(a)); + auto a = std::make_unique(value.at("source")); + a->setTilePosition( + Vector2{static_cast(x), static_cast(y)}, + tileMap->getTileSet()->getTileSize()); + actors[value.at("name")] = std::move(a); } interactables->addJsonData(roomJson.at("interactables")); @@ -133,12 +133,12 @@ Room::Room(const RoomBin &bin) : Room() { this->interactables = std::make_unique(); this->collisions = std::make_unique(); - this->actors = std::make_unique(); + this->actors = std::map>{}; this->props = std::make_unique(); this->tileMap = std::make_unique(bin); - auto &actorBin = Game::getBin().actors["playerActor"]; + auto &actorBin = Game::getBin().actors["krisactor"]; auto actor = std::make_unique(actorBin); actor->setTilePosition(Vector2{static_cast(bin.startPoint.x), @@ -180,14 +180,15 @@ Room::Room(const RoomBin &bin) : Room() { for (const auto &actorSource : bin.actors) { for (const auto [name, actorBin] : Game::getBin().actors) { - if (actorBin.name == actorSource.name) { - auto a = Actor(actorBin); - a.setTilePosition( + std::string sourceFileName = + GetFileName(actorSource.source.c_str()); + if (actorBin.name == sourceFileName) { + auto a = std::make_unique(actorBin); + a->setTilePosition( Vector2{static_cast(actorSource.tilePos.x), static_cast(actorSource.tilePos.y)}, - Vector2{static_cast(worldTileSize), - static_cast(worldTileSize)}); - actors->pushObject(actorSource.tilePos, std::move(a)); + tileMap->getTileSet()->getTileSize()); + actors[actorSource.name] = std::move(a); break; } } @@ -240,13 +241,18 @@ json Room::dumpJson() { propsMap[key] = propJson; } - auto actorsMap = std::map{}; - for (auto &[vect, obj] : actors->getObjects()) { + auto actorsMap = std::map{}; + for (auto &[name, obj] : actors) { std::string key = - TextFormat("%i;%i", static_cast(obj.getTilePosition().x), - static_cast(obj.getTilePosition().y)); + TextFormat("%i;%i", static_cast(obj->getTilePosition().x), + static_cast(obj->getTilePosition().y)); + + auto actorInfo = std::map{}; + actorInfo["source"] = + TextFormat("actors/%s", GetFileName(obj->getSourcePath().c_str())); + actorInfo["name"] = name; - actorsMap[key] = obj.getSourcePath(); + actorsMap[key] = actorInfo; } roomJson.push_back({"interactables", interactableProps}); @@ -263,16 +269,16 @@ json Room::dumpJson() { void Room::unload() const { tileMap->unload(); - for (auto &[vect, actor] : actors->getObjects()) { - actor.unload(); + for (auto &[vect, actor] : actors) { + actor->unload(); } player->unload(); } void Room::update() { - for (auto &[vect, actor] : actors->getObjects()) { - actor.update(); + for (auto &[vect, actor] : actors) { + actor->update(); } player->update(); if (!lock) @@ -322,8 +328,8 @@ void Room::draw() const { } } - for (auto &[vect, actor] : actors->getObjects()) { - actor.draw(); + for (auto &[vect, actor] : actors) { + actor->draw(); } player->draw(); @@ -372,4 +378,6 @@ InteractablesContainer &Room::getInteractables() const { PropsContainer &Room::getProps() const { return *this->props; } -ActorContainer &Room::getActors() const { return *this->actors; } +std::map> &Room::getActors() { + return this->actors; +} From c943fff26a7de2d5b5b7a681ee42ba670d0af20c Mon Sep 17 00:00:00 2001 From: CDevv Date: Sun, 22 Mar 2026 19:08:43 +0200 Subject: [PATCH 19/35] Look for playerActor Signed-off-by: CDevv --- src/room.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/room.cpp b/src/room.cpp index 1567582..ae96a3e 100644 --- a/src/room.cpp +++ b/src/room.cpp @@ -138,7 +138,7 @@ Room::Room(const RoomBin &bin) : Room() { this->tileMap = std::make_unique(bin); - auto &actorBin = Game::getBin().actors["krisactor"]; + auto &actorBin = Game::getBin().actors["playerActor"]; auto actor = std::make_unique(actorBin); actor->setTilePosition(Vector2{static_cast(bin.startPoint.x), From 202c8dc365a67dae2e2d512128772c05688d4191 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Mon, 23 Mar 2026 00:30:10 +0700 Subject: [PATCH 20/35] Improve hotkey service, allow for modifying of hotkeys! --- include/editor/defaultConfig.hpp | 38 +- include/editor/editor.hpp | 2 +- .../editor/services/configurationService.hpp | 27 +- include/editor/services/hotkeyService.hpp | 45 +- include/editor/widgets/hotkeyModifier.hpp | 42 ++ resources/translations/en_us.json | 6 +- resources/translations/vn.json | 6 +- rpgpp.ini | 20 +- src/editor/childWindows/settingsWindow.cpp | 47 +- src/editor/screens/projectScreen.cpp | 11 +- src/editor/screens/welcomeScreen.cpp | 6 +- src/editor/services/configurationService.cpp | 15 +- src/editor/services/editorGuiService.cpp | 44 +- src/editor/services/hotkeyService.cpp | 89 ++-- src/editor/widgets/hotkeyModifier.cpp | 484 ++++++++++++++++++ 15 files changed, 751 insertions(+), 131 deletions(-) create mode 100644 include/editor/widgets/hotkeyModifier.hpp create mode 100644 src/editor/widgets/hotkeyModifier.cpp diff --git a/include/editor/defaultConfig.hpp b/include/editor/defaultConfig.hpp index a5160c1..d6c7478 100644 --- a/include/editor/defaultConfig.hpp +++ b/include/editor/defaultConfig.hpp @@ -1,4 +1,6 @@ +#include "editor.hpp" #include "raylib.h" +#include "services/hotkeyService.hpp" #include #include #include @@ -9,18 +11,28 @@ using ConfigEntry = std::map; // [field] using Config = std::map; +const std::string h(Hotkey hk) { + return std::to_string(HotkeyService::pack(hk)); +} + const Config BASE_CONFIG = { - {"rpgpp", { - {"language", "en_us"}, - {"theme", "Dark"}, - }}, - {"hotkeys", { - {"close_tab", "341,87,"}, - {"new_project", "341,78,"}, - {"open_project", "341,79,"}, - {"redo", "341,89,"}, - {"save_file", "341,83,"}, - {"toggle_debug", "292,"}, - {"undo", "341,90,"}, - }}, + {"rpgpp", + { + {"language", "en_us"}, + {"theme", "Dark"}, + }}, + {"hotkeys", + {{"close_tab", h({.ctrl = true, .key = KEY_W})}, + {"new_project", h({.ctrl = true, .key = KEY_N})}, + {"open_project", h({.ctrl = true, .key = KEY_O})}, + {"redo", h({.ctrl = true, .key = KEY_Y})}, + {"save_file", h({.ctrl = true, .key = KEY_S})}, + {"toggle_debug", h({.key = KEY_F3})}, + {"undo", h({.ctrl = true, .key = KEY_Z})}, + {"room_tool.mouse", h({.key = KEY_ONE})}, + {"room_tool.pen", h({.key = KEY_TWO})}, + {"room_tool.eraser", h({.key = KEY_THREE})}, + {"room_tool.edit", h({.key = KEY_FOUR})}, + {"room_tool.set_spoint", h({.key = KEY_FIVE})}, + {"room_tool.toggle_bm", h({.key = KEY_SIX})}}}, }; diff --git a/include/editor/editor.hpp b/include/editor/editor.hpp index d0c1588..ca4b68b 100644 --- a/include/editor/editor.hpp +++ b/include/editor/editor.hpp @@ -5,9 +5,9 @@ #include "raylib.h" #include "services/editorGuiService.hpp" #include "services/fileSystemService.hpp" +#include "services/hotkeyService.hpp" #include "services/themeService.hpp" #include "services/translationService.hpp" -#include "services/hotkeyService.hpp" #include #include diff --git a/include/editor/services/configurationService.hpp b/include/editor/services/configurationService.hpp index 1ab810b..27a5eed 100644 --- a/include/editor/services/configurationService.hpp +++ b/include/editor/services/configurationService.hpp @@ -7,18 +7,21 @@ constexpr auto GENERAL_CONF_FIELD = "rpgpp"; #define RPGPP_CONFIG_FILE "rpgpp.ini" class ConfigurationService { - private: - std::unique_ptr iniFile; - mINI::INIStructure iniStructure; - void regenerate(); - public: - ConfigurationService(); - mINI::INIMap> getField(const std::string &field); - std::string getStringValue(const std::string &key); - std::string getStringValue(const std::string& field, const std::string &key); - void setStringValue(const std::string &key, const std::string &value); - void setStringValue(const std::string& field, const std::string &key, const std::string &value); - void saveConfiguration(); + private: + std::unique_ptr iniFile; + mINI::INIStructure iniStructure; + void regenerate(); + + public: + ConfigurationService(); + mINI::INIMap> getField(const std::string &field); + std::string getStringValue(const std::string &key); + std::string getStringValue(const std::string &field, + const std::string &key); + void setStringValue(const std::string &key, const std::string &value); + void setStringValue(const std::string &field, const std::string &key, + const std::string &value); + void saveConfiguration(); }; #endif // RPGPP_CONFIGURATIONSERVICE_H diff --git a/include/editor/services/hotkeyService.hpp b/include/editor/services/hotkeyService.hpp index 4a8da80..7f5a570 100644 --- a/include/editor/services/hotkeyService.hpp +++ b/include/editor/services/hotkeyService.hpp @@ -1,31 +1,42 @@ +#include "ini.h" #include "raylib.h" #include #include #include -#include "ini.h" #ifndef RPGPP_HOTKEYSERVICE_H #define RPGPP_HOTKEYSERVICE_H class Editor; - +struct Hotkey { + bool ctrl; + bool shift; + bool alt; + bool super; + KeyboardKey key; +}; +using HotkeyMap = std::unordered_map; class HotkeyService { - private: - std::multimap> hotkeysCb; - std::unordered_map> hotkeyMap; - std::unordered_map activatedHotkey; - void write(const std::string& keyId, const std::string& keyStr); - public: - HotkeyService(); - void registerHotkeyCallback(const std::string &keyId, std::function cb); - void unregisterHotkeyCallback(const std::string &keyId); - void addHotkey(const std::string &keyId, std::vector keys); - void removeHotkey(const std::string &keyId); - std::map serialize(); - void deserialize(const std::map &serialized); - void deserialize(mINI::INIMap> iniSerialized); - void fire(); + private: + std::multimap> hotkeysCb; + void write(const std::string &keyId, const std::string &keyStr); + HotkeyMap hotkeyMap; + + public: + HotkeyService(); + void registerHotkeyCallback(const std::string &keyId, + std::function cb); + void unregisterHotkeyCallback(const std::string &keyId); + void addHotkey(const std::string &keyId, const Hotkey &keys); + void removeHotkey(const std::string &keyId); + const HotkeyMap listHotkeys(); + std::map serialize(); + void deserialize(const std::map &serialized); + void deserialize(mINI::INIMap> iniSerialized); + void fire(); + static const int pack(Hotkey hk); + static const Hotkey unpack(int packed); }; #endif diff --git a/include/editor/widgets/hotkeyModifier.hpp b/include/editor/widgets/hotkeyModifier.hpp new file mode 100644 index 0000000..6a7ca95 --- /dev/null +++ b/include/editor/widgets/hotkeyModifier.hpp @@ -0,0 +1,42 @@ + +#ifndef _RPGPP_HOTKEYMODIFIER_H +#define _RPGPP_HOTKEYMODIFIER_H + +#include "TGUI/Signal.hpp" +#include "TGUI/SubwidgetContainer.hpp" +#include "TGUI/Widgets/Button.hpp" +#include "editor.hpp" +#include "raylib.h" + +enum State { DEFAULT, START_EDITING, IS_EDITING }; + +using tguiKey = tgui::Event::KeyboardKey; + +class HotkeyModifier : public tgui::SubwidgetContainer { + private: + std::vector keys; + Hotkey hk; + tgui::Button::Ptr button; + State modifingState = DEFAULT; + std::string id; + + public: + typedef std::shared_ptr Ptr; + typedef std::shared_ptr ConstPtr; + HotkeyModifier(const char *typeName = "FileChooser", + bool initRenderer = true); + static HotkeyModifier::Ptr create(); + static HotkeyModifier::Ptr copy(HotkeyModifier::ConstPtr widget); + + void keyPressed(const tgui::Event::KeyEvent &event) override; + + void setKey(const std::string &id, KeyboardKey key, bool isShift, + bool isCtrl, bool isAlt, bool isSuper, bool override = false); + + tgui::SignalTyped2 onChange = {"onChange"}; + + protected: + Widget::Ptr clone() const override; +}; + +#endif diff --git a/resources/translations/en_us.json b/resources/translations/en_us.json index 4d2bfd6..a8cb1f5 100644 --- a/resources/translations/en_us.json +++ b/resources/translations/en_us.json @@ -30,7 +30,8 @@ "options": { "language": "Language", "theme": "Theme", - "theme_notice": "It is recommended to restart the editor after switching theme!" + "theme_notice": "It is recommended to restart the editor after switching theme!", + "hotkey": "Hotkey" }, "project": { "create_new_resource": "New Resource", @@ -100,6 +101,9 @@ "widget": { "filechooser": { "select_a_file": "Select a File" + }, + "hotkey_modifier": { + "listening": "Listening..." } }, "button": { diff --git a/resources/translations/vn.json b/resources/translations/vn.json index 3439257..8f1b5a2 100644 --- a/resources/translations/vn.json +++ b/resources/translations/vn.json @@ -33,7 +33,8 @@ "options": { "language": "Ngôn ngữ", "theme": "Chủ đề", - "theme_notice": "Để áp dụng chủ đề mới, vui lòng khởi động lại phần mềm!" + "theme_notice": "Để áp dụng chủ đề mới, vui lòng khởi động lại phần mềm!", + "hotkey": "Phím tắt" }, "project": { "create_new_resource": "Tạo tài nguyên", @@ -110,6 +111,9 @@ "widget": { "filechooser": { "select_a_file": "Chọn tệp tin" + }, + "hotkey_modifier": { + "listening": "Đang nhận phím..." } }, diff --git a/rpgpp.ini b/rpgpp.ini index 28508bb..2e10c1d 100644 --- a/rpgpp.ini +++ b/rpgpp.ini @@ -3,10 +3,16 @@ language=en_us theme=Dark [hotkeys] -close_tab=341,87, -new_project=341,78, -open_project=341,79, -redo=341,89, -save_file=341,83, -toggle_debug=292, -undo=341,90, +close_tab=1393 +new_project=1249 +open_project=1265 +redo=1425 +room_tool.edit=832 +room_tool.eraser=816 +room_tool.mouse=784 +room_tool.pen=800 +room_tool.set_spoint=848 +room_tool.toggle_bm=864 +save_file=1329 +toggle_debug=4672 +undo=1441 diff --git a/src/editor/childWindows/settingsWindow.cpp b/src/editor/childWindows/settingsWindow.cpp index 0eb210e..1be08ee 100644 --- a/src/editor/childWindows/settingsWindow.cpp +++ b/src/editor/childWindows/settingsWindow.cpp @@ -3,23 +3,30 @@ #include "TGUI/Widgets/GrowVerticalLayout.hpp" #include "TGUI/Widgets/HorizontalLayout.hpp" #include "TGUI/Widgets/Label.hpp" +#include "TGUI/Widgets/ScrollablePanel.hpp" #include "bindTranslation.hpp" #include "childWindows/popupWindow.hpp" #include "editor.hpp" +#include "widgets/hotkeyModifier.hpp" SettingsWindow::SettingsWindow(const std::string &title) : PopupWindow(title) { TranslationService &ts = Editor::instance->getTranslations(); ThemeService &theme = Editor::instance->getThemeService(); + HotkeyService &hks = Editor::instance->getHotkeyService(); bindTranslation(this->currentWindow, "menu.options._label", &tgui::ChildWindow::setTitle); + const tgui::ScrollablePanel::Ptr scrollPanel = + tgui::ScrollablePanel::create(); + scrollPanel->setSize("100%", "100%"); + scrollPanel->getRenderer()->setPadding(4); const auto layout = tgui::GrowVerticalLayout::create(); layout->setSize("80%", "100%"); - layout->setPosition({"50%", "50%"}); - layout->setOrigin({0.5, 0.5}); + layout->setPosition({"50%", "0%"}); + layout->setOrigin({0.5, 0}); layout->getRenderer()->setSpaceBetweenWidgets(10.0f); const auto topOptionsHeader = tgui::Label::create(); @@ -95,11 +102,43 @@ SettingsWindow::SettingsWindow(const std::string &title) : PopupWindow(title) { auto warnLabel = tgui::Label::create(); bindTranslation(warnLabel, "screen.options.theme_notice", &tgui::Label::setText); - warnLabel->setSize({"100%", 100}); + warnLabel->setSize({"100%", 36}); layout->add(languageLayout); layout->add(themeLayout); layout->add(warnLabel); - this->currentWindow->add(layout); + // Hotkeys + const auto hotkeyLabel = tgui::Label::create(); + bindTranslation(hotkeyLabel, "screen.options.hotkey", + &tgui::Label::setText); + + layout->add(hotkeyLabel); + + for (auto &[k, v] : hks.listHotkeys()) { + const auto container = tgui::HorizontalLayout::create(); + const auto hotkeyLabel = tgui::Label::create(); + hotkeyLabel->setVerticalAlignment(tgui::VerticalAlignment::Center); + hotkeyLabel->setText(k); + + const auto hotkeyModifier = HotkeyModifier::create(); + + hotkeyModifier->setKey(k, v.key, v.shift, v.ctrl, v.alt, v.super, true); + hotkeyModifier->onChange([&](const std::string &id, Hotkey hk) { + ConfigurationService &cfgs = Editor::instance->getConfiguration(); + hks.removeHotkey(id); + hks.addHotkey(id, hk); + cfgs.setStringValue("hotkeys", id, + to_string(HotkeyService::pack(hk))); + cfgs.saveConfiguration(); + }); + + container->setSize({"100%", 32}); + container->add(hotkeyLabel); + container->add(hotkeyModifier); + layout->add(container); + } + + scrollPanel->add(layout); + this->currentWindow->add(scrollPanel); } diff --git a/src/editor/screens/projectScreen.cpp b/src/editor/screens/projectScreen.cpp index e464920..74e1980 100644 --- a/src/editor/screens/projectScreen.cpp +++ b/src/editor/screens/projectScreen.cpp @@ -72,13 +72,9 @@ void ProjectScreen::bindMenuBarAndHK(tgui::MenuBar::Ptr menuBarPtr) { } }; - auto undoAction = [this] { - getCurrentFile().getView().undoAction(); - }; + auto undoAction = [this] { getCurrentFile().getView().undoAction(); }; - auto redoAction = [this] { - getCurrentFile().getView().redoAction(); - }; + auto redoAction = [this] { getCurrentFile().getView().redoAction(); }; std::vector saveFileHierarchy = { ts.getKey("menu.file._label"), ts.getKey("menu.file.save_file")}; @@ -180,7 +176,8 @@ void ProjectScreen::initItems(tgui::Group::Ptr layout) { } }); - Editor::instance->getHotkeyService().registerHotkeyCallback("close_tab", [this] { fileTabs->closeCurrentTab(); }); + Editor::instance->getHotkeyService().registerHotkeyCallback( + "close_tab", [this] { fileTabs->closeCurrentTab(); }); tabsContainer->add(fileTabs); layout->add(tabsContainer); diff --git a/src/editor/screens/welcomeScreen.cpp b/src/editor/screens/welcomeScreen.cpp index 0521915..bd23f76 100644 --- a/src/editor/screens/welcomeScreen.cpp +++ b/src/editor/screens/welcomeScreen.cpp @@ -59,10 +59,8 @@ void screens::WelcomeScreen::initItems(tgui::Group::Ptr layout) { &tgui::Button::setText); openProjButton->setTextSize(ACTION_BUTTON_SIZE); - newProjButton->onPress([this] { - Editor::instance->getFs().promptNewProject(); - }); - + newProjButton->onPress( + [this] { Editor::instance->getFs().promptNewProject(); }); openProjButton->onPress( [] { Editor::instance->getFs().promptOpenProject(); }); diff --git a/src/editor/services/configurationService.cpp b/src/editor/services/configurationService.cpp index 62fe55d..4814e78 100644 --- a/src/editor/services/configurationService.cpp +++ b/src/editor/services/configurationService.cpp @@ -1,11 +1,11 @@ #include "services/configurationService.hpp" +#include "defaultConfig.hpp" #include "ini.h" #include "raylib.h" #include #include #include #include -#include "defaultConfig.hpp" void ConfigurationService::regenerate() { assert(this->iniFile && "iniFile is not initialized"); @@ -14,7 +14,8 @@ void ConfigurationService::regenerate() { if (!this->iniStructure.has(field)) { this->iniStructure.set(field, mINI::INIMap()); } - if (this->iniStructure[field].has(k)) continue; + if (this->iniStructure[field].has(k)) + continue; this->iniStructure[field].set(k, v); } } @@ -34,11 +35,13 @@ ConfigurationService::ConfigurationService() { this->regenerate(); }; -mINI::INIMap> ConfigurationService::getField(const std::string &field) { +mINI::INIMap> +ConfigurationService::getField(const std::string &field) { return this->iniStructure[field]; } -std::string ConfigurationService::getStringValue(const std::string& field, const std::string &key) { +std::string ConfigurationService::getStringValue(const std::string &field, + const std::string &key) { return this->iniStructure[field][key]; } @@ -46,7 +49,9 @@ std::string ConfigurationService::getStringValue(const std::string &key) { return this->getStringValue(GENERAL_CONF_FIELD, key); } -void ConfigurationService::setStringValue(const std::string& field, const std::string &key, const std::string &value) { +void ConfigurationService::setStringValue(const std::string &field, + const std::string &key, + const std::string &value) { this->iniStructure[field].set(key, value); } diff --git a/src/editor/services/editorGuiService.cpp b/src/editor/services/editorGuiService.cpp index d709b61..7321575 100644 --- a/src/editor/services/editorGuiService.cpp +++ b/src/editor/services/editorGuiService.cpp @@ -44,18 +44,17 @@ void EditorGuiService::init() { hks.deserialize(cfgs.getField("hotkeys")); auto serialized = hks.serialize(); - for (auto &[keyId, keyStr]: serialized) { + for (auto &[keyId, keyStr] : serialized) { std::cout << keyId << ": " << keyStr << std::endl; } this->resetUi(); - hks.registerHotkeyCallback("toggle_debug", [this]() { perfOverlay.Toggle(); }); - hks.registerHotkeyCallback("new_project", [] { - Editor::instance->getFs().promptNewProject(); - }); - hks.registerHotkeyCallback("open_project", [] { - Editor::instance->getFs().promptOpenProject(); - }); + hks.registerHotkeyCallback("toggle_debug", + [this]() { perfOverlay.Toggle(); }); + hks.registerHotkeyCallback( + "new_project", [] { Editor::instance->getFs().promptNewProject(); }); + hks.registerHotkeyCallback( + "open_project", [] { Editor::instance->getFs().promptOpenProject(); }); } void EditorGuiService::resetUi() { @@ -107,14 +106,20 @@ void EditorGuiService::uiLoop() { tgui::Theme::addRendererInheritanceParent("RoomToolbox", "Tabs"); // main loop. while (!WindowShouldClose()) { - Editor::instance->getHotkeyService().fire(); perfOverlay.Update(); cg->handleEvents(); - while (const int pressed_char = GetCharPressed()) - cg->handleCharPressed(pressed_char); - while (const int pressedKey = GetKeyPressed()) + int pressedChar = GetCharPressed(); + while (pressedChar) { + cg->handleCharPressed(pressedChar); + pressedChar = GetCharPressed(); + } + int pressedKey = GetKeyPressed(); + while (pressedKey) { + Editor::instance->getHotkeyService().fire(); cg->handleKeyPressed(pressedKey); + pressedKey = GetKeyPressed(); + } for (const auto &widget : updatableWidgets) { if (!widget.expired()) { @@ -256,20 +261,19 @@ void EditorGuiService::initMenuBar() { auto &ts = Editor::instance->getTranslations(); const auto &fileT = ts.getKey("menu.file._label"); - const auto &fileOpenProjectT = - ts.getKey("menu.file.open_project"); + const auto &fileOpenProjectT = ts.getKey("menu.file.open_project"); const auto &fileNewProjectT = ts.getKey("menu.file.new_project"); menuBarPtr->addMenu(fileT); menuBarPtr->addMenuItem(fileNewProjectT); - menuBarPtr->connectMenuItem( - {fileT, fileNewProjectT}, - [] { Editor::instance->getFs().promptNewProject(); }); + menuBarPtr->connectMenuItem({fileT, fileNewProjectT}, [] { + Editor::instance->getFs().promptNewProject(); + }); menuBarPtr->addMenuItem(fileOpenProjectT); menuBarPtr->addMenuItem(ts.getKey("menu.file.save_file")); - menuBarPtr->connectMenuItem( - {fileT, fileOpenProjectT}, - [] { Editor::instance->getFs().promptOpenProject(); }); + menuBarPtr->connectMenuItem({fileT, fileOpenProjectT}, [] { + Editor::instance->getFs().promptOpenProject(); + }); menuBarPtr->addMenu(ts.getKey("menu.edit._label")); menuBarPtr->addMenuItem(ts.getKey("menu.edit.undo")); diff --git a/src/editor/services/hotkeyService.cpp b/src/editor/services/hotkeyService.cpp index e83b40a..c125a17 100644 --- a/src/editor/services/hotkeyService.cpp +++ b/src/editor/services/hotkeyService.cpp @@ -1,84 +1,95 @@ #include "services/hotkeyService.hpp" #include "raylib.h" +#include +#include HotkeyService::HotkeyService() {} +const int HotkeyService::pack(Hotkey hk) { + int k = 0; + k |= hk.ctrl * (1 << 0); + k |= hk.shift * (1 << 1); + k |= hk.alt * (1 << 2); + k |= hk.super * (1 << 3); + k |= hk.key << 4; + return k; +} + +const Hotkey HotkeyService::unpack(int packed) { + return { + .ctrl = (bool)((packed >> 0) & 1), + .shift = (bool)((packed >> 1) & 1), + .alt = (bool)((packed >> 2) & 1), + .super = (bool)((packed >> 3) & 1), + .key = static_cast(packed >> 4), + }; +} + std::map HotkeyService::serialize() { std::map serialized{}; - for (auto &[keyId, keys]: hotkeyMap) { - std::string keyStr{}; - for (KeyboardKey k : keys) { - keyStr += std::to_string(k) + ","; - } + for (auto &[keyId, keys] : hotkeyMap) { + int k = pack(keys); + std::string keyStr = std::to_string(k); serialized[keyId] = keyStr; } return serialized; } -void HotkeyService::write(const std::string& keyId, const std::string& keyStr) { - std::vector keys{}; - std::stringstream ss(keyStr); - std::string token; - while (std::getline(ss, token, ',')) { - if (!token.empty()) - keys.push_back(static_cast(std::stoi(token))); - } - hotkeyMap[keyId] = keys; +void HotkeyService::write(const std::string &keyId, const std::string &keyStr) { + int k = stoi(keyStr); + hotkeyMap[keyId] = unpack(k); } -void HotkeyService::deserialize(const std::map &serialized) { +void HotkeyService::deserialize( + const std::map &serialized) { hotkeyMap.clear(); - for (auto &[keyId, keyStr]: serialized) { + for (auto &[keyId, keyStr] : serialized) { write(keyId, keyStr); } } -void HotkeyService::deserialize(mINI::INIMap> iniSerialized) { +void HotkeyService::deserialize( + mINI::INIMap> iniSerialized) { hotkeyMap.clear(); - for (auto &[keyId, keyStr]: iniSerialized) { + for (auto &[keyId, keyStr] : iniSerialized) { write(keyId, keyStr); } } -void HotkeyService::registerHotkeyCallback(const std::string& keyId, std::function cb) { - activatedHotkey[keyId] = false; +void HotkeyService::registerHotkeyCallback(const std::string &keyId, + std::function cb) { hotkeysCb.insert({keyId, cb}); } -void HotkeyService::unregisterHotkeyCallback(const std::string& keyId) { +void HotkeyService::unregisterHotkeyCallback(const std::string &keyId) { hotkeysCb.erase(keyId); - if (hotkeyMap.find(keyId) == hotkeyMap.end()) - activatedHotkey.erase(keyId); } -void HotkeyService::addHotkey(const std::string& keyId, std::vector keys) { - activatedHotkey[keyId] = false; +void HotkeyService::addHotkey(const std::string &keyId, const Hotkey &keys) { hotkeyMap[keyId] = keys; } -void HotkeyService::removeHotkey(const std::string& keyId) { +void HotkeyService::removeHotkey(const std::string &keyId) { hotkeyMap.erase(keyId); - if (hotkeysCb.find(keyId) == hotkeysCb.end()) - activatedHotkey.erase(keyId); } +const HotkeyMap HotkeyService::listHotkeys() { return this->hotkeyMap; } + void HotkeyService::fire() { for (auto &[keyId, keys] : hotkeyMap) { - if (keys.empty()) continue; - bool allDown = true; - for (KeyboardKey k : keys) { - if (!IsKeyDown(k)) { - allDown = false; - activatedHotkey[keyId] = false; - break; - } - } - if (allDown && !activatedHotkey[keyId]) { + if ((keys.ctrl ^ IsKeyDown(KEY_LEFT_CONTROL))) + continue; + if ((keys.shift ^ IsKeyDown(KEY_LEFT_SHIFT))) + continue; + if ((keys.alt ^ IsKeyDown(KEY_LEFT_ALT))) + continue; + if ((keys.super ^ IsKeyDown(KEY_LEFT_SUPER))) + continue; + if (IsKeyDown(keys.key)) { auto range = hotkeysCb.equal_range(keyId); for (auto it = range.first; it != range.second; ++it) { it->second(); } - activatedHotkey[keyId] = true; } } } diff --git a/src/editor/widgets/hotkeyModifier.cpp b/src/editor/widgets/hotkeyModifier.cpp new file mode 100644 index 0000000..8883761 --- /dev/null +++ b/src/editor/widgets/hotkeyModifier.cpp @@ -0,0 +1,484 @@ +#include "TGUI/SubwidgetContainer.hpp" +#include "TGUI/Widgets/Button.hpp" +#include "bindTranslation.hpp" +#include "raylib.h" +#include "services/translationService.hpp" +#include +#include +// this is the most unholy shit i've wrote +const KeyboardKey tguiToRaylibKey(tguiKey k) { + switch (k) { + case tguiKey::A: + return KEY_A; + case tguiKey::B: + return KEY_B; + case tguiKey::C: + return KEY_C; + case tguiKey::D: + return KEY_D; + case tguiKey::E: + return KEY_E; + case tguiKey::F: + return KEY_F; + case tguiKey::G: + return KEY_G; + case tguiKey::H: + return KEY_H; + case tguiKey::I: + return KEY_I; + case tguiKey::J: + return KEY_J; + case tguiKey::K: + return KEY_K; + case tguiKey::L: + return KEY_L; + case tguiKey::M: + return KEY_M; + case tguiKey::N: + return KEY_N; + case tguiKey::O: + return KEY_O; + case tguiKey::P: + return KEY_P; + case tguiKey::Q: + return KEY_Q; + case tguiKey::R: + return KEY_R; + case tguiKey::S: + return KEY_S; + case tguiKey::T: + return KEY_T; + case tguiKey::U: + return KEY_U; + case tguiKey::V: + return KEY_V; + case tguiKey::W: + return KEY_W; + case tguiKey::X: + return KEY_X; + case tguiKey::Y: + return KEY_Y; + case tguiKey::Z: + return KEY_Z; + case tguiKey::Num0: + return KEY_ZERO; + case tguiKey::Num1: + return KEY_ONE; + case tguiKey::Num2: + return KEY_TWO; + case tguiKey::Num3: + return KEY_THREE; + case tguiKey::Num4: + return KEY_FOUR; + case tguiKey::Num5: + return KEY_FIVE; + case tguiKey::Num6: + return KEY_SIX; + case tguiKey::Num7: + return KEY_SEVEN; + case tguiKey::Num8: + return KEY_EIGHT; + case tguiKey::Num9: + return KEY_NINE; + case tguiKey::Escape: + return KEY_ESCAPE; + case tguiKey::LControl: + return KEY_LEFT_CONTROL; + case tguiKey::LShift: + return KEY_LEFT_SHIFT; + case tguiKey::LAlt: + return KEY_LEFT_ALT; + case tguiKey::LSystem: + return KEY_LEFT_SUPER; + case tguiKey::RControl: + return KEY_RIGHT_CONTROL; + case tguiKey::RShift: + return KEY_RIGHT_SHIFT; + case tguiKey::RAlt: + return KEY_RIGHT_ALT; + case tguiKey::RSystem: + return KEY_RIGHT_SUPER; + case tguiKey::Menu: + return KEY_KB_MENU; + case tguiKey::LBracket: + return KEY_LEFT_BRACKET; + case tguiKey::RBracket: + return KEY_RIGHT_BRACKET; + case tguiKey::Semicolon: + return KEY_SEMICOLON; + case tguiKey::Comma: + return KEY_COMMA; + case tguiKey::Period: + return KEY_PERIOD; + case tguiKey::Quote: + return KEY_APOSTROPHE; + case tguiKey::Slash: + return KEY_SLASH; + case tguiKey::Backslash: + return KEY_BACKSLASH; + case tguiKey::Equal: + return KEY_EQUAL; + case tguiKey::Minus: + return KEY_MINUS; + case tguiKey::Space: + return KEY_SPACE; + case tguiKey::Enter: + return KEY_ENTER; + case tguiKey::Backspace: + return KEY_BACKSPACE; + case tguiKey::Tab: + return KEY_TAB; + case tguiKey::PageUp: + return KEY_PAGE_UP; + case tguiKey::PageDown: + return KEY_PAGE_DOWN; + case tguiKey::End: + return KEY_END; + case tguiKey::Home: + return KEY_HOME; + case tguiKey::Insert: + return KEY_INSERT; + case tguiKey::Delete: + return KEY_DELETE; + case tguiKey::Add: + return KEY_KP_ADD; + case tguiKey::Subtract: + return KEY_KP_SUBTRACT; + case tguiKey::Multiply: + return KEY_KP_MULTIPLY; + case tguiKey::Divide: + return KEY_KP_DIVIDE; + case tguiKey::Left: + return KEY_LEFT; + case tguiKey::Right: + return KEY_RIGHT; + case tguiKey::Up: + return KEY_UP; + case tguiKey::Down: + return KEY_DOWN; + case tguiKey::Numpad0: + return KEY_KP_0; + case tguiKey::Numpad1: + return KEY_KP_1; + case tguiKey::Numpad2: + return KEY_KP_2; + case tguiKey::Numpad3: + return KEY_KP_3; + case tguiKey::Numpad4: + return KEY_KP_4; + case tguiKey::Numpad5: + return KEY_KP_5; + case tguiKey::Numpad6: + return KEY_KP_6; + case tguiKey::Numpad7: + return KEY_KP_7; + case tguiKey::Numpad8: + return KEY_KP_8; + case tguiKey::Numpad9: + return KEY_KP_9; + case tguiKey::F1: + return KEY_F1; + case tguiKey::F2: + return KEY_F2; + case tguiKey::F3: + return KEY_F3; + case tguiKey::F4: + return KEY_F4; + case tguiKey::F5: + return KEY_F5; + case tguiKey::F6: + return KEY_F6; + case tguiKey::F7: + return KEY_F7; + case tguiKey::F8: + return KEY_F8; + case tguiKey::F9: + return KEY_F9; + case tguiKey::F10: + return KEY_F10; + case tguiKey::F11: + return KEY_F11; + case tguiKey::F12: + return KEY_F12; + case tguiKey::Tilde: + case tguiKey::F13: + case tguiKey::F14: + case tguiKey::F15: + case tguiKey::Pause: + case tguiKey::Unknown: + return KEY_NULL; + } +} +const std::string keyboardKeyToName(KeyboardKey k) { + switch (k) { + case KEY_NULL: + return "Unknown"; + case KEY_APOSTROPHE: + return "'"; + case KEY_COMMA: + return ","; + case KEY_MINUS: + return "-"; + case KEY_PERIOD: + return "."; + case KEY_SLASH: + return "/"; + case KEY_ZERO: + return "0"; + case KEY_ONE: + return "1"; + case KEY_TWO: + return "2"; + case KEY_THREE: + return "3"; + case KEY_FOUR: + return "4"; + case KEY_FIVE: + return "5"; + case KEY_SIX: + return "6"; + case KEY_SEVEN: + return "7"; + case KEY_EIGHT: + return "8"; + case KEY_NINE: + return "9"; + case KEY_SEMICOLON: + return ";"; + case KEY_EQUAL: + return "="; + case KEY_A: + return "A"; + case KEY_B: + return "B"; + case KEY_C: + return "C"; + case KEY_D: + return "D"; + case KEY_E: + return "E"; + case KEY_F: + return "F"; + case KEY_G: + return "G"; + case KEY_H: + return "H"; + case KEY_I: + return "I"; + case KEY_J: + return "J"; + case KEY_K: + return "K"; + case KEY_L: + return "L"; + case KEY_M: + return "M"; + case KEY_N: + return "N"; + case KEY_O: + return "O"; + case KEY_P: + return "P"; + case KEY_Q: + return "Q"; + case KEY_R: + return "R"; + case KEY_S: + return "S"; + case KEY_T: + return "T"; + case KEY_U: + return "U"; + case KEY_V: + return "V"; + case KEY_W: + return "W"; + case KEY_X: + return "X"; + case KEY_Y: + return "Y"; + case KEY_Z: + return "Z"; + case KEY_LEFT_BRACKET: + return "["; + case KEY_BACKSLASH: + return "\\"; + case KEY_RIGHT_BRACKET: + return "]"; + case KEY_SPACE: + return "Space"; + case KEY_ESCAPE: + return "Esc"; + case KEY_ENTER: + return "Enter"; + case KEY_TAB: + return "Tab"; + case KEY_BACKSPACE: + return "Backspace"; + case KEY_INSERT: + return "Ins"; + case KEY_DELETE: + return "Del"; + case KEY_RIGHT: + return "Right"; + case KEY_LEFT: + return "Left"; + case KEY_DOWN: + return "Down"; + case KEY_UP: + return "Up"; + case KEY_PAGE_UP: + return "PgUp"; + case KEY_PAGE_DOWN: + return "PgDn"; + case KEY_HOME: + return "Home"; + case KEY_END: + return "End"; + case KEY_F1: + return "F1"; + case KEY_F2: + return "F2"; + case KEY_F3: + return "F3"; + case KEY_F4: + return "F4"; + case KEY_F5: + return "F5"; + case KEY_F6: + return "F6"; + case KEY_F7: + return "F7"; + case KEY_F8: + return "F8"; + case KEY_F9: + return "F9"; + case KEY_F10: + return "F10"; + case KEY_F11: + return "F11"; + case KEY_F12: + return "F12"; + case KEY_LEFT_SHIFT: + return "Shift"; + case KEY_LEFT_CONTROL: + return "Control"; + case KEY_LEFT_ALT: + return "Alt"; + case KEY_LEFT_SUPER: + return "Super"; + case KEY_RIGHT_SHIFT: + return "RightShift"; + case KEY_RIGHT_CONTROL: + return "RightControl"; + case KEY_RIGHT_ALT: + return "RightAlt"; + case KEY_RIGHT_SUPER: + return "RightSuper"; + case KEY_KB_MENU: + return "KBMenu"; + case KEY_KP_0: + return "Keypad0"; + case KEY_KP_1: + return "Keypad1"; + case KEY_KP_2: + return "Keypad2"; + case KEY_KP_3: + return "Keypad3"; + case KEY_KP_4: + return "Keypad4"; + case KEY_KP_5: + return "Keypad5"; + case KEY_KP_6: + return "Keypad6"; + case KEY_KP_7: + return "Keypad7"; + case KEY_KP_8: + return "Keypad8"; + case KEY_KP_9: + return "Keypad9"; + case KEY_KP_DIVIDE: + return "Keypad /"; + case KEY_KP_MULTIPLY: + return "Keypad *"; + case KEY_KP_SUBTRACT: + return "Keypad -"; + case KEY_KP_ADD: + return "Keypad +"; + } +} + +HotkeyModifier::HotkeyModifier(const char *typeName, bool initRenderer) + : tgui::SubwidgetContainer(typeName, initRenderer) { + this->button = tgui::Button::create(); + this->button->setSize({"100%", "100%"}); + this->button->onClick([this]() { + TranslationService &ts = Editor::instance->getTranslations(); + if (modifingState == State::DEFAULT) { + modifingState = State::START_EDITING; + this->button->setText( + ts.getKey("widget.hotkey_modifier.listening")); + } + }); + m_container->add(this->button); +} + +HotkeyModifier::Ptr HotkeyModifier::create() { + return std::make_shared(); +} + +HotkeyModifier::Ptr HotkeyModifier::copy(HotkeyModifier::ConstPtr widget) { + if (widget) + return std::static_pointer_cast(widget->clone()); + else + return nullptr; +} + +tgui::Widget::Ptr HotkeyModifier::clone() const { + return std::make_shared(*this); +} + +void HotkeyModifier::setKey(const std::string &id, KeyboardKey key, + bool isShift, bool isCtrl, bool isAlt, bool isSuper, + bool override) { + if (modifingState == State::DEFAULT && !override) + return; + keys.clear(); + if (isSuper) { + keys.push_back(KEY_LEFT_SUPER); + } + if (isCtrl) { + keys.push_back(KEY_LEFT_CONTROL); + } + if (isShift) { + keys.push_back(KEY_LEFT_SHIFT); + } + if (isAlt) { + keys.push_back(KEY_LEFT_ALT); + } + keys.push_back(key); + std::string label{}; + for (auto i = keys.begin(); i != keys.end(); ++i) { + if (i != keys.begin()) + label += "+"; + label += keyboardKeyToName(*i); + } + this->button->setText(label); + + hk = { + .ctrl = isCtrl, + .shift = isShift, + .alt = isAlt, + .super = isSuper, + .key = key, + }; + this->id = id; +} + +void HotkeyModifier::keyPressed(const tgui::Event::KeyEvent &event) { + tgui::SubwidgetContainer::keyPressed(event); + setKey(this->id, tguiToRaylibKey(event.code), event.shift, event.control, + event.alt, event.system); + modifingState = State::DEFAULT; + onChange.emit(this, this->id, hk); +} From 02c375eb70f03fd0d7b059b0a75a6be21625a244 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Mon, 23 Mar 2026 12:13:26 +0700 Subject: [PATCH 21/35] Bind hotkeys for RoomFileView --- include/editor/fileViews/fileView.hpp | 2 + include/editor/fileViews/roomFileView.hpp | 6 +++ include/editor/screens/projectScreen.hpp | 2 + include/editor/services/hotkeyService.hpp | 6 +-- include/editor/widgets/toolbox.hpp | 16 +++++++ src/editor/fileViews/roomFileView.cpp | 55 ++++++++++++++++------- src/editor/screens/projectScreen.cpp | 8 +++- src/editor/services/hotkeyService.cpp | 39 ++++++++++++---- 8 files changed, 105 insertions(+), 29 deletions(-) diff --git a/include/editor/fileViews/fileView.hpp b/include/editor/fileViews/fileView.hpp index caae85c..bd9d51a 100644 --- a/include/editor/fileViews/fileView.hpp +++ b/include/editor/fileViews/fileView.hpp @@ -20,6 +20,8 @@ class FileView { std::stack> future; public: + bool fileViewFocused = false; + FileView(); virtual ~FileView(); diff --git a/include/editor/fileViews/roomFileView.hpp b/include/editor/fileViews/roomFileView.hpp index 2cc92b6..9af1c03 100644 --- a/include/editor/fileViews/roomFileView.hpp +++ b/include/editor/fileViews/roomFileView.hpp @@ -6,8 +6,10 @@ #include "roomViewModesHandler.hpp" #include "views/roomView.hpp" #include "views/tileSetView.hpp" +#include "views/worldView.hpp" #include "widgets/propertyFields/fileField.hpp" #include "widgets/propertyFields/intField.hpp" +#include "widgets/toolbox.hpp" #include #include @@ -28,9 +30,13 @@ class RoomFileView : public FileView { FileField::Ptr tileSetField; FileField::Ptr musicFileField; + void setRoomTool(ToolboxItem tool); + std::vector hotkeyEntries; + public: std::unique_ptr modesHandler; RoomFileView(); + ~RoomFileView(); void init(tgui::Group::Ptr layout, VariantWrapper *variant) override; }; diff --git a/include/editor/screens/projectScreen.hpp b/include/editor/screens/projectScreen.hpp index d7ec927..41e97c7 100644 --- a/include/editor/screens/projectScreen.hpp +++ b/include/editor/screens/projectScreen.hpp @@ -47,6 +47,8 @@ class ProjectScreen : public UIScreen { tgui::ContextMenu::Ptr fileContextMenu; tgui::Label::Ptr projectLabel; + tgui::String focusedFile; + private: void switchView(tgui::String id); void clearView(); diff --git a/include/editor/services/hotkeyService.hpp b/include/editor/services/hotkeyService.hpp index 7f5a570..c901d6e 100644 --- a/include/editor/services/hotkeyService.hpp +++ b/include/editor/services/hotkeyService.hpp @@ -19,15 +19,15 @@ struct Hotkey { using HotkeyMap = std::unordered_map; class HotkeyService { private: - std::multimap> hotkeysCb; + std::map>> hotkeysCb; void write(const std::string &keyId, const std::string &keyStr); HotkeyMap hotkeyMap; public: HotkeyService(); - void registerHotkeyCallback(const std::string &keyId, + std::string registerHotkeyCallback(const std::string &keyId, std::function cb); - void unregisterHotkeyCallback(const std::string &keyId); + void unregisterHotkeyCallback(const std::string &uniqueHkCbId); void addHotkey(const std::string &keyId, const Hotkey &keys); void removeHotkey(const std::string &keyId); const HotkeyMap listHotkeys(); diff --git a/include/editor/widgets/toolbox.hpp b/include/editor/widgets/toolbox.hpp index 19e7274..358e823 100644 --- a/include/editor/widgets/toolbox.hpp +++ b/include/editor/widgets/toolbox.hpp @@ -1,6 +1,7 @@ #ifndef _RPGPP_TOOLBOX2_H #define _RPGPP_TOOLBOX2_H #include "TGUI/Backend/Renderer/BackendRenderTarget.hpp" +#include "TGUI/Vector2.hpp" #include "TGUI/Widgets/BitmapButton.hpp" #include "TGUI/Widgets/Button.hpp" #include "TGUI/Widgets/GrowHorizontalLayout.hpp" @@ -49,6 +50,7 @@ template class Toolbox : public tgui::ScrollablePanel { void addTool(const ToolboxItem &item, int idx = -1); void addWidget(tgui::Widget::Ptr widget, int idx = -1); void removeItemById(const Type &id); + void selectTool(const ToolboxItem &item); tgui::SignalTyped &> onItemClicked = { "OnItemClicked"}; @@ -114,6 +116,20 @@ void Toolbox::resetToolSelection(std::string groupToReset) { } } +template +void Toolbox::selectTool(const ToolboxItem &item) { + for (const auto &widgets : this->container->getWidgets()) { + if (auto btn = std::dynamic_pointer_cast(widgets)) { + ToolboxItemIdentifier identifier = + btn->template getUserData>(); + if (identifier.id == item.id) { + btn->onClick.emit(btn.get(), tgui::Vector2f{1, 1}); + return; + } + } + } +} + template void Toolbox::addTool(const ToolboxItem &item, int idx) { tgui::Texture texture( diff --git a/src/editor/fileViews/roomFileView.cpp b/src/editor/fileViews/roomFileView.cpp index a694ee2..a1f2738 100644 --- a/src/editor/fileViews/roomFileView.cpp +++ b/src/editor/fileViews/roomFileView.cpp @@ -22,6 +22,7 @@ RoomFileView::RoomFileView() { RoomTool a; TranslationService &ts = Editor::instance->getTranslations(); + HotkeyService &hks = Editor::instance->getHotkeyService(); roomView = RoomView::create(); roomView->setSize({TextFormat("100%% - %d", RIGHT_PANEL_W), @@ -133,17 +134,26 @@ RoomFileView::RoomFileView() { toolbox->getHorizontalScrollbar()->setPolicy( tgui::Scrollbar::Policy::Never); toolbox->setSize({TextFormat("100%% - %d", RIGHT_PANEL_W), TOOLBOX_H}); - toolbox->addTool(ToolboxItem{"tool", RoomTool::TOOL_NONE, "Mouse", - "tool_none.png"}); - toolbox->addTool(ToolboxItem{"tool", RoomTool::TOOL_PLACE, - "Place", "tool_place.png"}); - toolbox->addTool(ToolboxItem{"tool", RoomTool::TOOL_ERASE, - "Erase", "tool_erase.png"}); - toolbox->addTool(ToolboxItem{"tool", RoomTool::TOOL_EDIT, "Edit", - "tool_edit.png"}); - toolbox->addTool(ToolboxItem{"tool", RoomTool::TOOL_STARTPOINT, + + std::vector>> tools = { + {"room_tool.mouse", ToolboxItem{"tool", RoomTool::TOOL_NONE, "Mouse", + "tool_none.png"}}, + {"room_tool.pen", ToolboxItem{"tool", RoomTool::TOOL_PLACE, + "Place", "tool_place.png"}}, + {"room_tool.eraser", ToolboxItem{"tool", RoomTool::TOOL_ERASE, + "Erase", "tool_erase.png"}}, + {"room_tool.edit", ToolboxItem{"tool", RoomTool::TOOL_EDIT, "Edit", + "tool_edit.png"}}, + {"room_tool.set_spoint", ToolboxItem{"tool", RoomTool::TOOL_STARTPOINT, "Start Point", - "tool_startpoint.png"}); + "tool_startpoint.png"}} + }; + + for (auto &[k, tool] : tools) { + auto capturedTool = tool; + toolbox->addTool(tool); + hotkeyEntries.push_back(hks.registerHotkeyCallback(k, [this, capturedTool, toolbox](){ if (fileViewFocused) toolbox->selectTool(capturedTool); })); + } auto brushToggle = tgui::CheckBox::create(); bindTranslation(brushToggle, @@ -157,17 +167,28 @@ RoomFileView::RoomFileView() { toolbox->addWidget(brushToggle); toolbox->onItemClicked([this](ToolboxItem tool) { - tileSetView->setTool(tool.id); - roomView->setTool(tool.id); - layerVisitor.tool = tool.id; - layerVisitor.group->removeAllWidgets(); - mj::visit(layerVisitor, - static_cast(layerChoose->getSelectedItemIndex())); - cout << "Selected tool: " << tool.text << endl; + setRoomTool(tool); }); + widgetContainer.push_back(toolbox); } +void RoomFileView::setRoomTool(ToolboxItem tool) { + tileSetView->setTool(tool.id); + roomView->setTool(tool.id); + layerVisitor.tool = tool.id; + layerVisitor.group->removeAllWidgets(); + mj::visit(layerVisitor, + static_cast(layerChoose->getSelectedItemIndex())); + cout << "Selected tool: " << tool.text << endl; +} +RoomFileView::~RoomFileView() { + HotkeyService &hks = Editor::instance->getHotkeyService(); + for (const auto &entry : hotkeyEntries) { + hks.unregisterHotkeyCallback(entry); + } +} + void RoomFileView::init(tgui::Group::Ptr layout, VariantWrapper *variant) { if (variant == nullptr) return; diff --git a/src/editor/screens/projectScreen.cpp b/src/editor/screens/projectScreen.cpp index 74e1980..d6b2bdc 100644 --- a/src/editor/screens/projectScreen.cpp +++ b/src/editor/screens/projectScreen.cpp @@ -1,5 +1,4 @@ #include "screens/projectScreen.hpp" -#include "TGUI/Color.hpp" #include "TGUI/Layout.hpp" #include "TGUI/String.hpp" #include "TGUI/Texture.hpp" @@ -220,16 +219,23 @@ void ProjectScreen::addFileView(EngineFileType fileType, projectFile->initUi(fileViewGroup); projectFile->setFilePath(path); tgui::String id = path; + focusedFile = id; + projectFile->getView().fileViewFocused = true; openedFiles.try_emplace(id, std::move(projectFile)); } } void ProjectScreen::switchView(tgui::String id) { + if (openedFiles.find(focusedFile) != openedFiles.end()) + openedFiles.at(focusedFile)->getView().fileViewFocused = false; + focusedFile = id; fileViewGroup->removeAllWidgets(); openedFiles.at(id)->addWidgets(fileViewGroup); + openedFiles.at(id)->getView().fileViewFocused = true; } void ProjectScreen::clearView() { + focusedFile = ""; fileViewGroup->removeAllWidgets(); std::unique_ptr empty = fileVisitor->visit(EngineFileType::FILE_EMPTY, "."); diff --git a/src/editor/services/hotkeyService.cpp b/src/editor/services/hotkeyService.cpp index c125a17..898ff4c 100644 --- a/src/editor/services/hotkeyService.cpp +++ b/src/editor/services/hotkeyService.cpp @@ -1,10 +1,30 @@ #include "services/hotkeyService.hpp" #include "raylib.h" #include +#include #include - HotkeyService::HotkeyService() {} +// It's not true UUID, but it will work in this case +// https://stackoverflow.com/a/58467162 +std::string get_uuid() { + static std::random_device dev; + static std::mt19937 rng(dev()); + + std::uniform_int_distribution dist(0, 15); + + const char *v = "0123456789abcdef"; + const bool dash[] = { 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0 }; + + std::string res; + for (int i = 0; i < 16; i++) { + if (dash[i]) res += "-"; + res += v[dist(rng)]; + res += v[dist(rng)]; + } + return res; +} + const int HotkeyService::pack(Hotkey hk) { int k = 0; k |= hk.ctrl * (1 << 0); @@ -56,13 +76,15 @@ void HotkeyService::deserialize( } } -void HotkeyService::registerHotkeyCallback(const std::string &keyId, +std::string HotkeyService::registerHotkeyCallback(const std::string &keyId, std::function cb) { - hotkeysCb.insert({keyId, cb}); + std::string uniqueHkCbId = get_uuid(); + hotkeysCb[uniqueHkCbId] = {keyId, cb}; + return uniqueHkCbId; } -void HotkeyService::unregisterHotkeyCallback(const std::string &keyId) { - hotkeysCb.erase(keyId); +void HotkeyService::unregisterHotkeyCallback(const std::string &uniqueHkCbId) { + hotkeysCb.erase(uniqueHkCbId); } void HotkeyService::addHotkey(const std::string &keyId, const Hotkey &keys) { @@ -86,9 +108,10 @@ void HotkeyService::fire() { if ((keys.super ^ IsKeyDown(KEY_LEFT_SUPER))) continue; if (IsKeyDown(keys.key)) { - auto range = hotkeysCb.equal_range(keyId); - for (auto it = range.first; it != range.second; ++it) { - it->second(); + for (auto [_, data] : hotkeysCb) { + if (data.first == keyId) { + data.second(); + } } } } From 6619511624660588397e497f0a195cae9fb0e3c3 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:46:46 +0700 Subject: [PATCH 22/35] Turns out designated initializers are C++20 features. --- include/editor/defaultConfig.hpp | 26 +++++++++++------------ include/editor/services/hotkeyService.hpp | 16 +++++++++----- src/editor/services/hotkeyService.cpp | 12 +++++------ src/editor/widgets/hotkeyModifier.cpp | 9 ++------ 4 files changed, 32 insertions(+), 31 deletions(-) diff --git a/include/editor/defaultConfig.hpp b/include/editor/defaultConfig.hpp index d6c7478..67f0d94 100644 --- a/include/editor/defaultConfig.hpp +++ b/include/editor/defaultConfig.hpp @@ -22,17 +22,17 @@ const Config BASE_CONFIG = { {"theme", "Dark"}, }}, {"hotkeys", - {{"close_tab", h({.ctrl = true, .key = KEY_W})}, - {"new_project", h({.ctrl = true, .key = KEY_N})}, - {"open_project", h({.ctrl = true, .key = KEY_O})}, - {"redo", h({.ctrl = true, .key = KEY_Y})}, - {"save_file", h({.ctrl = true, .key = KEY_S})}, - {"toggle_debug", h({.key = KEY_F3})}, - {"undo", h({.ctrl = true, .key = KEY_Z})}, - {"room_tool.mouse", h({.key = KEY_ONE})}, - {"room_tool.pen", h({.key = KEY_TWO})}, - {"room_tool.eraser", h({.key = KEY_THREE})}, - {"room_tool.edit", h({.key = KEY_FOUR})}, - {"room_tool.set_spoint", h({.key = KEY_FIVE})}, - {"room_tool.toggle_bm", h({.key = KEY_SIX})}}}, + {{"close_tab", h(Hotkey{}.withCtrl().withKey(KEY_W))}, + {"new_project", h(Hotkey{}.withCtrl().withKey(KEY_N))}, + {"open_project", h(Hotkey{}.withCtrl().withKey(KEY_O))}, + {"redo", h(Hotkey{}.withCtrl().withKey(KEY_Y))}, + {"save_file", h(Hotkey{}.withCtrl().withKey(KEY_S))}, + {"toggle_debug", h(Hotkey{}.withKey(KEY_F3))}, + {"undo", h(Hotkey{}.withCtrl().withKey(KEY_Z))}, + {"room_tool.mouse", h(Hotkey{}.withKey(KEY_ONE))}, + {"room_tool.pen", h(Hotkey{}.withKey(KEY_TWO))}, + {"room_tool.eraser", h(Hotkey{}.withKey(KEY_THREE))}, + {"room_tool.edit", h(Hotkey{}.withKey(KEY_FOUR))}, + {"room_tool.set_spoint",h(Hotkey{}.withKey(KEY_FIVE))}, + {"room_tool.toggle_bm", h(Hotkey{}.withKey(KEY_SIX))}}}, }; diff --git a/include/editor/services/hotkeyService.hpp b/include/editor/services/hotkeyService.hpp index c901d6e..ccbe657 100644 --- a/include/editor/services/hotkeyService.hpp +++ b/include/editor/services/hotkeyService.hpp @@ -10,11 +10,17 @@ class Editor; struct Hotkey { - bool ctrl; - bool shift; - bool alt; - bool super; - KeyboardKey key; + bool ctrl = false; + bool shift = false; + bool alt = false; + bool super = false; + KeyboardKey key = KEY_NULL; + + Hotkey& withCtrl(bool is = true) { ctrl = is; return *this; } + Hotkey& withShift(bool is = true) { shift = is; return *this; } + Hotkey& withAlt(bool is = true) { alt = is; return *this; } + Hotkey& withSuper(bool is = true) { super = is; return *this; } + Hotkey& withKey(KeyboardKey k) { key = k; return *this; } }; using HotkeyMap = std::unordered_map; class HotkeyService { diff --git a/src/editor/services/hotkeyService.cpp b/src/editor/services/hotkeyService.cpp index 898ff4c..d741682 100644 --- a/src/editor/services/hotkeyService.cpp +++ b/src/editor/services/hotkeyService.cpp @@ -36,12 +36,12 @@ const int HotkeyService::pack(Hotkey hk) { } const Hotkey HotkeyService::unpack(int packed) { - return { - .ctrl = (bool)((packed >> 0) & 1), - .shift = (bool)((packed >> 1) & 1), - .alt = (bool)((packed >> 2) & 1), - .super = (bool)((packed >> 3) & 1), - .key = static_cast(packed >> 4), + return Hotkey{ + (bool)((packed >> 0) & 1), + (bool)((packed >> 1) & 1), + (bool)((packed >> 2) & 1), + (bool)((packed >> 3) & 1), + static_cast(packed >> 4) }; } diff --git a/src/editor/widgets/hotkeyModifier.cpp b/src/editor/widgets/hotkeyModifier.cpp index 8883761..50def57 100644 --- a/src/editor/widgets/hotkeyModifier.cpp +++ b/src/editor/widgets/hotkeyModifier.cpp @@ -1,6 +1,7 @@ #include "TGUI/SubwidgetContainer.hpp" #include "TGUI/Widgets/Button.hpp" #include "bindTranslation.hpp" +#include "editor.hpp" #include "raylib.h" #include "services/translationService.hpp" #include @@ -465,13 +466,7 @@ void HotkeyModifier::setKey(const std::string &id, KeyboardKey key, } this->button->setText(label); - hk = { - .ctrl = isCtrl, - .shift = isShift, - .alt = isAlt, - .super = isSuper, - .key = key, - }; + hk = Hotkey{isCtrl, isShift, isAlt, isSuper, key}; this->id = id; } From 773f70a2f468deed5df96f5b92927cb3e12b1192 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:00:50 +0700 Subject: [PATCH 23/35] Fix problems on MacOS --- include/stateService.hpp | 2 +- src/editor/widgets/hotkeyModifier.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/stateService.hpp b/include/stateService.hpp index 031a4a2..434fa1b 100644 --- a/include/stateService.hpp +++ b/include/stateService.hpp @@ -10,7 +10,7 @@ /** The StateService is responsible for storing gameplay-related variables * that make up the state of the game. */ -using Value = std::variant; +using Value = std::variant; class StateService { private: /** A pair of string keys and boolean values. */ diff --git a/src/editor/widgets/hotkeyModifier.cpp b/src/editor/widgets/hotkeyModifier.cpp index 50def57..4948def 100644 --- a/src/editor/widgets/hotkeyModifier.cpp +++ b/src/editor/widgets/hotkeyModifier.cpp @@ -406,6 +406,8 @@ const std::string keyboardKeyToName(KeyboardKey k) { return "Keypad -"; case KEY_KP_ADD: return "Keypad +"; + default: + return "Unknown"; } } From 70f655d3046e801dd64e6a669fcda390351856d5 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:23:56 +0700 Subject: [PATCH 24/35] Added bind for toggling brush mode --- src/editor/fileViews/roomFileView.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor/fileViews/roomFileView.cpp b/src/editor/fileViews/roomFileView.cpp index a1f2738..ae8279f 100644 --- a/src/editor/fileViews/roomFileView.cpp +++ b/src/editor/fileViews/roomFileView.cpp @@ -165,6 +165,7 @@ RoomFileView::RoomFileView() { TOOLBOX_H - toolbox->getRenderer()->getPadding().getTop(); brushToggle->setSize({brushToggleSize, brushToggleSize}); toolbox->addWidget(brushToggle); + hotkeyEntries.push_back(hks.registerHotkeyCallback("room_tool.toggle_bm", [this, brushToggle](){brushToggle->setChecked(!brushToggle->isChecked());})); toolbox->onItemClicked([this](ToolboxItem tool) { setRoomTool(tool); From cdb9d844ce4578f068b8ee24d75fe540ef2c40f0 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:24:26 +0700 Subject: [PATCH 25/35] Also check whether file view is in focus --- src/editor/fileViews/roomFileView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/fileViews/roomFileView.cpp b/src/editor/fileViews/roomFileView.cpp index ae8279f..6ca6e27 100644 --- a/src/editor/fileViews/roomFileView.cpp +++ b/src/editor/fileViews/roomFileView.cpp @@ -165,7 +165,7 @@ RoomFileView::RoomFileView() { TOOLBOX_H - toolbox->getRenderer()->getPadding().getTop(); brushToggle->setSize({brushToggleSize, brushToggleSize}); toolbox->addWidget(brushToggle); - hotkeyEntries.push_back(hks.registerHotkeyCallback("room_tool.toggle_bm", [this, brushToggle](){brushToggle->setChecked(!brushToggle->isChecked());})); + hotkeyEntries.push_back(hks.registerHotkeyCallback("room_tool.toggle_bm", [this, brushToggle](){ if (fileViewFocused) brushToggle->setChecked(!brushToggle->isChecked());})); toolbox->onItemClicked([this](ToolboxItem tool) { setRoomTool(tool); From 206d7f8f306abbfa5f156975f2e62273364eb828 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:25:07 +0700 Subject: [PATCH 26/35] Code linting --- include/editor/defaultConfig.hpp | 24 +++++++------- include/editor/fileViews/fileView.hpp | 2 +- include/editor/services/hotkeyService.hpp | 30 +++++++++++++----- include/editor/widgets/toolbox.hpp | 3 +- src/editor/fileViews/roomFileView.cpp | 38 +++++++++++++---------- src/editor/services/hotkeyService.cpp | 37 ++++++++++------------ 6 files changed, 76 insertions(+), 58 deletions(-) diff --git a/include/editor/defaultConfig.hpp b/include/editor/defaultConfig.hpp index 67f0d94..4911fd2 100644 --- a/include/editor/defaultConfig.hpp +++ b/include/editor/defaultConfig.hpp @@ -22,17 +22,17 @@ const Config BASE_CONFIG = { {"theme", "Dark"}, }}, {"hotkeys", - {{"close_tab", h(Hotkey{}.withCtrl().withKey(KEY_W))}, - {"new_project", h(Hotkey{}.withCtrl().withKey(KEY_N))}, - {"open_project", h(Hotkey{}.withCtrl().withKey(KEY_O))}, - {"redo", h(Hotkey{}.withCtrl().withKey(KEY_Y))}, - {"save_file", h(Hotkey{}.withCtrl().withKey(KEY_S))}, - {"toggle_debug", h(Hotkey{}.withKey(KEY_F3))}, - {"undo", h(Hotkey{}.withCtrl().withKey(KEY_Z))}, - {"room_tool.mouse", h(Hotkey{}.withKey(KEY_ONE))}, - {"room_tool.pen", h(Hotkey{}.withKey(KEY_TWO))}, - {"room_tool.eraser", h(Hotkey{}.withKey(KEY_THREE))}, - {"room_tool.edit", h(Hotkey{}.withKey(KEY_FOUR))}, - {"room_tool.set_spoint",h(Hotkey{}.withKey(KEY_FIVE))}, + {{"close_tab", h(Hotkey{}.withCtrl().withKey(KEY_W))}, + {"new_project", h(Hotkey{}.withCtrl().withKey(KEY_N))}, + {"open_project", h(Hotkey{}.withCtrl().withKey(KEY_O))}, + {"redo", h(Hotkey{}.withCtrl().withKey(KEY_Y))}, + {"save_file", h(Hotkey{}.withCtrl().withKey(KEY_S))}, + {"toggle_debug", h(Hotkey{}.withKey(KEY_F3))}, + {"undo", h(Hotkey{}.withCtrl().withKey(KEY_Z))}, + {"room_tool.mouse", h(Hotkey{}.withKey(KEY_ONE))}, + {"room_tool.pen", h(Hotkey{}.withKey(KEY_TWO))}, + {"room_tool.eraser", h(Hotkey{}.withKey(KEY_THREE))}, + {"room_tool.edit", h(Hotkey{}.withKey(KEY_FOUR))}, + {"room_tool.set_spoint", h(Hotkey{}.withKey(KEY_FIVE))}, {"room_tool.toggle_bm", h(Hotkey{}.withKey(KEY_SIX))}}}, }; diff --git a/include/editor/fileViews/fileView.hpp b/include/editor/fileViews/fileView.hpp index bd9d51a..6032707 100644 --- a/include/editor/fileViews/fileView.hpp +++ b/include/editor/fileViews/fileView.hpp @@ -20,7 +20,7 @@ class FileView { std::stack> future; public: - bool fileViewFocused = false; + bool fileViewFocused = false; FileView(); virtual ~FileView(); diff --git a/include/editor/services/hotkeyService.hpp b/include/editor/services/hotkeyService.hpp index ccbe657..10cc1fe 100644 --- a/include/editor/services/hotkeyService.hpp +++ b/include/editor/services/hotkeyService.hpp @@ -16,23 +16,39 @@ struct Hotkey { bool super = false; KeyboardKey key = KEY_NULL; - Hotkey& withCtrl(bool is = true) { ctrl = is; return *this; } - Hotkey& withShift(bool is = true) { shift = is; return *this; } - Hotkey& withAlt(bool is = true) { alt = is; return *this; } - Hotkey& withSuper(bool is = true) { super = is; return *this; } - Hotkey& withKey(KeyboardKey k) { key = k; return *this; } + Hotkey &withCtrl(bool is = true) { + ctrl = is; + return *this; + } + Hotkey &withShift(bool is = true) { + shift = is; + return *this; + } + Hotkey &withAlt(bool is = true) { + alt = is; + return *this; + } + Hotkey &withSuper(bool is = true) { + super = is; + return *this; + } + Hotkey &withKey(KeyboardKey k) { + key = k; + return *this; + } }; using HotkeyMap = std::unordered_map; class HotkeyService { private: - std::map>> hotkeysCb; + std::map>> + hotkeysCb; void write(const std::string &keyId, const std::string &keyStr); HotkeyMap hotkeyMap; public: HotkeyService(); std::string registerHotkeyCallback(const std::string &keyId, - std::function cb); + std::function cb); void unregisterHotkeyCallback(const std::string &uniqueHkCbId); void addHotkey(const std::string &keyId, const Hotkey &keys); void removeHotkey(const std::string &keyId); diff --git a/include/editor/widgets/toolbox.hpp b/include/editor/widgets/toolbox.hpp index 358e823..b53b83c 100644 --- a/include/editor/widgets/toolbox.hpp +++ b/include/editor/widgets/toolbox.hpp @@ -116,8 +116,7 @@ void Toolbox::resetToolSelection(std::string groupToReset) { } } -template -void Toolbox::selectTool(const ToolboxItem &item) { +template void Toolbox::selectTool(const ToolboxItem &item) { for (const auto &widgets : this->container->getWidgets()) { if (auto btn = std::dynamic_pointer_cast(widgets)) { ToolboxItemIdentifier identifier = diff --git a/src/editor/fileViews/roomFileView.cpp b/src/editor/fileViews/roomFileView.cpp index 6ca6e27..90fc68e 100644 --- a/src/editor/fileViews/roomFileView.cpp +++ b/src/editor/fileViews/roomFileView.cpp @@ -136,23 +136,26 @@ RoomFileView::RoomFileView() { toolbox->setSize({TextFormat("100%% - %d", RIGHT_PANEL_W), TOOLBOX_H}); std::vector>> tools = { - {"room_tool.mouse", ToolboxItem{"tool", RoomTool::TOOL_NONE, "Mouse", - "tool_none.png"}}, + {"room_tool.mouse", ToolboxItem{"tool", RoomTool::TOOL_NONE, + "Mouse", "tool_none.png"}}, {"room_tool.pen", ToolboxItem{"tool", RoomTool::TOOL_PLACE, - "Place", "tool_place.png"}}, + "Place", "tool_place.png"}}, {"room_tool.eraser", ToolboxItem{"tool", RoomTool::TOOL_ERASE, - "Erase", "tool_erase.png"}}, - {"room_tool.edit", ToolboxItem{"tool", RoomTool::TOOL_EDIT, "Edit", - "tool_edit.png"}}, - {"room_tool.set_spoint", ToolboxItem{"tool", RoomTool::TOOL_STARTPOINT, - "Start Point", - "tool_startpoint.png"}} - }; + "Erase", "tool_erase.png"}}, + {"room_tool.edit", ToolboxItem{"tool", RoomTool::TOOL_EDIT, + "Edit", "tool_edit.png"}}, + {"room_tool.set_spoint", + ToolboxItem{"tool", RoomTool::TOOL_STARTPOINT, "Start Point", + "tool_startpoint.png"}}}; for (auto &[k, tool] : tools) { auto capturedTool = tool; toolbox->addTool(tool); - hotkeyEntries.push_back(hks.registerHotkeyCallback(k, [this, capturedTool, toolbox](){ if (fileViewFocused) toolbox->selectTool(capturedTool); })); + hotkeyEntries.push_back( + hks.registerHotkeyCallback(k, [this, capturedTool, toolbox]() { + if (fileViewFocused) + toolbox->selectTool(capturedTool); + })); } auto brushToggle = tgui::CheckBox::create(); @@ -165,11 +168,14 @@ RoomFileView::RoomFileView() { TOOLBOX_H - toolbox->getRenderer()->getPadding().getTop(); brushToggle->setSize({brushToggleSize, brushToggleSize}); toolbox->addWidget(brushToggle); - hotkeyEntries.push_back(hks.registerHotkeyCallback("room_tool.toggle_bm", [this, brushToggle](){ if (fileViewFocused) brushToggle->setChecked(!brushToggle->isChecked());})); - - toolbox->onItemClicked([this](ToolboxItem tool) { - setRoomTool(tool); - }); + hotkeyEntries.push_back(hks.registerHotkeyCallback( + "room_tool.toggle_bm", [this, brushToggle]() { + if (fileViewFocused) + brushToggle->setChecked(!brushToggle->isChecked()); + })); + + toolbox->onItemClicked( + [this](ToolboxItem tool) { setRoomTool(tool); }); widgetContainer.push_back(toolbox); } diff --git a/src/editor/services/hotkeyService.cpp b/src/editor/services/hotkeyService.cpp index d741682..ff6c4a9 100644 --- a/src/editor/services/hotkeyService.cpp +++ b/src/editor/services/hotkeyService.cpp @@ -8,21 +8,22 @@ HotkeyService::HotkeyService() {} // It's not true UUID, but it will work in this case // https://stackoverflow.com/a/58467162 std::string get_uuid() { - static std::random_device dev; - static std::mt19937 rng(dev()); + static std::random_device dev; + static std::mt19937 rng(dev()); - std::uniform_int_distribution dist(0, 15); + std::uniform_int_distribution dist(0, 15); - const char *v = "0123456789abcdef"; - const bool dash[] = { 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0 }; + const char *v = "0123456789abcdef"; + const bool dash[] = {0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0}; - std::string res; - for (int i = 0; i < 16; i++) { - if (dash[i]) res += "-"; - res += v[dist(rng)]; - res += v[dist(rng)]; - } - return res; + std::string res; + for (int i = 0; i < 16; i++) { + if (dash[i]) + res += "-"; + res += v[dist(rng)]; + res += v[dist(rng)]; + } + return res; } const int HotkeyService::pack(Hotkey hk) { @@ -36,13 +37,9 @@ const int HotkeyService::pack(Hotkey hk) { } const Hotkey HotkeyService::unpack(int packed) { - return Hotkey{ - (bool)((packed >> 0) & 1), - (bool)((packed >> 1) & 1), - (bool)((packed >> 2) & 1), - (bool)((packed >> 3) & 1), - static_cast(packed >> 4) - }; + return Hotkey{(bool)((packed >> 0) & 1), (bool)((packed >> 1) & 1), + (bool)((packed >> 2) & 1), (bool)((packed >> 3) & 1), + static_cast(packed >> 4)}; } std::map HotkeyService::serialize() { @@ -77,7 +74,7 @@ void HotkeyService::deserialize( } std::string HotkeyService::registerHotkeyCallback(const std::string &keyId, - std::function cb) { + std::function cb) { std::string uniqueHkCbId = get_uuid(); hotkeysCb[uniqueHkCbId] = {keyId, cb}; return uniqueHkCbId; From c81672d0ff0f20300850ae019b46e682a34a76a9 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Mon, 23 Mar 2026 17:42:29 +0700 Subject: [PATCH 27/35] Recently opened project support! --- include/editor/editor.hpp | 3 + include/editor/project.hpp | 5 +- include/editor/services/fileSystemService.hpp | 1 + .../editor/services/recentProjectService.hpp | 22 +++++++ src/editor/editor.cpp | 4 ++ src/editor/project.cpp | 11 +++- src/editor/screens/welcomeScreen.cpp | 60 ++++++++++++++++- src/editor/services/fileSystemService.cpp | 8 +-- src/editor/services/recentProjectService.cpp | 65 +++++++++++++++++++ 9 files changed, 166 insertions(+), 13 deletions(-) create mode 100644 include/editor/services/recentProjectService.hpp create mode 100644 src/editor/services/recentProjectService.cpp diff --git a/include/editor/editor.hpp b/include/editor/editor.hpp index ca4b68b..a29d477 100644 --- a/include/editor/editor.hpp +++ b/include/editor/editor.hpp @@ -6,6 +6,7 @@ #include "services/editorGuiService.hpp" #include "services/fileSystemService.hpp" #include "services/hotkeyService.hpp" +#include "services/recentProjectService.hpp" #include "services/themeService.hpp" #include "services/translationService.hpp" #include @@ -31,6 +32,7 @@ class Editor { ThemeService themeService; EditorGuiService guiService; HotkeyService hotkeyService; + RecentProjectService recentProjectService; public: Editor(); @@ -45,6 +47,7 @@ class Editor { TranslationService &getTranslations(); ThemeService &getThemeService(); FileSystemService &getFs(); + RecentProjectService &getRecentProjectService(); ConfigurationService &getConfiguration(); HotkeyService &getHotkeyService(); Project *getProject() const; diff --git a/include/editor/project.hpp b/include/editor/project.hpp index 3b1445d..4e39212 100644 --- a/include/editor/project.hpp +++ b/include/editor/project.hpp @@ -18,7 +18,8 @@ class Project { public: Project(const std::string &path); - static void create(const std::string &dirPath, const std::string &title); + static std::string create(const std::string &dirPath, const std::string &title); + static void openProject(const tgui::String &filePath, bool forceSwitch = false); json toJson(); std::string &getTitle(); std::string &getBasePath(); @@ -32,4 +33,4 @@ class Project { void buildProject(); }; -#endif \ No newline at end of file +#endif diff --git a/include/editor/services/fileSystemService.hpp b/include/editor/services/fileSystemService.hpp index 87be816..458b762 100644 --- a/include/editor/services/fileSystemService.hpp +++ b/include/editor/services/fileSystemService.hpp @@ -1,6 +1,7 @@ #ifndef _RPGPP_FILESYSTEMSERVICE_H #define _RPGPP_FILESYSTEMSERVICE_H +#include "TGUI/String.hpp" #include "variant.hpp" #include #include diff --git a/include/editor/services/recentProjectService.hpp b/include/editor/services/recentProjectService.hpp new file mode 100644 index 0000000..4a9a209 --- /dev/null +++ b/include/editor/services/recentProjectService.hpp @@ -0,0 +1,22 @@ + +#ifndef RPGPP_RECENTPROJECTSERVICE_H +#define RPGPP_RECENTPROJECTSERVICE_H + +#include +#include +#include +#define RPGPP_RECENT_FILE ".rpgpp_recent_project" + +class RecentProjectService { + private: + int limit = 10; + std::deque recentProjects; + std::filesystem::path path; + void save(); + public: + RecentProjectService(); + void enqueue(const std::string &projectPath); + const std::deque &getRecentProjects() const; +}; + +#endif // RPGPP_RECENTPROJECTSERVICE_H diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index b3265e8..f981c3e 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -33,6 +33,10 @@ HotkeyService &Editor::getHotkeyService() { return hotkeyService; } Project *Editor::getProject() const { return project.get(); } +RecentProjectService &Editor::getRecentProjectService() { + return recentProjectService; +} + ConfigurationService &Editor::getConfiguration() { return configurationService; } diff --git a/src/editor/project.cpp b/src/editor/project.cpp index 23d7ebb..d53c8d9 100644 --- a/src/editor/project.cpp +++ b/src/editor/project.cpp @@ -61,7 +61,7 @@ Project::Project(const std::string &path) { UnloadFileText(jsonContent); } -void Project::create(const std::string &dirPath, const std::string &title) { +std::string Project::create(const std::string &dirPath, const std::string &title) { json j = json::object(); j["title"] = title; std::string fileContent = j.dump(); @@ -75,9 +75,14 @@ void Project::create(const std::string &dirPath, const std::string &title) { MakeDirectory( std::filesystem::path(dirPath).append("maps").u8string().c_str()); - Editor::instance->setProject(filePath.u8string()); + return filePath.u8string(); +} + +void Project::openProject(const tgui::String &filePath, bool forceSwitch) { + Editor::instance->setProject(filePath.toStdString()); + Editor::instance->getRecentProjectService().enqueue(filePath.toStdString()); Editor::instance->getGui().setScreen( - std::make_unique(), true); + std::make_unique(), forceSwitch); } json Project::toJson() { diff --git a/src/editor/screens/welcomeScreen.cpp b/src/editor/screens/welcomeScreen.cpp index bd23f76..ab50f29 100644 --- a/src/editor/screens/welcomeScreen.cpp +++ b/src/editor/screens/welcomeScreen.cpp @@ -1,8 +1,15 @@ #include "screens/welcomeScreen.hpp" +#include "TGUI/Layout.hpp" +#include "TGUI/String.hpp" +#include "TGUI/Widgets/BoxLayout.hpp" #include "TGUI/Widgets/Button.hpp" #include "TGUI/Widgets/ChildWindow.hpp" #include "TGUI/Widgets/GrowVerticalLayout.hpp" +#include "TGUI/Widgets/HorizontalLayout.hpp" #include "TGUI/Widgets/Label.hpp" +#include "TGUI/Widgets/ListBox.hpp" +#include "TGUI/Widgets/Picture.hpp" +#include "TGUI/Widgets/VerticalLayout.hpp" #include "bindTranslation.hpp" #include "editor.hpp" #include "project.hpp" @@ -47,13 +54,30 @@ void screens::WelcomeScreen::initItems(tgui::Group::Ptr layout) { &tgui::Label::setText); introLabel->setTextSize(16); introLabel->setHorizontalAlignment(tgui::HorizontalAlignment::Center); - introLabel->setSize({0, 81}); verticalLayout->add(introLabel); + const auto actionsContainer = tgui::BoxLayout::create(); + actionsContainer->setHeight(240); + verticalLayout->add(actionsContainer); + + const auto left = tgui::GrowVerticalLayout::create(); + left->setAutoLayout(tgui::AutoLayout::Left); + left->setWidth(180); + actionsContainer->add(left); + + const auto actionsLabel = tgui::Label::create(""); + actionsLabel->setText("Actions"); + actionsLabel->setTextSize(24); + left->add(actionsLabel); + const auto newProjButton = tgui::Button::create(); bindTranslation(newProjButton, "menu.file.new_project", &tgui::Button::setText); newProjButton->setTextSize(ACTION_BUTTON_SIZE); + + const auto buttonPadding = tgui::BoxLayout::create(); + buttonPadding->setHeight(10); + const auto openProjButton = tgui::Button::create(); bindTranslation(openProjButton, "menu.file.open_project", &tgui::Button::setText); @@ -65,8 +89,38 @@ void screens::WelcomeScreen::initItems(tgui::Group::Ptr layout) { openProjButton->onPress( [] { Editor::instance->getFs().promptOpenProject(); }); - verticalLayout->add(newProjButton); - verticalLayout->add(openProjButton); + left->add(newProjButton); + left->add(buttonPadding); + left->add(openProjButton); + + const auto padding = tgui::BoxLayout::create(); + padding->setWidth(10); + padding->setAutoLayout(tgui::AutoLayout::Left); + actionsContainer->add(padding); + + const auto right = tgui::BoxLayout::create(); + right->setAutoLayout(tgui::AutoLayout::Fill); + + const auto recentProjectLabel = tgui::Label::create(""); + recentProjectLabel->setText("Recent Projects"); + recentProjectLabel->setTextSize(24); + recentProjectLabel->setAutoLayout(tgui::AutoLayout::Top); + right->add(recentProjectLabel); + + const auto recentProject = tgui::ListBox::create(); + recentProject->setAutoLayout(tgui::AutoLayout::Fill); + + for (auto i : Editor::instance->getRecentProjectService().getRecentProjects()) { + recentProject->addItem(i); + } + + recentProject->onItemSelect([this](const tgui::String& path) { + Project::openProject(path); + }); + + right->add(recentProject); + + actionsContainer->add(right); layout->add(verticalLayout); } diff --git a/src/editor/services/fileSystemService.cpp b/src/editor/services/fileSystemService.cpp index 0b3288a..3513993 100644 --- a/src/editor/services/fileSystemService.cpp +++ b/src/editor/services/fileSystemService.cpp @@ -17,7 +17,6 @@ #include "editor.hpp" #include "raylib.h" -#include "screens/projectScreen.hpp" FileSystemService::FileSystemService() { editorBaseDir = GetWorkingDirectory(); @@ -52,7 +51,8 @@ void FileSystemService::promptNewProject() { std::string dirPath = newProjectDialog->fileField->getChosenPath().toStdString(); if (!title.empty() && !dirPath.empty()) { - Project::create(dirPath, title); + auto path = Project::create(dirPath, title); + Project::openProject(path, true); } newProjectDialog->window->close(); }); @@ -63,9 +63,7 @@ void FileSystemService::promptOpenProject() { files->setFileTypeFilters({{"RPG++ Project", {"*.rpgpp"}}}); files->onFileSelect([](const tgui::String &filePath) { - Editor::instance->setProject(filePath.toStdString()); - Editor::instance->getGui().setScreen( - std::make_unique(), false); + Project::openProject(filePath); }); Editor::instance->getGui().gui->add(files); diff --git a/src/editor/services/recentProjectService.cpp b/src/editor/services/recentProjectService.cpp new file mode 100644 index 0000000..d9be805 --- /dev/null +++ b/src/editor/services/recentProjectService.cpp @@ -0,0 +1,65 @@ +#include "services/recentProjectService.hpp" +#include "raylib.h" +#include +#include +#include +#include + +RecentProjectService::RecentProjectService() { + path = GetWorkingDirectory(); + path /= RPGPP_RECENT_FILE; + + if (!std::filesystem::exists(path)) { + std::ofstream file(path); + file.close(); + } + + std::ifstream file(path); + if (!file.is_open()) { + std::cerr << "Failed to open recent project file" << std::endl; + return; + } + + std::string s; + while (std::getline(file, s)) { + if (!std::filesystem::exists(s)) { + continue; + } + recentProjects.push_back(s); + } + file.close(); + save(); +} + +void RecentProjectService::save() { + + std::ofstream file(path); + if (!file.is_open()) { + std::cerr << "Failed to open recent project file for saving" << std::endl; + return; + } + + for (auto i = recentProjects.begin(); i != recentProjects.end(); ++i) { + file << *i << std::endl; + } + + file.close(); +} + +void RecentProjectService::enqueue(const std::string &projectPath) { + for (auto i = recentProjects.begin(); i != recentProjects.end(); ++i) { + if (*i == projectPath) { + recentProjects.erase(i); + break; + } + } + recentProjects.push_front(projectPath); + if (recentProjects.size() > limit) { + recentProjects.pop_back(); + } + save(); +} + +const std::deque &RecentProjectService::getRecentProjects() const { + return recentProjects; +} From 391f7fee5aa10dd55ce16edd96194e017ec0d771 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Mon, 23 Mar 2026 17:51:20 +0700 Subject: [PATCH 28/35] Bind translation, added translation --- resources/translations/en_us.json | 4 +++- resources/translations/vn.json | 4 +++- src/editor/screens/welcomeScreen.cpp | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/resources/translations/en_us.json b/resources/translations/en_us.json index a8cb1f5..cfd6ce5 100644 --- a/resources/translations/en_us.json +++ b/resources/translations/en_us.json @@ -25,7 +25,9 @@ "screen": { "starting": { "get_started": "Get Started!", - "description": "Press on 'New Project' to create a new project, or 'Open Project' to open one!" + "description": "Press on 'New Project' to create a new project, or 'Open Project' to open one!", + "actions": "Actions", + "recent_projects": "Recent Projects" }, "options": { "language": "Language", diff --git a/resources/translations/vn.json b/resources/translations/vn.json index 8f1b5a2..2027019 100644 --- a/resources/translations/vn.json +++ b/resources/translations/vn.json @@ -27,7 +27,9 @@ "screen": { "starting": { "get_started": "Bắt đầu!", - "description": "Nhấn vào 'Tạo dự án' để tạo dự án mới, hoặc 'Mở dự án' để mở một dự án!" + "description": "Nhấn vào 'Tạo dự án' để tạo dự án mới, hoặc 'Mở dự án' để mở một dự án!", + "actions": "Hành động", + "recent_projects": "Những dự án gần đây" }, "options": { diff --git a/src/editor/screens/welcomeScreen.cpp b/src/editor/screens/welcomeScreen.cpp index ab50f29..14d67e1 100644 --- a/src/editor/screens/welcomeScreen.cpp +++ b/src/editor/screens/welcomeScreen.cpp @@ -66,9 +66,10 @@ void screens::WelcomeScreen::initItems(tgui::Group::Ptr layout) { actionsContainer->add(left); const auto actionsLabel = tgui::Label::create(""); - actionsLabel->setText("Actions"); actionsLabel->setTextSize(24); left->add(actionsLabel); + bindTranslation(actionsLabel, "screen.starting.actions", + &tgui::Label::setText); const auto newProjButton = tgui::Button::create(); bindTranslation(newProjButton, "menu.file.new_project", @@ -102,7 +103,8 @@ void screens::WelcomeScreen::initItems(tgui::Group::Ptr layout) { right->setAutoLayout(tgui::AutoLayout::Fill); const auto recentProjectLabel = tgui::Label::create(""); - recentProjectLabel->setText("Recent Projects"); + bindTranslation(recentProjectLabel, "screen.starting.recent_projects", + &tgui::Label::setText); recentProjectLabel->setTextSize(24); recentProjectLabel->setAutoLayout(tgui::AutoLayout::Top); right->add(recentProjectLabel); From 0b9f62a6f59320c0aab24fda8bd33c14fe97e395 Mon Sep 17 00:00:00 2001 From: CDevv Date: Mon, 23 Mar 2026 18:33:51 +0200 Subject: [PATCH 29/35] Add bindings for Containers, TileMap, Prop, Interactable Signed-off-by: CDevv --- include/actorContainer.hpp | 20 +++++- include/baseContainer.hpp | 9 +++ include/collisionsContainer.hpp | 3 + include/gamedata.hpp | 10 +++ include/interactablesContainer.hpp | 4 ++ include/lua/worldApi.hpp | 2 + include/propsContainer.hpp | 2 + include/room.hpp | 4 +- include/worldService.hpp | 4 +- resources/scripts/dialogue.lua | 22 ++++++- src/actorContainer.cpp | 44 +++++++++++++ src/collisionsContainer.cpp | 3 + src/editor/actions/eraseTileAction.cpp | 4 +- src/editor/actions/placeTileAction.cpp | 6 +- src/editor/project.cpp | 13 ++-- src/editor/views/roomView.cpp | 8 ++- src/interactable.cpp | 1 + src/interactablesContainer.cpp | 26 ++++++++ src/lua/apiTypes.cpp | 90 +++++++++++++++++++++++++- src/lua/worldApi.cpp | 12 ++++ src/prop.cpp | 8 ++- src/propsContainer.cpp | 23 +++++++ src/room.cpp | 22 +++---- src/worldService.cpp | 17 +++-- 24 files changed, 320 insertions(+), 37 deletions(-) create mode 100644 src/actorContainer.cpp create mode 100644 src/propsContainer.cpp diff --git a/include/actorContainer.hpp b/include/actorContainer.hpp index 2a901c7..e06bab2 100644 --- a/include/actorContainer.hpp +++ b/include/actorContainer.hpp @@ -2,11 +2,25 @@ #define _RPGPP_ACTORCONTAINER_H #include "actor.hpp" -#include "baseContainer.hpp" -class ActorContainer : public BaseContainer { + +class ActorContainer { + private: + std::map> actors; + public: /** Construct the actor container. */ - ActorContainer() = default; + ActorContainer(); + /** Get the map itself */ + std::map> &getActors(); + /** Get an Actor with the specified name */ + Actor &getActor(const std::string &name); + /** Add a new Actor with a name from the GameBin and an in-room name*/ + void addActor(Vector2 pos, const std::string &typeName, + const std::string &roomName); + /** Remove an Actor */ + void removeActor(const std::string &roomName); + /** Check whether an Actor with such an in-room name exists. */ + bool actorExists(const std::string &roomName); }; #endif // !_RPGPP_ACTORCONTAINER_H diff --git a/include/baseContainer.hpp b/include/baseContainer.hpp index 0592dfa..9d1345a 100644 --- a/include/baseContainer.hpp +++ b/include/baseContainer.hpp @@ -1,7 +1,9 @@ #ifndef _RPGPP_BASECONTAINER_H #define _RPGPP_BASECONTAINER_H +#include "conversion.hpp" #include "gamedata.hpp" +#include "raylib.h" #include #include @@ -18,6 +20,9 @@ template class BaseContainer { void pushObject(IVector pos, T obj) { this->objects.try_emplace(pos, std::move(obj)); } + void pushObjectVec2(Vector2 pos, T obj) { + pushObject(fromVector2(pos), obj); + } /** Remove an object existing at a position. */ void removeObject(IVector pos) { auto key = this->objects.find(pos); @@ -25,10 +30,14 @@ template class BaseContainer { this->objects.erase(key); } } + void removeObjectVec2(Vector2 pos) { removeObject(fromVector2(pos)); } /** Check if an object exists at specified position. */ bool objectExistsAtPosition(IVector pos) { return this->objects.find(pos) != this->objects.end(); } + bool objectExistsAtPositionVec2(Vector2 pos) { + return objectExistsAtPosition(fromVector2(pos)); + } /** Gets the object at a specified IVector Position. */ T &getObjectAtPosition(IVector pos) { if (this->objects.find(pos) != this->objects.end()) diff --git a/include/collisionsContainer.hpp b/include/collisionsContainer.hpp index 8618de2..ba701e3 100644 --- a/include/collisionsContainer.hpp +++ b/include/collisionsContainer.hpp @@ -2,6 +2,7 @@ #define _RPGPP_COLLISIONSCONTAINER_H #include "baseContainer.hpp" +#include "gamedata.hpp" #include /** A container of collision tiles to be used by a Room */ @@ -9,6 +10,8 @@ class CollisionsContainer : public BaseContainer { public: /** Empty constructor */ CollisionsContainer(); + /** Add a collision */ + void pushCollision(IVector pos); }; #endif \ No newline at end of file diff --git a/include/gamedata.hpp b/include/gamedata.hpp index 7b38cb5..d450558 100644 --- a/include/gamedata.hpp +++ b/include/gamedata.hpp @@ -23,6 +23,16 @@ struct IVector { bool operator<(const IVector &other) const { return x < other.x || (x == other.x && y < other.y); } + + IVector() = default; + IVector(int x, int y) { + this->x = x; + this->y = y; + } + IVector(const Vector2 &from) { + x = static_cast(from.x); + y = static_cast(from.y); + } }; struct IRect { diff --git a/include/interactablesContainer.hpp b/include/interactablesContainer.hpp index 7afcee1..03b2b50 100644 --- a/include/interactablesContainer.hpp +++ b/include/interactablesContainer.hpp @@ -20,8 +20,12 @@ class InteractablesContainer Interactable *add(IVector pos, const std::string &type); /** Add a new Interactable using a bin structure */ void addBin(InteractableInRoomBin bin); + /** Add a new Interactable using a Vector2 tile position and an interactable + * type in the GameBin */ + void addBinFromTypename(Vector2 pos, const std::string &type); /** Get an Interactable by its tile position */ Interactable *getInt(IVector pos); + Interactable *getIntVec2(Vector2 pos); /** Change the Interactable's type at the specified tile position */ void setInteractableType(IVector pos, const std::string &type); /** Get the vector that contains all Interactables */ diff --git a/include/lua/worldApi.hpp b/include/lua/worldApi.hpp index 2c8b5b1..7791793 100644 --- a/include/lua/worldApi.hpp +++ b/include/lua/worldApi.hpp @@ -5,6 +5,8 @@ #include "sol/state_view.hpp" sol::object lua_world_getroom(sol::this_state lua); +void lua_world_setroom(const std::string &room); +sol::object lua_world_getplayer(sol::this_state lua); void lua_world_set(sol::state_view lua); #endif \ No newline at end of file diff --git a/include/propsContainer.hpp b/include/propsContainer.hpp index 4181ae6..476a43c 100644 --- a/include/propsContainer.hpp +++ b/include/propsContainer.hpp @@ -8,6 +8,8 @@ class PropsContainer : public BaseContainer> { public: PropsContainer() = default; + void addProp(Vector2 pos, const std::string &type); + Prop *getPropAt(Vector2 pos); }; #endif \ No newline at end of file diff --git a/include/room.hpp b/include/room.hpp index 733bb96..e68621e 100644 --- a/include/room.hpp +++ b/include/room.hpp @@ -46,7 +46,7 @@ class Room : public ISaveable { /** This Room's TileMap, which contains all placed tiles. */ std::unique_ptr tileMap; /** A collection of all Actors in this Room */ - std::map> actors; + std::unique_ptr actors; /** This Room's only Player. */ std::unique_ptr player; void updateCamera(); @@ -107,7 +107,7 @@ class Room : public ISaveable { /** Get a reference to the PropsContainer of this Room. */ PropsContainer &getProps() const; /** Get a refernece to the collection of Actors. */ - std::map> &getActors(); + ActorContainer &getActors(); }; #endif diff --git a/include/worldService.hpp b/include/worldService.hpp index efa4195..3bb2e43 100644 --- a/include/worldService.hpp +++ b/include/worldService.hpp @@ -33,7 +33,9 @@ class WorldService { void setRoom(const std::string_view &filePath); /** Set the current room using a RoomBin binary structure. */ void setRoomBin(RoomBin bin); - /** Activate transition effect (warper) */ + /** Set the current room using a room title in the GameBin. */ + void setRoomBin(const std::string &roomBin); + /** Activate transition effect */ void doFadeTransition(); /** Get a reference to the Player object. */ Player &getPlayer() const; diff --git a/resources/scripts/dialogue.lua b/resources/scripts/dialogue.lua index 2fa7597..b1292c1 100644 --- a/resources/scripts/dialogue.lua +++ b/resources/scripts/dialogue.lua @@ -2,7 +2,7 @@ function interact() Interface.OpenDialogue(props.dialogue) room = World.GetRoom() - player = room:GetPlayer() + player = World.GetPlayer() pos = player:GetPosition() actor = player:GetActor() @@ -12,6 +12,26 @@ function interact() print(actorpos.x) print(actorpos.y) + col = room:GetCollisions() + print(col:Exists(Vector2.new(0, 3))) + + inters = room:GetInteractables() + print(inters:Exists(Vector2.new(12, 0))) + + thispos = this:GetPosition() + + print(this:GetPosition().x) + print(this:GetPosition().y) + inters:Remove(Vector2.new(12, 0)) + + inters:Push(Vector2.new(14, 0), "dialogue") + newInter = inters:GetAt(Vector2.new(14, 0)) + newInter:SetProp("dialogue", "mydiag") + + room:GetActors():Push(Vector2.new(2, 2), "krisactor", "sudo") + + room:GetTileMap():SetTile(thispos, Vector2.new(1, 1)) + GameState.Set("test_nil", nil); GameState.Set("test_bool", true); GameState.Set("test_int1", 50); diff --git a/src/actorContainer.cpp b/src/actorContainer.cpp new file mode 100644 index 0000000..5048dcc --- /dev/null +++ b/src/actorContainer.cpp @@ -0,0 +1,44 @@ +#include "actorContainer.hpp" +#include "actor.hpp" +#include "game.hpp" +#include "raylib.h" +#include +#include +#include + +ActorContainer::ActorContainer() { + actors = std::map>{}; +} + +std::map> &ActorContainer::getActors() { + return actors; +} + +Actor &ActorContainer::getActor(const std::string &name) { + return *actors[name]; +} + +void ActorContainer::addActor(Vector2 pos, const std::string &typeName, + const std::string &roomName) { + if (Game::getBin().actors.count(typeName) > 0) { + auto newActor = + std::make_unique(Game::getBin().actors[typeName]); + newActor->setTilePosition(pos, Game::getWorld() + .getRoom() + .getTileMap() + ->getTileSet() + ->getTileSize()); + actors[roomName] = std::move(newActor); + } else { + throw std::runtime_error( + TextFormat("This Actor does not exist: %s", typeName.c_str())); + } +} + +void ActorContainer::removeActor(const std::string &roomName) { + actors.erase(roomName); +} + +bool ActorContainer::actorExists(const std::string &roomName) { + return (actors.count(roomName) > 0); +} \ No newline at end of file diff --git a/src/collisionsContainer.cpp b/src/collisionsContainer.cpp index 4d140d1..960570c 100644 --- a/src/collisionsContainer.cpp +++ b/src/collisionsContainer.cpp @@ -1,4 +1,7 @@ #include "collisionsContainer.hpp" +#include "gamedata.hpp" #include CollisionsContainer::CollisionsContainer() = default; + +void CollisionsContainer::pushCollision(IVector pos) { pushObject(pos, true); } diff --git a/src/editor/actions/eraseTileAction.cpp b/src/editor/actions/eraseTileAction.cpp index a589f3d..c4c4812 100644 --- a/src/editor/actions/eraseTileAction.cpp +++ b/src/editor/actions/eraseTileAction.cpp @@ -23,9 +23,9 @@ void EraseTileAction::execute() { data.room->getProps().removeObject(fromVector2(data.worldTile)); } break; case RoomLayer::LAYER_ACTORS: { - for (auto &&a : data.room->getActors()) { + for (auto &&a : data.room->getActors().getActors()) { if (Vector2Equals(a.second->getTilePosition(), data.worldTile)) { - data.room->getActors().erase(a.first); + data.room->getActors().getActors().erase(a.first); break; } } diff --git a/src/editor/actions/placeTileAction.cpp b/src/editor/actions/placeTileAction.cpp index 857d22d..36535dd 100644 --- a/src/editor/actions/placeTileAction.cpp +++ b/src/editor/actions/placeTileAction.cpp @@ -70,7 +70,7 @@ void PlaceTileAction::execute() { a->setTilePosition( data.worldTile, data.room->getTileMap()->getTileSet()->getTileSize()); - data.room->getActors()[data.actorName] = std::move(a); + data.room->getActors().getActors()[data.actorName] = std::move(a); } break; default: break; @@ -92,9 +92,9 @@ void PlaceTileAction::undo() { data.room->getProps().removeObject(fromVector2(data.worldTile)); } break; case RoomLayer::LAYER_ACTORS: { - for (auto &&a : data.room->getActors()) { + for (auto &&a : data.room->getActors().getActors()) { if (Vector2Equals(a.second->getTilePosition(), data.worldTile)) { - data.room->getActors().erase(a.first); + data.room->getActors().getActors().erase(a.first); break; } } diff --git a/src/editor/project.cpp b/src/editor/project.cpp index d53c8d9..fa24081 100644 --- a/src/editor/project.cpp +++ b/src/editor/project.cpp @@ -61,7 +61,8 @@ Project::Project(const std::string &path) { UnloadFileText(jsonContent); } -std::string Project::create(const std::string &dirPath, const std::string &title) { +std::string Project::create(const std::string &dirPath, + const std::string &title) { json j = json::object(); j["title"] = title; std::string fileContent = j.dump(); @@ -191,7 +192,7 @@ GameData Project::generateStruct() { std::unique_ptr map = std::make_unique(roomPath); RoomBin roomBin; - roomBin.name = GetFileName(roomPath.c_str()); + roomBin.name = GetFileNameWithoutExt(roomPath.c_str()); roomBin.tileSetName = GetFileName(map->getTileSetSource().c_str()); Vector2 worldSize = map->getMaxWorldSize(); roomBin.width = static_cast(worldSize.x); @@ -255,7 +256,7 @@ GameData Project::generateStruct() { nlohmann::json::to_cbor(prop->getInteractable()->getProps()); roomBin.props.push_back(pBin); } - for (auto &[aName, actor] : room->getActors()) { + for (auto &[aName, actor] : room->getActors().getActors()) { ActorInRoomBin aBin; aBin.name = aName; aBin.source = actor->getSourcePath(); @@ -379,7 +380,11 @@ GameData Project::generateStruct() { static_cast(prop.getCollisionRect().height)}; bin.imagePath = std::string(prop.getImagePath()); bin.hasInteractable = prop.getHasInteractable(); - bin.intType = prop.getInteractable()->getType(); + if (prop.getInteractable() == nullptr) { + bin.intType = ""; + } else { + bin.intType = prop.getInteractable()->getType(); + } data.props.push_back(bin); } diff --git a/src/editor/views/roomView.cpp b/src/editor/views/roomView.cpp index 7ed1edd..b733213 100644 --- a/src/editor/views/roomView.cpp +++ b/src/editor/views/roomView.cpp @@ -22,6 +22,7 @@ #include "tileset.hpp" #include "views/worldView.hpp" #include +#include #include #include @@ -186,8 +187,13 @@ void RoomView::drawCanvas() { } // actors - for (auto &[name, actor] : room->getActors()) { + for (auto &[name, actor] : room->getActors().getActors()) { actor->draw(); + auto actorPos = actor->getPosition(); + auto tileWidth = actor->getTileSet().getTileWidth(); + auto textWidth = MeasureText(name.c_str(), 16); + int diff = (abs(tileWidth - textWidth) / 2); + DrawText(name.c_str(), actorPos.x + diff, actorPos.y, 16, MAROON); } // handle hovering diff --git a/src/interactable.cpp b/src/interactable.cpp index a260c09..f2b6141 100644 --- a/src/interactable.cpp +++ b/src/interactable.cpp @@ -112,6 +112,7 @@ void Interactable::interact() { auto &state = Game::getScripts().getState(); Game::getScripts().addToState(*props); + state["this"] = this; auto intBin = Game::getBin().interactables.at(type); if (Game::getBin().scripts.count(intBin.scriptPath) != 0) { diff --git a/src/interactablesContainer.cpp b/src/interactablesContainer.cpp index 1a20ca7..5f9e575 100644 --- a/src/interactablesContainer.cpp +++ b/src/interactablesContainer.cpp @@ -1,5 +1,6 @@ #include "interactablesContainer.hpp" #include "conversion.hpp" +#include "game.hpp" #include "gamedata.hpp" #include "interactable.hpp" #include "tilemap.hpp" @@ -7,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -36,12 +39,35 @@ void InteractablesContainer::addBin(InteractableInRoomBin bin) { this->pushObject(pos, std::move(std::make_unique(bin))); } +void InteractablesContainer::addBinFromTypename(Vector2 pos, + const std::string &type) { + if (Game::getBin().interactables.count(type) > 0) { + auto interactable = Game::getBin().interactables[type]; + InteractableInRoomBin intBin; + intBin.x = static_cast(pos.x); + intBin.y = static_cast(pos.y); + intBin.type = type; + intBin.onTouch = false; + + intBin.propsCbor = interactable.props; + + addBin(intBin); + } else { + throw std::runtime_error(TextFormat( + "This Interactable type does not exist: %s", type.c_str())); + } +} + Interactable *InteractablesContainer::getInt(IVector pos) { if (!this->objectExistsAtPosition(pos)) return nullptr; return this->getObjectAtPosition(pos).get(); } +Interactable *InteractablesContainer::getIntVec2(Vector2 pos) { + return getInt(fromVector2(pos)); +} + void InteractablesContainer::setInteractableType(IVector pos, const std::string &type) { auto &obj = this->getObjectAtPosition(pos); diff --git a/src/lua/apiTypes.cpp b/src/lua/apiTypes.cpp index 049c4dc..e5bd12a 100644 --- a/src/lua/apiTypes.cpp +++ b/src/lua/apiTypes.cpp @@ -1,9 +1,52 @@ #include "lua/apiTypes.hpp" #include "actor.hpp" +#include "actorContainer.hpp" +#include "collisionsContainer.hpp" +#include "interactable.hpp" +#include "interactablesContainer.hpp" #include "player.hpp" +#include "prop.hpp" +#include "propsContainer.hpp" #include "raylib.h" +#include "sol/forward.hpp" #include "sol/raii.hpp" #include "sol/types.hpp" +#include "tilemap.hpp" +#include +#include + +void lua_interactable_setprop(Interactable *inter, sol::object key, + sol::object value) { + if (inter == nullptr) + return; + + auto &props = inter->getProps(); + if (!key.is()) { + return; + } + + printf("%s \n", props.dump().c_str()); + + const std::string keyStr = key.as(); + + nlohmann::json &target = props[keyStr]; + + if (props.count(keyStr) > 0) { + if (props[keyStr].is_object()) { + target = props[keyStr]["value"]; + } + } + + if (value.is()) { + target = value.as(); + } else if (value.is()) { + target = value.as(); + } else if (value.is()) { + target = value.as(); + } else if (value.is()) { + target = value.as(); + } +} void lua_types_set(sol::state_view lua) { lua.new_usertype( @@ -19,8 +62,51 @@ void lua_types_set(sol::state_view lua) { "RIGHT_IDLE", Direction::RPGPP_RIGHT_IDLE, "RIGHT", Direction::RPGPP_RIGHT); - lua.new_usertype(sol::no_construction(), "GetPlayer", - &Room::getPlayer); + lua.new_usertype( + sol::no_construction(), "Push", &CollisionsContainer::pushObjectVec2, + "Remove", &CollisionsContainer::removeObjectVec2, "Exists", + &CollisionsContainer::objectExistsAtPositionVec2); + + lua.new_usertype( + sol::no_construction(), "Push", + &InteractablesContainer::addBinFromTypename, "Remove", + &InteractablesContainer::removeObjectVec2, "Exists", + &InteractablesContainer::objectExistsAtPositionVec2, "GetAt", + &InteractablesContainer::getIntVec2); + + lua.new_usertype( + sol::no_construction(), "SetProp", &lua_interactable_setprop, + "IsOnTouch", &Interactable::isOnTouch, "SetOnTouch", + &Interactable::setOnTouch, "GetPosition", &Interactable::getWorldPos, + "GetType", &Interactable::getType, "SetType", &Interactable::setType); + + lua.new_usertype( + sol::no_construction(), "Push", &PropsContainer::addProp, "Remove", + &PropsContainer::removeObjectVec2, "Exists", + &PropsContainer::objectExistsAtPositionVec2, "GetAt", + &PropsContainer::getPropAt); + + lua.new_usertype( + sol::no_construction(), "GetPosition", &Prop::getWorldPos, + "SetPosition", &Prop::setWorldPos, "GetTilePosition", + &Prop::getWorldTilePos, "SetTilePosition", &Prop::setWorldTilePos, + "GetInteractable", &Prop::getInteractable); + + lua.new_usertype( + sol::no_construction(), "Push", &ActorContainer::addActor, "Remove", + &ActorContainer::removeActor, "Exists", &ActorContainer::actorExists, + "Get", &ActorContainer::getActor); + + lua.new_usertype( + sol::no_construction(), "GetPlayer", &Room::getPlayer, "GetStartTile", + &Room::getStartTile, "GetTileMap", &Room::getTileMap, "GetCollisions", + &Room::getCollisions, "GetInteractables", &Room::getInteractables, + "GetProps", &Room::getProps, "GetActors", &Room::getActors); + + lua.new_usertype(sol::no_construction(), "SetTile", + &TileMap::setTile, "SetEmptyTile", + &TileMap::setEmptyTile, "GetWorldSizeInTiles", + &TileMap::getMaxWorldSize); lua.new_usertype( sol::no_construction(), "GetPosition", &Actor::getPosition, diff --git a/src/lua/worldApi.cpp b/src/lua/worldApi.cpp index 2a3ea73..809bade 100644 --- a/src/lua/worldApi.cpp +++ b/src/lua/worldApi.cpp @@ -1,5 +1,7 @@ #include "lua/worldApi.hpp" #include "game.hpp" +#include "player.hpp" +#include "sol/forward.hpp" #include "sol/object.hpp" #include "sol/table.hpp" #include "sol/types.hpp" @@ -8,8 +10,18 @@ sol::object lua_world_getroom(sol::this_state lua) { return sol::make_object(lua, &Game::getWorld().getRoom()); } +void lua_world_setroom(const std::string &room) { + Game::getWorld().setRoomBin(room); +} + +sol::object lua_world_getplayer(sol::this_state lua) { + return sol::make_object(lua, &Game::getWorld().getPlayer()); +} + void lua_world_set(sol::state_view lua) { auto space = lua["World"].get_or_create(); space.set_function("GetRoom", lua_world_getroom); + space.set_function("SetRoom", lua_world_setroom); + space.set_function("GetPlayer", lua_world_getplayer); } \ No newline at end of file diff --git a/src/prop.cpp b/src/prop.cpp index b6ba7c1..6884135 100644 --- a/src/prop.cpp +++ b/src/prop.cpp @@ -165,7 +165,13 @@ Vector2 Prop::getCollisionCenter() const { bool Prop::getHasInteractable() const { return hasInteractable; } void Prop::setHasInteractable(bool value) { this->hasInteractable = value; } -Interactable *Prop::getInteractable() const { return interactable.get(); } +Interactable *Prop::getInteractable() const { + if (hasInteractable) { + return interactable.get(); + } else { + return nullptr; + } +} std::string Prop::getInteractableType() const { return interactable->getType(); diff --git a/src/propsContainer.cpp b/src/propsContainer.cpp new file mode 100644 index 0000000..3d9551e --- /dev/null +++ b/src/propsContainer.cpp @@ -0,0 +1,23 @@ +#include "propsContainer.hpp" +#include "conversion.hpp" +#include "game.hpp" +#include "prop.hpp" + +void PropsContainer::addProp(Vector2 pos, const std::string &type) { + for (auto const &propBin : Game::getBin().props) { + if (propBin.name == type) { + printf("c \n"); + auto p = std::make_unique(propBin); + p->setWorldTilePos(pos, + Game::getWorld().getRoom().getWorldTileSize()); + p->getInteractable()->setProps(nlohmann::json::object()); + + pushObject(fromVector2(pos), std::move(p)); + break; + } + } +} + +Prop *PropsContainer::getPropAt(Vector2 pos) { + return objects[fromVector2(pos)].get(); +} \ No newline at end of file diff --git a/src/room.cpp b/src/room.cpp index ae96a3e..170c8d8 100644 --- a/src/room.cpp +++ b/src/room.cpp @@ -36,7 +36,7 @@ Room::Room() { this->collisions = std::make_unique(); this->props = std::make_unique(); this->tileMap = std::unique_ptr{}; - this->actors = std::map>{}; + this->actors = std::make_unique(); this->player = std::unique_ptr{}; } @@ -63,7 +63,7 @@ Room::Room(const std::string &fileName, int tileSize) { this->interactables = std::make_unique(); this->collisions = std::make_unique(); - this->actors = std::map>{}; + this->actors = std::make_unique(); this->props = std::make_unique(); this->tileMap = std::make_unique(fileName); @@ -116,7 +116,7 @@ Room::Room(const std::string &fileName, int tileSize) { a->setTilePosition( Vector2{static_cast(x), static_cast(y)}, tileMap->getTileSet()->getTileSize()); - actors[value.at("name")] = std::move(a); + actors->getActors()[value.at("name")] = std::move(a); } interactables->addJsonData(roomJson.at("interactables")); @@ -133,7 +133,7 @@ Room::Room(const RoomBin &bin) : Room() { this->interactables = std::make_unique(); this->collisions = std::make_unique(); - this->actors = std::map>{}; + this->actors = std::make_unique(); this->props = std::make_unique(); this->tileMap = std::make_unique(bin); @@ -188,7 +188,7 @@ Room::Room(const RoomBin &bin) : Room() { Vector2{static_cast(actorSource.tilePos.x), static_cast(actorSource.tilePos.y)}, tileMap->getTileSet()->getTileSize()); - actors[actorSource.name] = std::move(a); + actors->getActors()[actorSource.name] = std::move(a); break; } } @@ -242,7 +242,7 @@ json Room::dumpJson() { } auto actorsMap = std::map{}; - for (auto &[name, obj] : actors) { + for (auto &[name, obj] : actors->getActors()) { std::string key = TextFormat("%i;%i", static_cast(obj->getTilePosition().x), static_cast(obj->getTilePosition().y)); @@ -269,7 +269,7 @@ json Room::dumpJson() { void Room::unload() const { tileMap->unload(); - for (auto &[vect, actor] : actors) { + for (auto &[vect, actor] : actors->getActors()) { actor->unload(); } @@ -277,7 +277,7 @@ void Room::unload() const { } void Room::update() { - for (auto &[vect, actor] : actors) { + for (auto &[vect, actor] : actors->getActors()) { actor->update(); } player->update(); @@ -328,7 +328,7 @@ void Room::draw() const { } } - for (auto &[vect, actor] : actors) { + for (auto &[vect, actor] : actors->getActors()) { actor->draw(); } player->draw(); @@ -378,6 +378,4 @@ InteractablesContainer &Room::getInteractables() const { PropsContainer &Room::getProps() const { return *this->props; } -std::map> &Room::getActors() { - return this->actors; -} +ActorContainer &Room::getActors() { return *this->actors; } diff --git a/src/worldService.cpp b/src/worldService.cpp index b12327b..846f069 100644 --- a/src/worldService.cpp +++ b/src/worldService.cpp @@ -1,5 +1,7 @@ #include "worldService.hpp" #include "game.hpp" +#include "room.hpp" +#include #include WorldService::WorldService() { @@ -17,19 +19,24 @@ WorldService::WorldService() { Room &WorldService::getRoom() const { return *this->room; } void WorldService::setRoom(const std::string_view &filePath) { + this->room = std::make_unique(std::string(filePath)); +} + +void WorldService::setRoomBin(RoomBin bin) { + this->room = std::make_unique(bin); +} + +void WorldService::setRoomBin(const std::string &roomBin) { for (RoomBin bin : Game::getBin().rooms) { - if (bin.name == filePath) { + if (bin.name == roomBin) { deferRoomChange = true; deferredRoomId = bin.name; + doFadeTransition(); break; } } } -void WorldService::setRoomBin(RoomBin bin) { - this->room = std::make_unique(bin); -} - void WorldService::doFadeTransition() { this->transitionActive = true; this->frameCounter = 0; From e11132bc0473ceaf8a44ee873bc831fcab575422 Mon Sep 17 00:00:00 2001 From: CDevv Date: Mon, 23 Mar 2026 18:38:15 +0200 Subject: [PATCH 30/35] Translate new keys in bulgarian Signed-off-by: CDevv --- resources/translations/bg.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/resources/translations/bg.json b/resources/translations/bg.json index 0e6d719..bc35d6c 100644 --- a/resources/translations/bg.json +++ b/resources/translations/bg.json @@ -25,12 +25,15 @@ "screen": { "starting": { "get_started": "Започни!", - "description": "Натиснете на 'Нов проект', за да създадете нов проект или 'Отвори проект' за да отворите съществуващ проект!" + "description": "Натиснете на 'Нов проект', за да създадете нов проект или 'Отвори проект' за да отворите съществуващ проект!", + "actions": "Действия", + "recent_projects": "Скорошни проекти" }, "options": { "language": "Език", "theme": "Тема", - "theme_notice": "Препоръчително е да рестартирате редактора след смяна на темата!" + "theme_notice": "Препоръчително е да рестартирате редактора след смяна на темата!", + "hotkey": "Бърз клавиш" }, "project": { "create_new_resource": "Нов ресурс", @@ -100,6 +103,9 @@ "widget": { "filechooser": { "select_a_file": "Избери файл" + }, + "hotkey_modifier": { + "listening": "Слушам..." } }, "button": { From 3e5b886ab79b497a19741fc9ed91e3e13576bf94 Mon Sep 17 00:00:00 2001 From: thefirey33 Date: Mon, 23 Mar 2026 19:46:08 +0300 Subject: [PATCH 31/35] update turkish translation --- resources/translations/tr.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/resources/translations/tr.json b/resources/translations/tr.json index 5be3277..37e7070 100644 --- a/resources/translations/tr.json +++ b/resources/translations/tr.json @@ -25,12 +25,15 @@ "screen": { "starting": { "get_started": "Başla!", - "description": "'Yeni Proje'ye tıklayarak yeni proje oluşturun, ya da 'Proje Aç' ile birini açın!" + "description": "'Yeni Proje'ye tıklayarak yeni proje oluşturun, ya da 'Proje Aç' ile birini açın!", + "actions": "Eylemler", + "recent_projects": "Son Projeler" }, "options": { "language": "Dil", "theme": "Tema", - "theme_notice": "Tema değiştirdikten sonra düzenleyici yeniden başlatmanız önerilir!" + "theme_notice": "Tema değiştirdikten sonra düzenleyici yeniden başlatmanız önerilir!", + "hotkey": "Kısayol Tuşu" }, "project": { "create_new_resource": "Yeni Kaynak", @@ -100,9 +103,12 @@ "widget": { "filechooser": { "select_a_file": "Dosya Seç" + }, + "hotkey_modifier": { + "listening": "Bir tuşa basın..." } }, "button": { "go_back": "Geri Git" } -} \ No newline at end of file +} From 7d33293595b2bfa7029b1742fd7ff2bb3bbb4792 Mon Sep 17 00:00:00 2001 From: thefirey33 Date: Mon, 23 Mar 2026 19:57:48 +0300 Subject: [PATCH 32/35] fix compiler warning --- src/editor/widgets/hotkeyModifier.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor/widgets/hotkeyModifier.cpp b/src/editor/widgets/hotkeyModifier.cpp index 4948def..801fa6f 100644 --- a/src/editor/widgets/hotkeyModifier.cpp +++ b/src/editor/widgets/hotkeyModifier.cpp @@ -209,6 +209,7 @@ const KeyboardKey tguiToRaylibKey(tguiKey k) { case tguiKey::Unknown: return KEY_NULL; } + return KEY_NULL; } const std::string keyboardKeyToName(KeyboardKey k) { switch (k) { From 5126fc0c04ede520eacd615aea4e19021712ece7 Mon Sep 17 00:00:00 2001 From: thefirey33 Date: Mon, 23 Mar 2026 20:45:29 +0300 Subject: [PATCH 33/35] fix segfault --- src/editor/project.cpp | 10 ++++++++-- src/game.cpp | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/editor/project.cpp b/src/editor/project.cpp index fa24081..59e8aab 100644 --- a/src/editor/project.cpp +++ b/src/editor/project.cpp @@ -558,8 +558,14 @@ void Project::buildProject() { resultPath /= projectTitle; #endif - std::filesystem::copy(baseGamePath, resultPath, - std::filesystem::copy_options::overwrite_existing); + try { + std::filesystem::copy( + baseGamePath, resultPath, + std::filesystem::copy_options::overwrite_existing); + } catch (const std::exception &) { + printf("failed to copy file, aborting...\n"); + return; + } #ifdef _WIN64 diff --git a/src/game.cpp b/src/game.cpp index c5a9967..aa803f3 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2,7 +2,6 @@ #include "gamedata.hpp" #include "scriptService.hpp" #include "soundService.hpp" -#include #include #include #include @@ -94,6 +93,8 @@ void Game::unload() { sounds->unload(); world->unload(); ui->unload(); + // NOTE: Without this, we might get segfaults... beware! + state.reset(); } void Game::setLua(sol::state_view lua) { scripts->setLua(lua); } From 89d496fdf97bbe055dc59fcb653616bd9f7dcddc Mon Sep 17 00:00:00 2001 From: thefirey33 Date: Mon, 23 Mar 2026 20:47:16 +0300 Subject: [PATCH 34/35] remove not needed dependencies and finish up --- include/lua/worldApi.hpp | 1 - include/stateService.hpp | 4 ++-- src/lua/apiTypes.cpp | 1 - src/lua/worldApi.cpp | 1 - src/stateService.cpp | 1 - 5 files changed, 2 insertions(+), 6 deletions(-) diff --git a/include/lua/worldApi.hpp b/include/lua/worldApi.hpp index 7791793..bb0eeaa 100644 --- a/include/lua/worldApi.hpp +++ b/include/lua/worldApi.hpp @@ -1,7 +1,6 @@ #ifndef _RPGPP_LUA_WORLDAPI_H #define _RPGPP_LUA_WORLDAPI_H -#include "room.hpp" #include "sol/state_view.hpp" sol::object lua_world_getroom(sol::this_state lua); diff --git a/include/stateService.hpp b/include/stateService.hpp index 434fa1b..df845a3 100644 --- a/include/stateService.hpp +++ b/include/stateService.hpp @@ -3,14 +3,14 @@ #include "sol/table.hpp" #include "sol/types.hpp" -#include #include #include #include /** The StateService is responsible for storing gameplay-related variables * that make up the state of the game. */ -using Value = std::variant; +using Value = std::variant; class StateService { private: /** A pair of string keys and boolean values. */ diff --git a/src/lua/apiTypes.cpp b/src/lua/apiTypes.cpp index e5bd12a..ffd978d 100644 --- a/src/lua/apiTypes.cpp +++ b/src/lua/apiTypes.cpp @@ -10,7 +10,6 @@ #include "raylib.h" #include "sol/forward.hpp" #include "sol/raii.hpp" -#include "sol/types.hpp" #include "tilemap.hpp" #include #include diff --git a/src/lua/worldApi.cpp b/src/lua/worldApi.cpp index 809bade..6f46437 100644 --- a/src/lua/worldApi.cpp +++ b/src/lua/worldApi.cpp @@ -1,6 +1,5 @@ #include "lua/worldApi.hpp" #include "game.hpp" -#include "player.hpp" #include "sol/forward.hpp" #include "sol/object.hpp" #include "sol/table.hpp" diff --git a/src/stateService.cpp b/src/stateService.cpp index 85ad172..e29dafb 100644 --- a/src/stateService.cpp +++ b/src/stateService.cpp @@ -1,6 +1,5 @@ #include "stateService.hpp" #include "sol/error.hpp" -#include StateService::StateService() { gameState.emplace("test", false); } From df521bebb18fb4df6ebca16167c1c8067b461ff7 Mon Sep 17 00:00:00 2001 From: "D. Quan" <60545346+sudoker0@users.noreply.github.com> Date: Tue, 24 Mar 2026 01:00:11 +0700 Subject: [PATCH 35/35] Remove incorrect statement about no Lua binding --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 1d5196c..f6976c2 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ RPG++ is an experimental 2D RPG game engine written in C++. It is currently in early development, but contributions are welcome! -***This is a fresh restart, there are currently no RPG++ lua bindings, the engine hasn't been implemented in this branch yet.*** - screenshot of engine Requirements