diff --git a/README.md b/README.md index 43d060bc..f6976c2c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ ![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! -***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 diff --git a/include/actor.hpp b/include/actor.hpp index f68be713..d6c5d5b3 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. */ @@ -64,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. */ @@ -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. */ @@ -139,4 +145,7 @@ class Actor : public ISaveable { void setCollisionRect(Rectangle rect); }; +Vector2 calcActorTilePos(Vector2 newPosition, Vector2 worldTileSize, + TileSet *tileSet); + #endif diff --git a/include/actorContainer.hpp b/include/actorContainer.hpp new file mode 100644 index 00000000..e06bab28 --- /dev/null +++ b/include/actorContainer.hpp @@ -0,0 +1,26 @@ +#ifndef _RPGPP_ACTORCONTAINER_H +#define _RPGPP_ACTORCONTAINER_H + +#include "actor.hpp" + +class ActorContainer { + private: + std::map> actors; + + public: + /** Construct the actor container. */ + 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 new file mode 100644 index 00000000..9d1345a1 --- /dev/null +++ b/include/baseContainer.hpp @@ -0,0 +1,51 @@ +#ifndef _RPGPP_BASECONTAINER_H +#define _RPGPP_BASECONTAINER_H + +#include "conversion.hpp" +#include "gamedata.hpp" +#include "raylib.h" +#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)); + } + 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); + if (key != this->objects.end()) { + 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()) + 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 6d38a4b2..ba701e30 100644 --- a/include/collisionsContainer.hpp +++ b/include/collisionsContainer.hpp @@ -1,25 +1,17 @@ #ifndef _RPGPP_COLLISIONSCONTAINER_H #define _RPGPP_COLLISIONSCONTAINER_H +#include "baseContainer.hpp" +#include "gamedata.hpp" #include -#include /** A container of collision tiles to be used by a Room */ -class CollisionsContainer { - private: - /** A vector that contains the tile positions of the collision tiles */ - std::vector vec; - +class CollisionsContainer : public BaseContainer { 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; + /** Add a collision */ + void pushCollision(IVector pos); }; #endif \ No newline at end of file diff --git a/include/constants/room.hpp b/include/constants/room.hpp index f30b48b2..a3cfae57 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/conversion.hpp b/include/conversion.hpp new file mode 100644 index 00000000..14339e54 --- /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/editor/actions/mapAction.hpp b/include/editor/actions/mapAction.hpp index 5436041d..fa99dcac 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/defaultConfig.hpp b/include/editor/defaultConfig.hpp new file mode 100644 index 00000000..4911fd2e --- /dev/null +++ b/include/editor/defaultConfig.hpp @@ -0,0 +1,38 @@ +#include "editor.hpp" +#include "raylib.h" +#include "services/hotkeyService.hpp" +#include +#include +#include +#include + +// key-value +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", 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/editor.hpp b/include/editor/editor.hpp index 8cb50102..a29d477f 100644 --- a/include/editor/editor.hpp +++ b/include/editor/editor.hpp @@ -5,6 +5,8 @@ #include "raylib.h" #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 @@ -29,6 +31,8 @@ class Editor { // the current editor gui service, responsible for managing the gui. ThemeService themeService; EditorGuiService guiService; + HotkeyService hotkeyService; + RecentProjectService recentProjectService; public: Editor(); @@ -43,7 +47,9 @@ class Editor { TranslationService &getTranslations(); ThemeService &getThemeService(); FileSystemService &getFs(); + RecentProjectService &getRecentProjectService(); 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/fileViews/fileView.hpp b/include/editor/fileViews/fileView.hpp index caae85c8..6032707b 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 2cc92b60..9af1c032 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/project.hpp b/include/editor/project.hpp index 3b1445d8..4e392121 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/roomLayerViewVisitor.hpp b/include/editor/roomLayerViewVisitor.hpp index e9826adb..7780295c 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/editor/screens/guiScreen.hpp b/include/editor/screens/guiScreen.hpp index aae5f3d5..2d7d8919 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 00d22478..41e97c7b 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(); @@ -58,7 +60,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/configurationService.hpp b/include/editor/services/configurationService.hpp index 0476af9d..27a5eedc 100644 --- a/include/editor/services/configurationService.hpp +++ b/include/editor/services/configurationService.hpp @@ -7,13 +7,20 @@ 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(); }; diff --git a/include/editor/services/fileSystemService.hpp b/include/editor/services/fileSystemService.hpp index 87a6d5a1..458b7624 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 @@ -32,6 +33,7 @@ class FileSystemService { public: FileSystemService(); void unload(); + void promptNewProject(); void promptOpenProject(); std::string &getTypeName(EngineFileType fileType); std::array &getTypeNames(); diff --git a/include/editor/services/hotkeyService.hpp b/include/editor/services/hotkeyService.hpp new file mode 100644 index 00000000..10cc1fe7 --- /dev/null +++ b/include/editor/services/hotkeyService.hpp @@ -0,0 +1,64 @@ + +#include "ini.h" +#include "raylib.h" +#include +#include +#include + +#ifndef RPGPP_HOTKEYSERVICE_H +#define RPGPP_HOTKEYSERVICE_H + +class Editor; +struct Hotkey { + 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 { + private: + 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); + void unregisterHotkeyCallback(const std::string &uniqueHkCbId); + 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/services/recentProjectService.hpp b/include/editor/services/recentProjectService.hpp new file mode 100644 index 00000000..4a9a2099 --- /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/include/editor/widgets/fileTab.hpp b/include/editor/widgets/fileTab.hpp index 71b0d05b..cf364ce3 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/include/editor/widgets/hotkeyModifier.hpp b/include/editor/widgets/hotkeyModifier.hpp new file mode 100644 index 00000000..6a7ca958 --- /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/include/editor/widgets/toolbox.hpp b/include/editor/widgets/toolbox.hpp index 19e7274e..b53b83c9 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,19 @@ 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/include/game.hpp b/include/game.hpp index 339fcfff..cac70811 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/gamedata.hpp b/include/gamedata.hpp index 7a3f600a..d450558a 100644 --- a/include/gamedata.hpp +++ b/include/gamedata.hpp @@ -15,6 +15,24 @@ 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); + } + + 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 { @@ -33,6 +51,7 @@ struct ActorBin { struct ActorInRoomBin { std::string name; + std::string source; IVector tilePos; }; @@ -77,6 +96,7 @@ struct TileSetBin { struct ImageBin { std::vector data; int dataSize; + std::string ext; }; struct MusicBin { @@ -119,7 +139,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 be7f0f1c..7fe1ebdb 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/interactablesContainer.hpp b/include/interactablesContainer.hpp index 32f18f29..03b2b502 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,26 @@ 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); + /** 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(int x, int y) const; - /** Remove an Interactable by its tile position */ - void removeInteractable(int x, int y); + Interactable *getInt(IVector pos); + Interactable *getIntVec2(Vector2 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/interfaceService.hpp b/include/interfaceService.hpp index 298b724b..6e45f611 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 00000000..9c71e50c --- /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/interfaceApi.hpp b/include/lua/interfaceApi.hpp new file mode 100644 index 00000000..6fe52132 --- /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 00000000..3127be9f --- /dev/null +++ b/include/lua/soundsApi.hpp @@ -0,0 +1,12 @@ +#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_playSound(const std::string &id); +void lua_sounds_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 00000000..129ecc06 --- /dev/null +++ b/include/lua/stateApi.hpp @@ -0,0 +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, sol::object value); +Value lua_gamestate_getval(const std::string &prop); +void lua_gamestate_set(sol::state_view lua); + +#endif diff --git a/include/lua/worldApi.hpp b/include/lua/worldApi.hpp new file mode 100644 index 00000000..bb0eeaaa --- /dev/null +++ b/include/lua/worldApi.hpp @@ -0,0 +1,11 @@ +#ifndef _RPGPP_LUA_WORLDAPI_H +#define _RPGPP_LUA_WORLDAPI_H + +#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/player.hpp b/include/player.hpp index f07b9cec..4dde0cd7 100644 --- a/include/player.hpp +++ b/include/player.hpp @@ -42,10 +42,21 @@ 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. */ 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); + /** Get the position of the collision */ Vector2 getCollisionPos() const; }; diff --git a/include/propsContainer.hpp b/include/propsContainer.hpp new file mode 100644 index 00000000..476a43c7 --- /dev/null +++ b/include/propsContainer.hpp @@ -0,0 +1,15 @@ +#ifndef _RPGPP_PROPSCONTAINER_H +#define _RPGPP_PROPSCONTAINER_H + +#include "baseContainer.hpp" +#include "prop.hpp" +#include + +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 7d6b4d51..e68621e0 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; @@ -15,6 +16,7 @@ class TileMap; using json = nlohmann::json; #include "actor.hpp" +#include "actorContainer.hpp" #include "collisionsContainer.hpp" #include "interactablesContainer.hpp" #include "player.hpp" @@ -39,12 +41,12 @@ 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. */ - 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 */ - std::unique_ptr> actors; + std::unique_ptr actors; /** This Room's only Player. */ std::unique_ptr player; void updateCamera(); @@ -98,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(Vector2 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() const; - /** 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; + ActorContainer &getActors(); }; #endif diff --git a/include/scriptService.hpp b/include/scriptService.hpp new file mode 100644 index 00000000..d1377e97 --- /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/include/stateService.hpp b/include/stateService.hpp index ea04873d..df845a3b 100644 --- a/include/stateService.hpp +++ b/include/stateService.hpp @@ -1,21 +1,28 @@ #ifndef _RPGPP_STATESERVICE_H #define _RPGPP_STATESERVICE_H +#include "sol/table.hpp" +#include "sol/types.hpp" #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, 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/include/worldService.hpp b/include/worldService.hpp index efa41959..3bb2e436 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/interactables/dialogue.json b/resources/interactables/dialogue.json index c3c6f13d..b2cabeb1 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 00000000..b1292c1a --- /dev/null +++ b/resources/scripts/dialogue.lua @@ -0,0 +1,57 @@ +function interact() + Interface.OpenDialogue(props.dialogue) + + room = World.GetRoom() + player = World.GetPlayer() + pos = player:GetPosition() + + actor = player:GetActor() + + actorpos = actor:GetPosition() + + 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); + 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/resources/scripts/test.lua b/resources/scripts/test.lua deleted file mode 100644 index 2e9f11a1..00000000 --- a/resources/scripts/test.lua +++ /dev/null @@ -1,4 +0,0 @@ -function interact() -print(dialogue) -opendiag(dialogue) -end diff --git a/resources/translations/bg.json b/resources/translations/bg.json index 0e6d7193..bc35d6c6 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": { diff --git a/resources/translations/en_us.json b/resources/translations/en_us.json index 4d2bfd6e..cfd6ce5a 100644 --- a/resources/translations/en_us.json +++ b/resources/translations/en_us.json @@ -25,12 +25,15 @@ "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", "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 +103,9 @@ "widget": { "filechooser": { "select_a_file": "Select a File" + }, + "hotkey_modifier": { + "listening": "Listening..." } }, "button": { diff --git a/resources/translations/tr.json b/resources/translations/tr.json index 5be32778..37e7070b 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 +} diff --git a/resources/translations/vn.json b/resources/translations/vn.json index 3439257d..2027019f 100644 --- a/resources/translations/vn.json +++ b/resources/translations/vn.json @@ -27,13 +27,16 @@ "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": { "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 +113,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 ac4abc55..2e10c1d6 100644 --- a/rpgpp.ini +++ b/rpgpp.ini @@ -1,3 +1,18 @@ [rpgpp] language=en_us theme=Dark + +[hotkeys] +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/actor.cpp b/src/actor.cpp index 8561b89c..4bb39e98 100644 --- a/src/actor.cpp +++ b/src/actor.cpp @@ -1,13 +1,16 @@ #include "actor.hpp" #include "game.hpp" #include "gamedata.hpp" +#include "tileset.hpp" #include #include +#include #include #include #include #include #include +#include #include using json = nlohmann::json; @@ -117,7 +120,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}; @@ -261,8 +264,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); } @@ -328,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; @@ -389,6 +412,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; @@ -421,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/actorContainer.cpp b/src/actorContainer.cpp new file mode 100644 index 00000000..5048dcce --- /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 6b88b644..960570c8 100644 --- a/src/collisionsContainer.cpp +++ b/src/collisionsContainer.cpp @@ -1,30 +1,7 @@ #include "collisionsContainer.hpp" +#include "gamedata.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 +void CollisionsContainer::pushCollision(IVector pos) { pushObject(pos, true); } diff --git a/src/conversion.cpp b/src/conversion.cpp new file mode 100644 index 00000000..a41701a5 --- /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/dialogueBalloon.cpp b/src/dialogueBalloon.cpp index ce35d08d..83be1db3 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/actions/eraseTileAction.cpp b/src/editor/actions/eraseTileAction.cpp index b6381b94..c4c48127 100644 --- a/src/editor/actions/eraseTileAction.cpp +++ b/src/editor/actions/eraseTileAction.cpp @@ -1,6 +1,8 @@ #include "actions/eraseTileAction.hpp" #include "actions/mapAction.hpp" +#include "conversion.hpp" +#include "raymath.h" #include "views/worldView.hpp" EraseTileAction::EraseTileAction(MapActionData a) : MapAction(a) {} @@ -11,18 +13,22 @@ 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), - static_cast(data.worldTile.y)}); + data.room->getProps().removeObject(fromVector2(data.worldTile)); + } break; + case RoomLayer::LAYER_ACTORS: { + for (auto &&a : data.room->getActors().getActors()) { + if (Vector2Equals(a.second->getTilePosition(), data.worldTile)) { + data.room->getActors().getActors().erase(a.first); + break; + } + } } break; default: break; @@ -35,9 +41,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 2f1520ce..36535dd0 100644 --- a/src/editor/actions/placeTileAction.cpp +++ b/src/editor/actions/placeTileAction.cpp @@ -1,10 +1,14 @@ #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 #include PlaceTileAction::PlaceTileAction(MapActionData a) : MapAction(a) {} @@ -19,12 +23,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); @@ -34,11 +39,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; @@ -54,9 +59,18 @@ 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; + case RoomLayer::LAYER_ACTORS: { + auto a = std::make_unique(data.interactableFullPath); + a->setTilePosition( + data.worldTile, + data.room->getTileMap()->getTileSet()->getTileSize()); + data.room->getActors().getActors()[data.actorName] = std::move(a); } break; default: break; @@ -69,16 +83,21 @@ 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), - static_cast(data.worldTile.y)}); + data.room->getProps().removeObject(fromVector2(data.worldTile)); + } break; + case RoomLayer::LAYER_ACTORS: { + for (auto &&a : data.room->getActors().getActors()) { + if (Vector2Equals(a.second->getTilePosition(), data.worldTile)) { + data.room->getActors().getActors().erase(a.first); + break; + } + } } break; default: break; diff --git a/src/editor/childWindows/settingsWindow.cpp b/src/editor/childWindows/settingsWindow.cpp index 3867912a..1be08ee0 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(); @@ -46,14 +53,14 @@ 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()) { - Editor::instance->getGui().currentScreen->bindMenuBar(ptr); + Editor::instance->getGui().currentScreen->bindMenuBarAndHK(ptr); } }); @@ -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/editor.cpp b/src/editor/editor.cpp index 7c7434b4..f981c3e0 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -10,7 +10,7 @@ Editor *Editor::instance; Editor::Editor() : configurationService(), translationService(this), themeService(this), - project{nullptr} { + hotkeyService(), project{nullptr} { instance = this; } @@ -29,8 +29,14 @@ ThemeService &Editor::getThemeService() { return themeService; } FileSystemService &Editor::getFs() { return fileSystem; } +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/fileViews/roomFileView.cpp b/src/editor/fileViews/roomFileView.cpp index aaaf606c..90fc68e0 100644 --- a/src/editor/fileViews/roomFileView.cpp +++ b/src/editor/fileViews/roomFileView.cpp @@ -18,9 +18,11 @@ #include "widgets/propertyFields/fileField.hpp" #include "widgets/toolbox.hpp" #include + 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), @@ -57,6 +59,7 @@ RoomFileView::RoomFileView() { layerChoose->addItem("Collisions"); layerChoose->addItem("Interactables"); layerChoose->addItem("Props"); + layerChoose->addItem("Actors"); layerChoose->setSelectedItemByIndex(0); widgetContainer.push_back(layerChoose); @@ -131,17 +134,29 @@ 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, - "Start Point", - "tool_startpoint.png"}); + + 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"}}}; + + 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, @@ -153,19 +168,34 @@ 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); }); - 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; - }); 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/main.cpp b/src/editor/main.cpp index f18ea4fd..8f69c162 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 72792f02..59e8aab8 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 @@ -62,7 +61,8 @@ 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(); @@ -76,9 +76,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(), false); + std::make_unique(), forceSwitch); } json Project::toJson() { @@ -187,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); @@ -224,10 +229,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,21 +247,22 @@ 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 &[aName, actor] : room->getActors().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(); @@ -290,7 +296,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 +310,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; @@ -324,10 +331,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 +360,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)) { @@ -358,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); } @@ -532,8 +558,14 @@ void Project::buildProject() { resultPath /= projectTitle; #endif - std::filesystem::copy(baseGamePath, resultPath, - std::filesystem::copy_options::update_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/editor/roomLayerViewVisitor.cpp b/src/editor/roomLayerViewVisitor.cpp index 52e9db9d..7ce2cd84 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/screens/projectScreen.cpp b/src/editor/screens/projectScreen.cpp index 3fe28e77..d6b2bdc2 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" @@ -60,8 +59,22 @@ 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()) { + 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,26 +82,22 @@ 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); + + 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: @@ -166,6 +175,9 @@ void ProjectScreen::initItems(tgui::Group::Ptr layout) { } }); + Editor::instance->getHotkeyService().registerHotkeyCallback( + "close_tab", [this] { fileTabs->closeCurrentTab(); }); + tabsContainer->add(fileTabs); layout->add(tabsContainer); @@ -207,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/screens/welcomeScreen.cpp b/src/editor/screens/welcomeScreen.cpp index a33f2826..14d67e14 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,42 +54,75 @@ 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->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", &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); openProjButton->setTextSize(ACTION_BUTTON_SIZE); + newProjButton->onPress( + [this] { Editor::instance->getFs().promptNewProject(); }); + + openProjButton->onPress( + [] { Editor::instance->getFs().promptOpenProject(); }); - newProjectDialog = NewProjectWindow::create(); + left->add(newProjButton); + left->add(buttonPadding); + left->add(openProjButton); - newProjButton->onPress([this] { - auto childDialog = tgui::ChildWindow::create(); + const auto padding = tgui::BoxLayout::create(); + padding->setWidth(10); + padding->setAutoLayout(tgui::AutoLayout::Left); + actionsContainer->add(padding); - 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); - } - }); + const auto right = tgui::BoxLayout::create(); + right->setAutoLayout(tgui::AutoLayout::Fill); + + const auto recentProjectLabel = tgui::Label::create(""); + bindTranslation(recentProjectLabel, "screen.starting.recent_projects", + &tgui::Label::setText); + 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); }); - openProjButton->onPress( - [] { Editor::instance->getFs().promptOpenProject(); }); + right->add(recentProject); - verticalLayout->add(newProjButton); - verticalLayout->add(openProjButton); + actionsContainer->add(right); layout->add(verticalLayout); } diff --git a/src/editor/services/configurationService.cpp b/src/editor/services/configurationService.cpp index c11d1111..4814e781 100644 --- a/src/editor/services/configurationService.cpp +++ b/src/editor/services/configurationService.cpp @@ -1,30 +1,64 @@ #include "services/configurationService.hpp" +#include "defaultConfig.hpp" +#include "ini.h" #include "raylib.h" +#include +#include +#include +#include + +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) { + return this->iniStructure[field][key]; +} + std::string ConfigurationService::getStringValue(const std::string &key) { - if (this->iniStructure[GENERAL_CONF_FIELD].has(key)) - return this->iniStructure[GENERAL_CONF_FIELD][key]; - std::stringstream ss; - ss << "configuration key doesn't exist" << key; - throw std::runtime_error(ss.str()); + return this->getStringValue(GENERAL_CONF_FIELD, key); } -void ConfigurationService::setStringValue(const std::string &key, +void ConfigurationService::setStringValue(const std::string &field, + const std::string &key, const std::string &value) { - this->iniStructure[GENERAL_CONF_FIELD].set(key, value); + this->iniStructure[field].set(key, value); } +void ConfigurationService::setStringValue(const std::string &key, + const std::string &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 00be0d01..73215756 100644 --- a/src/editor/services/editorGuiService.cpp +++ b/src/editor/services/editorGuiService.cpp @@ -39,7 +39,22 @@ 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", [] { Editor::instance->getFs().promptNewProject(); }); + hks.registerHotkeyCallback( + "open_project", [] { Editor::instance->getFs().promptOpenProject(); }); } void EditorGuiService::resetUi() { @@ -91,15 +106,20 @@ void EditorGuiService::uiLoop() { tgui::Theme::addRendererInheritanceParent("RoomToolbox", "Tabs"); // main loop. while (!WindowShouldClose()) { - if (IsKeyPressed(KEY_F3)) - perfOverlay.Toggle(); 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()) { @@ -240,17 +260,20 @@ void EditorGuiService::initMenuBar() { this->menuBar = menuBarPtr; auto &ts = Editor::instance->getTranslations(); - const auto &fileOptionsTranslation = ts.getKey("menu.file._label"); - const auto &fileOpenProjectTranslation = - ts.getKey("menu.file.open_project"); + 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}, - [] { 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/fileSystemService.cpp b/src/editor/services/fileSystemService.cpp index 27ce788f..35139937 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 @@ -16,7 +17,6 @@ #include "editor.hpp" #include "raylib.h" -#include "screens/projectScreen.hpp" FileSystemService::FileSystemService() { editorBaseDir = GetWorkingDirectory(); @@ -40,14 +40,30 @@ 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()) { + auto path = Project::create(dirPath, title); + Project::openProject(path, true); + } + newProjectDialog->window->close(); + }); +} + void FileSystemService::promptOpenProject() { auto files = tgui::FileDialog::create(); 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/hotkeyService.cpp b/src/editor/services/hotkeyService.cpp new file mode 100644 index 00000000..ff6c4a9c --- /dev/null +++ b/src/editor/services/hotkeyService.cpp @@ -0,0 +1,115 @@ +#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); + 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 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() { + std::map serialized{}; + 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) { + int k = stoi(keyStr); + hotkeyMap[keyId] = unpack(k); +} + +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); + } +} + +std::string HotkeyService::registerHotkeyCallback(const std::string &keyId, + std::function cb) { + std::string uniqueHkCbId = get_uuid(); + hotkeysCb[uniqueHkCbId] = {keyId, cb}; + return uniqueHkCbId; +} + +void HotkeyService::unregisterHotkeyCallback(const std::string &uniqueHkCbId) { + hotkeysCb.erase(uniqueHkCbId); +} + +void HotkeyService::addHotkey(const std::string &keyId, const Hotkey &keys) { + hotkeyMap[keyId] = keys; +} + +void HotkeyService::removeHotkey(const std::string &keyId) { + hotkeyMap.erase(keyId); +} + +const HotkeyMap HotkeyService::listHotkeys() { return this->hotkeyMap; } + +void HotkeyService::fire() { + for (auto &[keyId, keys] : hotkeyMap) { + 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)) { + for (auto [_, data] : hotkeysCb) { + if (data.first == keyId) { + data.second(); + } + } + } + } +} diff --git a/src/editor/services/recentProjectService.cpp b/src/editor/services/recentProjectService.cpp new file mode 100644 index 00000000..d9be8053 --- /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; +} diff --git a/src/editor/views/roomView.cpp b/src/editor/views/roomView.cpp index 0052c227..b7332134 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" @@ -20,6 +22,7 @@ #include "tileset.hpp" #include "views/worldView.hpp" #include +#include #include #include @@ -110,6 +113,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); @@ -125,14 +129,10 @@ 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)) { - DrawRectangleLinesEx(destRect, 2.0f, Fade(GRAY, 0.5f)); + overlayRect = destRect; } } } @@ -145,9 +145,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); @@ -182,10 +182,30 @@ void RoomView::drawCanvas() { } // props - for (auto &&prop : room->getProps()) { - prop.draw(); + for (auto &[pos, prop] : room->getProps().getObjects()) { + prop->draw(); + } + + // actors + 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 + 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); } @@ -288,6 +308,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; } @@ -335,16 +378,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) { @@ -403,8 +460,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); @@ -414,14 +472,19 @@ 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)}); + 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/editor/widgets/fileTab.cpp b/src/editor/widgets/fileTab.cpp index 94bbe04b..3b602641 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)) diff --git a/src/editor/widgets/frameEditor.cpp b/src/editor/widgets/frameEditor.cpp index 68cfa429..a9c0f1cf 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/editor/widgets/hotkeyModifier.cpp b/src/editor/widgets/hotkeyModifier.cpp new file mode 100644 index 00000000..801fa6fc --- /dev/null +++ b/src/editor/widgets/hotkeyModifier.cpp @@ -0,0 +1,482 @@ +#include "TGUI/SubwidgetContainer.hpp" +#include "TGUI/Widgets/Button.hpp" +#include "bindTranslation.hpp" +#include "editor.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; + } + 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 +"; + default: + return "Unknown"; + } +} + +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 = Hotkey{isCtrl, isShift, isAlt, isSuper, 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); +} diff --git a/src/game.cpp b/src/game.cpp index e49526c0..aa803f37 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1,7 +1,7 @@ #include "game.hpp" #include "gamedata.hpp" +#include "scriptService.hpp" #include "soundService.hpp" -#include #include #include #include @@ -18,6 +18,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 +44,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) { @@ -50,8 +52,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); @@ -74,6 +76,8 @@ InterfaceService &Game::getUi() { return *ui; } SoundService &Game::getSounds() { return *sounds; } +ScriptService &Game::getScripts() { return *scripts; } + void Game::update() { sounds->update(); world->update(); @@ -89,17 +93,8 @@ void Game::unload() { sounds->unload(); world->unload(); ui->unload(); + // NOTE: Without this, we might get segfaults... beware! + state.reset(); } -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/gamedata.cpp b/src/gamedata.cpp index a4e24027..21de4833 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) { @@ -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 25ce1b1b..f2b61416 100644 --- a/src/interactable.cpp +++ b/src/interactable.cpp @@ -3,21 +3,22 @@ #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; + this->onTouch = false; } Interactable::Interactable(const std::string &path) { @@ -29,14 +30,16 @@ 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, int tileSize) { this->type = type; - this->props = std::make_unique(); + 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}; @@ -73,10 +77,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; } @@ -91,7 +91,7 @@ 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 = std::make_unique(json::object()); } void Interactable::setProps(nlohmann::json j) { @@ -109,30 +109,32 @@ 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); + state["this"] = this; 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, &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 (lua["interact"].valid()) { - lua["interact"].call(); + if (state["interact"].valid()) { + 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/interactablesContainer.cpp b/src/interactablesContainer.cpp index 819bb52f..5f9e575e 100644 --- a/src/interactablesContainer.cpp +++ b/src/interactablesContainer.cpp @@ -1,4 +1,6 @@ #include "interactablesContainer.hpp" +#include "conversion.hpp" +#include "game.hpp" #include "gamedata.hpp" #include "interactable.hpp" #include "tilemap.hpp" @@ -6,89 +8,81 @@ #include #include #include +#include +#include #include #include 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)); + this->pushObject(pos, std::move(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(); - } +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())); } +} - return res; +Interactable *InteractablesContainer::getInt(IVector pos) { + if (!this->objectExistsAtPosition(pos)) + return nullptr; + return this->getObjectAtPosition(pos).get(); } -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::getIntVec2(Vector2 pos) { + return getInt(fromVector2(pos)); } -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 +108,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 +116,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/interfaceService.cpp b/src/interfaceService.cpp index 50135147..8de9c8bc 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 00000000..ffd978d0 --- /dev/null +++ b/src/lua/apiTypes.cpp @@ -0,0 +1,123 @@ +#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 "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( + "Vector2", + 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(), "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, + "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, "GetActor", + &Player::getActor); +} \ No newline at end of file diff --git a/src/lua/interfaceApi.cpp b/src/lua/interfaceApi.cpp new file mode 100644 index 00000000..317c1b5c --- /dev/null +++ b/src/lua/interfaceApi.cpp @@ -0,0 +1,10 @@ +#include "lua/interfaceApi.hpp" +#include "game.hpp" +#include "sol/table.hpp" + +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(); + space.set_function("OpenDialogue", lua_opendiag); +} diff --git a/src/lua/soundsApi.cpp b/src/lua/soundsApi.cpp new file mode 100644 index 00000000..e9993f55 --- /dev/null +++ b/src/lua/soundsApi.cpp @@ -0,0 +1,23 @@ +#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_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(); + 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/lua/stateApi.cpp b/src/lua/stateApi.cpp new file mode 100644 index 00000000..09daa7c8 --- /dev/null +++ b/src/lua/stateApi.cpp @@ -0,0 +1,33 @@ +#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, 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()); + } +} + +Value 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); +} diff --git a/src/lua/worldApi.cpp b/src/lua/worldApi.cpp new file mode 100644 index 00000000..6f46437b --- /dev/null +++ b/src/lua/worldApi.cpp @@ -0,0 +1,26 @@ +#include "lua/worldApi.hpp" +#include "game.hpp" +#include "sol/forward.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_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/player.cpp b/src/player.cpp index 577d60f2..e99d4fad 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -1,7 +1,9 @@ #include "player.hpp" +#include "conversion.hpp" #include "interactable.hpp" #include #include +#include Player::Player(std::unique_ptr actor, Room &room) : room(room) { this->lock = false; @@ -62,10 +64,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); @@ -93,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)}; @@ -106,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; } @@ -161,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; } } @@ -176,6 +180,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; @@ -187,11 +193,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/prop.cpp b/src/prop.cpp index b6ba7c1b..68841354 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 00000000..3d9551e4 --- /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 bdf2683d..170c8d8e 100644 --- a/src/room.cpp +++ b/src/room.cpp @@ -1,17 +1,21 @@ #include "room.hpp" #include "actor.hpp" +#include "actorContainer.hpp" #include "collisionsContainer.hpp" +#include "conversion.hpp" #include "game.hpp" #include "gamedata.hpp" #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; @@ -30,10 +34,9 @@ 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->props = std::make_unique>(); + this->actors = std::make_unique(); this->player = std::unique_ptr{}; } @@ -60,8 +63,8 @@ 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->actors = std::make_unique(); + this->props = std::make_unique(); this->tileMap = std::make_unique(fileName); auto actor = std::make_unique(DEFAULT_PLAYER_PATH); @@ -76,7 +79,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 = @@ -90,16 +93,17 @@ 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"); + std::map actorsVec = roomJson.at("actors"); for (auto const &[key, value] : actorsVec) { int count = 0; char **textSplit = TextSplit(key.c_str(), ';', &count); @@ -108,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)}); - addActor(std::move(a)); + auto a = std::make_unique(value.at("source")); + a->setTilePosition( + Vector2{static_cast(x), static_cast(y)}, + tileMap->getTileSet()->getTileSize()); + actors->getActors()[value.at("name")] = std::move(a); } interactables->addJsonData(roomJson.at("interactables")); @@ -129,12 +133,14 @@ 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->actors = 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()); @@ -146,41 +152,43 @@ 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) { - 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) { - if (actorBin.name == actorSource.name) { - auto a = Actor(actorBin); - a.setTilePosition( + for (const auto [name, actorBin] : Game::getBin().actors) { + 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)}); - addActor(std::move(a)); + tileMap->getTileSet()->getTileSize()); + actors->getActors()[actorSource.name] = std::move(a); break; } } @@ -201,10 +209,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); } @@ -215,29 +223,36 @@ 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(); } propsMap[key] = propJson; } - auto actorsMap = std::map{}; - for (auto &&a : *actors) { + auto actorsMap = std::map{}; + for (auto &[name, obj] : actors->getActors()) { 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)); + + auto actorInfo = std::map{}; + actorInfo["source"] = + TextFormat("actors/%s", GetFileName(obj->getSourcePath().c_str())); + actorInfo["name"] = name; - actorsMap[key] = a.getSourcePath(); + actorsMap[key] = actorInfo; } roomJson.push_back({"interactables", interactableProps}); @@ -254,16 +269,16 @@ json Room::dumpJson() { void Room::unload() const { tileMap->unload(); - for (auto &&actor : *actors) { - actor.unload(); + for (auto &[vect, actor] : actors->getActors()) { + actor->unload(); } player->unload(); } void Room::update() { - for (auto &&actor : *actors) { - actor.update(); + for (auto &[vect, actor] : actors->getActors()) { + actor->update(); } player->update(); if (!lock) @@ -271,7 +286,7 @@ void Room::update() { } void Room::updateCamera() { - Vector2 playerPos = player->getPosition(); + Vector2 playerPos = player->getCenterPosition(); Vector2 cameraOffset = {0, 0}; Vector2 cameraTarget = {0, 0}; @@ -299,28 +314,28 @@ 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)); } - 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(); } } - for (auto &&actor : *actors) { - actor.draw(); + for (auto &[vect, actor] : actors->getActors()) { + actor->draw(); } 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(); } } @@ -343,10 +358,6 @@ 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(); -} - std::string Room::getMusicSource() const { return musicSource; } void Room::setMusicSource(const std::string_view &newMusicSource) { @@ -365,42 +376,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(Vector2 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() const { return *actors; } +PropsContainer &Room::getProps() const { return *this->props; } -void Room::addActor(Actor actor) const { - this->actors->push_back(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); - } - } -} +ActorContainer &Room::getActors() { return *this->actors; } diff --git a/src/scriptService.cpp b/src/scriptService.cpp new file mode 100644 index 00000000..1f6ae0ea --- /dev/null +++ b/src/scriptService.cpp @@ -0,0 +1,59 @@ +#include "scriptService.hpp" +#include "lua/apiTypes.hpp" +#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" +#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, 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); + lua_world_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 509e6aba..ce7f3768 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 = {}; @@ -15,9 +17,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; @@ -28,14 +27,14 @@ bool SoundService::loadMusic(const std::string &id) { musicBin.fileData.data(), musicBin.fileData.size()); - printf("loaded.. \n"); - if (IsMusicValid(newMusic)) { unload(); this->music = newMusic; musicLoaded = true; } + } else { + printf("Music with such id does not exist: %s \n", id.c_str()); } return musicLoaded; @@ -52,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())); } } diff --git a/src/stateService.cpp b/src/stateService.cpp index 3ee3f405..e29dafbe 100644 --- a/src/stateService.cpp +++ b/src/stateService.cpp @@ -1,8 +1,16 @@ #include "stateService.hpp" +#include "sol/error.hpp" StateService::StateService() { gameState.emplace("test", false); } -bool StateService::getProp(const std::string &prop) const { +void StateService::setProp(const std::string &prop, Value value) { + gameState[prop] = value; +} + +Value 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/src/worldService.cpp b/src/worldService.cpp index b12327b5..846f069f 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; diff --git a/xmake.lua b/xmake.lua index 87965f12..e9d8176d 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") @@ -77,8 +77,9 @@ 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") +add_defines("SOL_LUAJIT") if is_plat("linux") then add_cxxflags("-fPIC") end @@ -106,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) @@ -139,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 })