diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9c283eb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +# allow game dev to overwrite settings +root=false + +[*] +end_of_line = lf +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 84e7c1b..9f871ba 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,22 +1,22 @@ [submodule "SDL"] path = extern/SDL url = https://github.com/libsdl-org/SDL.git - branch = release-2.28.x [submodule "SDL_image"] path = extern/SDL_image url = https://github.com/libsdl-org/SDL_image.git - branch = release-2.8.x [submodule "extern/SDL_mixer"] path = extern/SDL_mixer url = https://github.com/libsdl-org/SDL_mixer.git - branch = release-2.8.x [submodule "extern/SDL_ttf"] path = extern/SDL_ttf url = https://github.com/libsdl-org/SDL_ttf.git - branch = release-2.22.x [submodule "extern/tmxlite"] path = extern/tmxlite url = https://github.com/fallahn/tmxlite.git [submodule "docs/doxygen-awesome-css"] path = docs/doxygen-awesome-css url = https://github.com/jothepro/doxygen-awesome-css.git + +[submodule "extern/nlohmann_json"] + path = extern/nlohmann_json + url = https://github.com/nlohmann/json.git diff --git a/CMakeLists.txt b/CMakeLists.txt index ea6a26b..75f7912 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.15) project(engine) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(ENGINE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) @@ -12,10 +12,10 @@ set(ENGINE_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(BUILD_SHARED_LIBS FALSE) -set(SDL2MIXER_VENDORED ON) -set(SDL2TTF_VENDORED ON) +set(SDLMIXER_VENDORED ON) +set(SDLTTF_VENDORED ON) -set(SDL2_SOURCE_DIR “${ENGINE_SOURCE_DIR}/extern/SDL”) +set(SDL_SOURCE_DIR “${ENGINE_SOURCE_DIR}/extern/SDL”) set(TMXLITE_STATIC_LIB TRUE) @@ -24,6 +24,8 @@ add_subdirectory(extern/SDL_image EXCLUDE_FROM_ALL) add_subdirectory(extern/SDL_mixer EXCLUDE_FROM_ALL) add_subdirectory(extern/SDL_ttf EXCLUDE_FROM_ALL) add_subdirectory(extern/tmxlite/tmxlite EXCLUDE_FROM_ALL) +add_subdirectory(extern/nlohmann_json EXCLUDE_FROM_ALL) + file(GLOB_RECURSE SOURCES ${ENGINE_SOURCE_DIR}/src/*.cpp) add_library(${PROJECT_NAME} ${SOURCES}) @@ -31,11 +33,11 @@ add_library(${PROJECT_NAME} ${SOURCES}) target_include_directories(${PROJECT_NAME} PUBLIC ${ENGINE_INCLUDE_DIR}) target_link_libraries(${PROJECT_NAME} PUBLIC # should be private when all SDL functionality has a wrapper - SDL2::SDL2main - SDL2::SDL2-static - SDL2_image::SDL2_image-static - SDL2_mixer::SDL2_mixer-static - SDL2_ttf::SDL2_ttf-static + SDL3::SDL3-static + SDL3_image::SDL3_image-static + SDL3_mixer::SDL3_mixer-static + SDL3_ttf::SDL3_ttf-static + nlohmann_json::nlohmann_json tmxlite ) diff --git a/config.json b/config.json new file mode 100644 index 0000000..33a7d13 --- /dev/null +++ b/config.json @@ -0,0 +1,7 @@ +{ + "fullscreen": false, + "title": "VGG (Very Good Game)", + "screen_height": 600, + "screen_width": 800, + "icon": "./engine/internalAssets/iconImage.bmp" +} \ No newline at end of file diff --git a/engine.sublime-project b/engine.sublime-project index 1466f8e..a2490fa 100644 --- a/engine.sublime-project +++ b/engine.sublime-project @@ -2,12 +2,12 @@ "folders": [ { - "path": "." + "path": ".", } - ], + ], "settings": { - "tab_size": 4 + "tab_size": 4, }, "build_systems": [ { @@ -23,7 +23,7 @@ "name": "Release", "shell_cmd": "cmake -DCMAKE_BUILD_TYPE=Release build && cmake --build build", }, - ] + ], }, { "name": "Generate CMake", @@ -43,7 +43,10 @@ "name": "Generate documentation", "shell_cmd": "docker run --rm -v \"$project_path:/source\" -v \"$project_path/docs:/output\" -v \"$project_path/docs/Doxyfile:/Doxyfile\" vego_engine-docker", }, - ] + ], } - ] + ], + "debugger_configurations": + [ + ], } diff --git a/extern/SDL b/extern/SDL index 05eb080..f686492 160000 --- a/extern/SDL +++ b/extern/SDL @@ -1 +1 @@ -Subproject commit 05eb08053d48fea9052ad02b3d619244aeb868d3 +Subproject commit f6864924f76e1a0b4abaefc76ae2ed22b1a8916e diff --git a/extern/SDL_image b/extern/SDL_image index abcf63a..b1c8ec7 160000 --- a/extern/SDL_image +++ b/extern/SDL_image @@ -1 +1 @@ -Subproject commit abcf63aa71b4e3ac32120fa9870a6500ddcdcc89 +Subproject commit b1c8ec7d75e3d8398940c9e04a8b82886ae6163d diff --git a/extern/SDL_mixer b/extern/SDL_mixer index 5bcd40a..5e2a705 160000 --- a/extern/SDL_mixer +++ b/extern/SDL_mixer @@ -1 +1 @@ -Subproject commit 5bcd40ad962dc72a3c051084ce128d78f7656566 +Subproject commit 5e2a70519294bc6ec44f1870b019ecd4760fde7d diff --git a/extern/SDL_ttf b/extern/SDL_ttf index 4a318f8..4a8bda9 160000 --- a/extern/SDL_ttf +++ b/extern/SDL_ttf @@ -1 +1 @@ -Subproject commit 4a318f8dfaa1bb6f10e0c5e54052e25d3c7f3440 +Subproject commit 4a8bda9197cc4d6fafd188bc9df6c7e8749a43a2 diff --git a/extern/nlohmann_json b/extern/nlohmann_json new file mode 160000 index 0000000..a006a7a --- /dev/null +++ b/extern/nlohmann_json @@ -0,0 +1 @@ +Subproject commit a006a7a48bb30a247f0344b788c62c2806edd90b diff --git a/include/AssetManager.h b/include/AssetManager.h deleted file mode 100644 index 6cb3719..0000000 --- a/include/AssetManager.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -#include "Entity.h" - -class Vector2D; -class Manager; - -enum class PowerupType -{ - HEART, - WALKINGSPEED, - SHOOTINGSPEED -}; - -class AssetManager -{ -public: - - AssetManager(Manager* manager); - ~AssetManager(); - - void createProjectile(Vector2D pos, Vector2D velocity, int scale, int range, int speed, const char* texturePath, Entity* owner); - void createPowerup(Vector2D pos, std::function pickupFunc, std::string texturePath); - - /*! - * \brief Calculates a random spawn position for an object within a given area - * \param size The size (collision box) of the object - * \param spawnArea The area within which a spawn position will be calculated - * \returns Spawn Coordinates for the object - */ - Vector2D calculateSpawnPosition(Vector2D size, Vector2D spawnArea); - template [[deprecated]] T calculateRandomType(int amount); - - //texture management - void addTexture(std::string id, const char* path); - - // sound management - void addSoundEffect(std::string id, const char* path); - - void addMusic(std::string id, const char* path); - - SDL_Texture* getTexture(std::string id); - Mix_Chunk* getSound(std::string id); - Mix_Music* getMusic(std::string id); - -private: - - Manager* man; - std::map textures; - std::map soundEffects; - std::map music; -}; diff --git a/include/BackgroundMusic.h b/include/BackgroundMusic.h new file mode 100644 index 0000000..48516a4 --- /dev/null +++ b/include/BackgroundMusic.h @@ -0,0 +1,3 @@ +#pragma once + +enum class BackgroundMusic; diff --git a/include/ColliderComponent.h b/include/ColliderComponent.h index ef4d5c8..91280c0 100644 --- a/include/ColliderComponent.h +++ b/include/ColliderComponent.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include "Component.h" #include "Vector2D.h" @@ -22,7 +22,7 @@ public: ColliderComponent(const char* tag, float hitboxScale); void init() override; - void update() override; + void update(uint_fast16_t diffTime) override; void removeCollision(); void handleCollision(Vector2D& characterPos, SDL_Rect& characterCollider, SDL_Rect& componentCollider); diff --git a/include/CollisionHandler.h b/include/CollisionHandler.h index 9802f7d..43aa0ae 100644 --- a/include/CollisionHandler.h +++ b/include/CollisionHandler.h @@ -7,7 +7,7 @@ #include "ColliderComponent.h" #include "Constants.h" #include "Entity.h" -#include "SDL_rect.h" +#include #include "SpriteComponent.h" #include "Vector2D.h" #include "Manager.h" diff --git a/include/Component.h b/include/Component.h index 566fee9..8f4a70a 100644 --- a/include/Component.h +++ b/include/Component.h @@ -1,5 +1,7 @@ #pragma once +#include + class Entity; class Component @@ -8,7 +10,7 @@ public: Entity* entity; virtual void init() {} - virtual void update() {} + virtual void update(uint_fast16_t diffTime) {} virtual ~Component() = default; }; \ No newline at end of file diff --git a/include/ConfigLoader.h b/include/ConfigLoader.h new file mode 100644 index 0000000..6262f2b --- /dev/null +++ b/include/ConfigLoader.h @@ -0,0 +1,62 @@ +#pragma once + +#include + +using json = nlohmann::json; + +/*! + * \class ConfigLoader + * \brief Enables configuration of specific engine variables via a custom JSON file. + * + * The Config loader is responsible to handling customization for engine parameters like the + * window icon, window title, ... through json files. + * + * It includes a standard config file and the option to add a custom one by overwriting the setConfigFilePath() + * function within the implementation of the \ref Game class. Those files get merged, with a priorization on + * the parameters set within the custom config file. + * + * + * The currently available config parameters with their default values are: + * \include ../config.json + * + */ + +class ConfigLoader { + +public: + ConfigLoader(); + ~ConfigLoader(); + + /*! + * \brief Creates the final config for the engine + * + * Loads the default config and then creates the final config by either merging + * (if the custom config has been set) or by implementing the standard config (if no custom + * config was set). + * + * \private + */ + void init(); + /*! + * \brief Sets the customConfigPath variable + * + * \param path optional variable that should include the path to the custom config JSON file + * + * \private + */ + void setCustomConfig(const std::optional& path); + /*! + * \brief Gets final configuration + * \return `json` variable containing the final config + * \private + */ + json getFinalConfig(); + +private: + + std::optional customConfigPath; + json finalConfig; + + json loadConfigFromJSON(const std::string& path); + json mergeConfigs(json baseConfig, json customConfig); // +#include +#include +#include +#include "Component.h" + +class DataComponent : public Component +{ +public: + DataComponent() {}; + ~DataComponent() {}; + /** + * @brief Set a key-value pair of any type in the data map + * @details e.g. setEntry("speed", 180); in this case the key is "speed" and the value is set to an integer of 180 + * @param key The name to store the value under + * @param value The value to store of type T + */ + template + void setEntry(const std::string& key, const T& value) { dataMap.insert_or_assign(key, value); } + + /** + * @brief Get a value of type T from the data map + * @details e.g. getEntry("speed"); in this case the key is "speed" and the value is returned as an integer + * @param key The name to retrieve the value from + * @return An optional of type T containing the value if it exists and matches in typeid, otherwise std::nullopt + */ + template + std::optional getEntry(std::string key) const { + if (!this->dataMap.contains(key)) return std::nullopt; + const std::any& value = this->dataMap.at(key); + if (value.type() != typeid(T)) { return std::nullopt; } + return std::any_cast(value); + } +private: + std::map dataMap; +}; \ No newline at end of file diff --git a/include/Entity.h b/include/Entity.h index aa067a4..978a9ac 100644 --- a/include/Entity.h +++ b/include/Entity.h @@ -35,85 +35,90 @@ class Entity { public: - /*! - * \brief Used for rendering order (last is highest) or retrieving entities of group - * \todo Label used in singular entity shouldn't use plural - * \todo HEARTS are rendered above POWERUPS, missleading order - * \todo PROJECTILE are rendered above POWERUPS, missleading order - * \todo Generalize HEARTS as UI or similar - */ - enum class GroupLabel - { - MAPTILES, //!< Entity using TileComponent - PLAYERS, //!< Primary entity in player controll - ENEMIES, //!< \deprecated All players now grouped as Entity::PLAYERS - COLLIDERS, //!< Fixed collider entity, e.g. a wall - PROJECTILE, //!< \todo Document - HEARTS, //!< \todo Document - POWERUPS //!< \todo Document - }; + /*! + * \brief Used for rendering order (last is highest) or retrieving entities of group + * \todo Label used in singular entity shouldn't use plural + * \todo HEARTS are rendered above POWERUPS, missleading order + * \todo PROJECTILE are rendered above POWERUPS, missleading order + * \todo Generalize HEARTS as UI or similar + */ + enum class GroupLabel + { + MAPTILES, //!< Entity using TileComponent + PLAYERS, //!< Primary entity in player controll + ENEMIES, //!< \deprecated All players now grouped as Entity::PLAYERS + COLLIDERS, //!< Fixed collider entity, e.g. a wall + PROJECTILE, //!< \todo Document + HEARTS, //!< \todo Document + POWERUPS //!< \todo Document + }; - /*! - * \todo Document - */ - explicit Entity(Manager& mManager) : - manager(mManager) { }; + /*! + * \todo Document + */ + explicit Entity(Manager& mManager) : + manager(mManager) { }; - void update() const; //!< Call each frame to update all components + void update(uint_fast16_t diffTime) const; //!< Call each frame to update all components - bool isActive() const { return this->active; } //!< \sa destroy() - //! Mark for destruction for Manager::refresh() and disables collision - //! \sa ColliderComponent - void destroy() { - this->active = false; - if (this->hasComponent()) { - this->getComponent().removeCollision(); - } - } + bool isActive() const { return this->active; } //!< \sa destroy() + //! Mark for destruction for Manager::refresh() and disables collision + //! \sa ColliderComponent + void destroy() { + this->active = false; + if (this->hasComponent()) { + this->getComponent().removeCollision(); + } + } - bool hasGroup(Group mGroup); //!< \sa GroupLabel - void addGroup(Group mGroup); //!< \sa GroupLabel - void delGroup(Group mGroup); //!< \sa GroupLabel - //! \returns bitset with true on position GroupLabel if the entity belongs to group - //! \sa GroupLabel - std::bitset getGroupBitSet(); + bool hasGroup(Group mGroup); //!< \sa GroupLabel + void addGroup(Group mGroup); //!< \sa GroupLabel + void delGroup(Group mGroup); //!< \sa GroupLabel + //! \returns bitset with true on position GroupLabel if the entity belongs to group + //! \sa GroupLabel + std::bitset getGroupBitSet(); - //! \sa Manager - Manager& getManager() { return manager; }; + //! \sa Manager + Manager& getManager() { return manager; }; - template bool hasComponent() const //! \sa Component - { - return componentBitSet[getComponentTypeID()]; - } + template bool hasComponent() const //! \sa Component + { + return componentBitSet[getComponentTypeID()]; + } - //! \brief Adds specified type as component and calls Component::init() - //! \param mArgs Constructor arguments of component - template T& addComponent(TArgs&&...mArgs) - { - T* c(new T(std::forward(mArgs)...)); - c->entity = this; - std::unique_ptr uPtr{ c }; - this->components.emplace_back(std::move(uPtr)); + //! \brief Adds specified type as component and calls Component::init() + //! \param mArgs Constructor arguments of component + template T& addComponent(TArgs&&...mArgs) + { + T* c(new T(std::forward(mArgs)...)); + c->entity = this; + std::shared_ptr uPtr{ c }; + this->components.at(getComponentTypeID()) = std::move(uPtr); - componentArray[getComponentTypeID()] = c; - componentBitSet[getComponentTypeID()] = true; + componentArray[getComponentTypeID()] = c; + componentBitSet[getComponentTypeID()] = true; - c->init(); - return *c; - }; - - template T& getComponent() const //! \returns Component of type T - { - auto ptr(componentArray[getComponentTypeID()]); - return *static_cast(ptr); - } + c->init(); + return *c; + }; + + template T& getComponent() const //!< \todo: rewrite to use optionals + { + auto ptr(componentArray[getComponentTypeID()]); + return *static_cast(ptr); + } + + template std::shared_ptr getComponentAsPointer() const + { + return std::static_pointer_cast(components.at(getComponentTypeID())); + } private: - Manager& manager; - bool active = true; - std::vector> components; + Manager& manager; + bool active = true; + std::array, MAX_COMPONENTS> components; - ComponentArray componentArray = {}; - ComponentBitSet componentBitSet; - GroupBitSet groupBitSet; + ComponentArray componentArray = {}; + ComponentBitSet componentBitSet; + GroupBitSet groupBitSet; }; \ No newline at end of file diff --git a/include/EventManager.h b/include/EventManager.h new file mode 100644 index 0000000..3a4f0b4 --- /dev/null +++ b/include/EventManager.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include +#include + +#include "SDL3/SDL_events.h" +#include "SDL3/SDL_init.h" + +typedef std::function EventListener; + +class EventManager { +public: + EventManager(); + + void registerListener(EventListener listener, std::initializer_list eventTypes); + SDL_AppResult handleEvent(SDL_Event* const event); +private: + std::map> eventListeners = std::map>(); +}; \ No newline at end of file diff --git a/include/Game.h b/include/Game.h index c1b1aa3..d4a8e8a 100644 --- a/include/Game.h +++ b/include/Game.h @@ -8,7 +8,17 @@ public: virtual ~Game() {} virtual void init() = 0; - virtual void update() = 0; + virtual void update(uint_fast16_t diffTime) = 0; + + /*! + * \brief Sets the path for a custom config file. + * + * Virtual function to be overwritten in the implementation to return the path of a custom config JSON file. + * \sa Layout of the config file is shown in ConfigLoader + * + * \return std::optional + */ + virtual std::optional setConfigFilePath() {return std::nullopt;} GameInternal* gameInternal; //!< \deprecated }; diff --git a/include/GameInternal.h b/include/GameInternal.h index 44cec2d..36b3194 100644 --- a/include/GameInternal.h +++ b/include/GameInternal.h @@ -1,19 +1,26 @@ #pragma once -#include -#include -#include +#include +#include +#include +#include #include #include +#include "EventManager.h" +#include "InteractionManager.h" #include "Manager.h" +#include "SDL3/SDL_events.h" +#include "SDL3/SDL_init.h" #include "Vector2D.h" #include "Entity.h" +#include "InputManager.h" #include "RenderManager.h" +#include "ConfigLoader.h" +#include "PickupManager.h" typedef std::function gamefunction; -class AssetManager; class CollisionHandler; class TextureManager; class SoundManager; @@ -23,44 +30,51 @@ class Game; class GameInternal { public: - GameInternal(); - ~GameInternal(); + GameInternal(); + ~GameInternal(); - void init(const char* title, int xpos, int ypos, int width, int height, bool fullscreen); + SDL_AppResult init(); - void handleEvents(); - void update(); - void render(); - void clean(); - bool isRunning() const; - void setRunning(bool running); // TODO: should be private/not accesible for game dev - void stopGame(); + SDL_AppResult handleEvent(SDL_Event* event); + void update(Uint64 frameTime); + void render(); + void clean(); + bool isRunning() const; + void setRunning(bool running); // TODO: should be private/not accesible for game dev + void stopGame(); /* static */ SDL_Renderer* renderer = nullptr; /* static */ SDL_Event event; /* static */ CollisionHandler* collisionHandler; - /* static */ AssetManager* assets; + /* static */ PickupManager* pickupManager; /* static */ TextureManager* textureManager; /* static */ SoundManager* soundManager; + /* static */ InputManager* inputManager; + RenderManager* renderManager; + EventManager* eventManager; + InteractionManager* interactionManager; Manager manager; - RenderManager renderManager; Map* map; // game specific, might not be needed for all types of games - std::vector& tiles; - std::vector& players; - std::vector& projectiles; - std::vector& hearts; - std::vector& powerups; - // end moved globals + ConfigLoader* config; + + std::vector& tiles; + std::vector& players; + std::vector& projectiles; + std::vector& hearts; + std::vector& powerups; + // end moved globals void refreshPlayers(); private: - Game* gameInstance; + Game* gameInstance; - int counter = 0; - bool running = true; - SDL_Window* window; + int counter = 0; + bool running = true; + SDL_Window* window; + + Uint64 lastFrameTime = 0; }; diff --git a/include/InputComponent.h b/include/InputComponent.h index 9382cf1..31cd381 100644 --- a/include/InputComponent.h +++ b/include/InputComponent.h @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include "Component.h" @@ -94,12 +94,12 @@ public: ~InputComponent(); void init() override; - void update() override; + void update(uint_fast16_t diffTime) override; bool isKeyDown(Key key); private: - const Uint8* m_keyStates; + const bool* m_keyStates; SDL_Scancode mapKeyToSDL(Key key); std::map m_keyMappings; void InitKeyMappings(); diff --git a/include/InputManager.h b/include/InputManager.h new file mode 100644 index 0000000..8c3b144 --- /dev/null +++ b/include/InputManager.h @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +class InputManager { +public: + enum class EventType { + KeyDown, + KeyUp + }; + + enum class Key + { + UP, + DOWN, + LEFT, + RIGHT, + SPACE, + ENTER, + ESCAPE, + TAB, + BACKSPACE, + DELETE, + HOME, + END, + PAGE_UP, + PAGE_DOWN, + INSERT, + CAPS_LOCK, + LEFT_SHIFT, + RIGHT_SHIFT, + LEFT_CTRL, + RIGHT_CTRL, + LEFT_ALT, + RIGHT_ALT, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + NUM_0, + NUM_1, + NUM_2, + NUM_3, + NUM_4, + NUM_5, + NUM_6, + NUM_7, + NUM_8, + NUM_9, + LEFT_BRACKET, + RIGHT_BRACKET, + SEMICOLON, + APOSTROPHE, + COMMA, + PERIOD, + SLASH, + BACKSLASH, + GRAVE + }; + + struct InputAction { + std::string name; + std::vector bindings; + std::function callback; + }; + + InputManager(); + ~InputManager(); + + void init(); // see if necessary + void processEvents(); + void registerAction(const std::string& actionName, const std::vector& keys, std::function callback, const std::string& context); + + void setActiveContext(const std::string& context); + std::string getActiveContext() const; + + //void rebindAction(const std::string& actionName, const std::vector& newBindings, const std::string& context); + //void removeBindings(const std::string& actionName, const std::string& context); + //std::vector getBindings(const std::string& actionName, const std::string& context) const; + std::vector getActionsByKey(const Key key) const; + + SDL_AppResult handleEvent(SDL_EventType type, SDL_Event* const event); + + void initKeyMap(); +private: + // TODO: flesh this out to avoid loops in process actions + // additionally to actionsByContext, not instead + std::map>> actionsByContextAndKey; + + std::map keyMap; + std::string activeContext; +}; + +std::ostream& operator<<(std::ostream& os, InputManager::Key key); +std::ostream& operator<<(std::ostream& os, const InputManager::InputAction& action); +std::ostream& operator<<(std::ostream& os, const InputManager::InputAction* action); +std::ostream& operator<<(std::ostream& os, const std::vector& actions); +std::ostream& operator<<(std::ostream& os, const std::vector& actions); diff --git a/include/InteractionComponent.h b/include/InteractionComponent.h new file mode 100644 index 0000000..d016a6f --- /dev/null +++ b/include/InteractionComponent.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Component.h" +#include "InteractionListener.h" + +#include + +class InteractionComponent : public Component, public InteractionListener +{ +public: + /** + * @brief Constructor for the InteractionComponent. + * @param callback A function to be called when an interaction event is triggered. void* actor, void* data are passed to the callback function from InteractionEventdataStruct. + */ + InteractionComponent(std::function callback); + + /** + * @brief Internal function to be called when an interaction event is triggered. + */ + void interact(void* actor, void* data) override; + + /** + * @brief Internal function to use as reference for targeting. + */ + std::shared_ptr getPosition() override; +private: + std::function interactionCallback; +}; \ No newline at end of file diff --git a/include/InteractionEventdataStruct.h b/include/InteractionEventdataStruct.h new file mode 100644 index 0000000..759bbb8 --- /dev/null +++ b/include/InteractionEventdataStruct.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Entity.h" +#include "InteractionListener.h" +#include "InteractionManager.h" +#include "Vector2D.h" +#include +#include + +/** + * @brief Struct to hold data for interaction events. + * This struct is used to pass data to the interaction manager when an interaction event is triggered. + */ +struct InteractionEventdataStruct { + /// Arbitray data to pass to the interaction listener. Can for example be an Entity ptr to represent the actor. + void* actor; + /// The data to pass to the interaction listener. Can be any type of pointer. + void* data; + /// The target of the interaction, e.g. InteractionComponent of an Entity. Is required if strategy is set to 0 (none) + std::weak_ptr target = std::weak_ptr(); + /// Coordinates from which to base targeting on. Is required if strategy is not set to 0 (none) + std::shared_ptr targetingReference = nullptr; + /// required without explicit target, defaults to none + /// @sa InteractionManager::TargetingStrategy + uint8_t strategy = 0; // int since enum would be impossibling user defined targetingStrategies + + void triggerEvent(); +}; \ No newline at end of file diff --git a/include/InteractionListener.h b/include/InteractionListener.h new file mode 100644 index 0000000..06216db --- /dev/null +++ b/include/InteractionListener.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Vector2D.h" +#include + +class InteractionListener { +public: + InteractionListener() { }; + virtual ~InteractionListener() { }; + + virtual void interact(void* actor, void* data) = 0; + virtual std::shared_ptr getPosition() // required for targeting strategy, return null to only allow explicit targeting + { + return nullptr; + } + +}; \ No newline at end of file diff --git a/include/InteractionManager.h b/include/InteractionManager.h new file mode 100644 index 0000000..f465fcb --- /dev/null +++ b/include/InteractionManager.h @@ -0,0 +1,35 @@ +#pragma once + +#include "InteractionListener.h" + +#include "SDL3/SDL_events.h" +#include "SDL3/SDL_init.h" +#include +#include +#include +#include +#include + +// TODO: ranges concept to avoid to in cpp +typedef std::function(Vector2D*, std::vector>)> TargetingFunc; + +class InteractionManager { +public: + InteractionManager(); + InteractionManager (const InteractionManager&) = delete; + InteractionManager& operator= (const InteractionManager&) = delete; + + enum class TargetingStrategy : uint8_t { + none = 0, + closest, + manhattenDistance + }; + + SDL_AppResult handleInteract(SDL_EventType type, SDL_Event* const event); + void registerListener(std::weak_ptr listener); + uint8_t registerTargetingFunc(TargetingFunc func); +private: + + std::vector> listeners; + std::array targetingFuncs; +}; \ No newline at end of file diff --git a/include/Manager.h b/include/Manager.h index 8b9ceb3..a796e42 100644 --- a/include/Manager.h +++ b/include/Manager.h @@ -24,7 +24,7 @@ class Manager public: Manager(GameInternal* game) : game(game) {}; - void update(); //!< \sa Entity::update() + void update(uint_fast16_t diffTime); //!< \sa Entity::update() //! Disables all functionality of entities marked for destruction //! \sa Entity::destroy() void refresh(); diff --git a/include/Map.h b/include/Map.h index 92b8179..a5b7ac7 100644 --- a/include/Map.h +++ b/include/Map.h @@ -1,36 +1,52 @@ #pragma once -#include -#include +#include +#include #include +#include + +#include +#include +#include +#include class GameInternal; class Map { public: - Map() = default; - ~Map() = default; - - /*! - * - * \brief - * This loads a map - * - * \param path The path to the map file - * \return Boolean for success - * - */ - [[deprecated("ID based text files are not supported anymore, use .txm maps instead")]] - static void loadMap(const char* path, int sizeX, int sizeY, GameInternal* game, const std::map>* textureDict /* backreference */); - [[deprecated]] - static void addTile(unsigned long id, int x, int y, GameInternal* game, const std::map>* textureDict); - /*! * \brief Loads a .tmx map + * \details Loads a `.tmx` file and extracts all relevant data. Any entities (including tiles) are only spawned once * \param path Path to the `.tmx` map file - * + * \sa Map::generateTiles() */ - static void loadMapTmx(const char* path); + Map(const char* path); + void generateTiles(); //!< Generates the map based on the loaded definition private: - static void addTile(float x, float y, const tmx::Vector2u& mapTileSize, int u, int v, int zIndex, const char* texturePath); -}; + // struct required for initialisation + struct MapData { + const std::vector* tileSets; + const std::vector* mapLayers; + const tmx::Vector2u* mapSize; + const tmx::Vector2u* mapTileSize; + const std::vector* texturePaths; + }; + + struct TileSetData { + std::string texturePath{}; + tmx::Vector2f textureSize; + uint32_t tileCount{}; + tmx::Vector2u tileCount2D; + uint32_t firstGID{}; + }; + + tmx::Map map; + Map::MapData mapData; + std::vector> tileConstructors; + + void loadTileLayer(const tmx::TileLayer& layer); + static void addTile(float x, float y, const tmx::Vector2u& mapTileSize, int u, int v, int zIndex, std::string texturePath, bool hasCollision); + + template + static std::optional getLayerProperty(const std::vector& properties, std::string propertyName) { return std::nullopt; }; +}; \ No newline at end of file diff --git a/include/PickupComponent.h b/include/PickupComponent.h new file mode 100644 index 0000000..754895d --- /dev/null +++ b/include/PickupComponent.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include "Component.h" + +class PickupComponent : public Component +{ +public: + /** + * @brief Construct a new Powerup Component object + * @param func The function to be called when the powerup is picked up + */ + PickupComponent(std::function func); + ~PickupComponent() {}; + + void update(uint_fast16_t diffTime) override; + +private: + std::function pickupFunc; +}; \ No newline at end of file diff --git a/include/PickupManager.h b/include/PickupManager.h new file mode 100644 index 0000000..c73af76 --- /dev/null +++ b/include/PickupManager.h @@ -0,0 +1,28 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "Entity.h" +#include "SoundEffects.h" + +class Vector2D; +class Manager; + +class PickupManager +{ +public: + + PickupManager(Manager* manager); + ~PickupManager(); + + void createPowerup(Vector2D pos, std::function pickupFunc, Textures texture); + + Vector2D calculateSpawnPosition(); + +private: + + Manager* man; +}; diff --git a/include/PowerupComponent.h b/include/PowerupComponent.h deleted file mode 100644 index 53f365b..0000000 --- a/include/PowerupComponent.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include "Component.h" - -class PowerupComponent : public Component -{ -public: - PowerupComponent(std::function func); - ~PowerupComponent() {}; - - void update() override; - -private: - std::function pickupFunc; -}; \ No newline at end of file diff --git a/include/ProjectileComponent.h b/include/ProjectileComponent.h index 4db0bfd..d9f5071 100644 --- a/include/ProjectileComponent.h +++ b/include/ProjectileComponent.h @@ -3,6 +3,7 @@ #include "Component.h" #include "Vector2D.h" #include "Constants.h" +#include "SoundEffects.h" class TransformComponent; @@ -11,21 +12,23 @@ class ProjectileComponent : public Component //can maybe be split in separate .cpp file public: - ProjectileComponent(int range, int speed, Vector2D direction, Entity* owner) - : range(range), speed(speed), direction(direction), owner(owner) {} + ProjectileComponent(int range, int speed, Vector2D direction, Entity* owner, SoundEffects soundEffect) + : range(range), speed(speed), direction(direction), owner(owner), soundEffect(soundEffect) {} ~ProjectileComponent() {} void init() override; - void update() override; + void update(uint_fast16_t diffTime) override; private: TransformComponent* transformComponent; int range = 0; - int speed = 0; - int distance = 0; + float speed = 0; + float distance = 0; Entity* owner = nullptr; Vector2D direction; + + SoundEffects soundEffect; }; \ No newline at end of file diff --git a/include/RenderObject.h b/include/RenderObject.h index ba4b929..9b3e881 100644 --- a/include/RenderObject.h +++ b/include/RenderObject.h @@ -7,7 +7,7 @@ class RenderObject public: virtual void draw() = 0; - RenderObject(int zIndex, RenderManager& renderManager); + RenderObject(int zIndex, RenderManager* renderManager); ~RenderObject(); int getZIndex() { return this->zIndex; }; @@ -23,5 +23,5 @@ private: int zIndex = 0; protected: - RenderManager& renderManager; + RenderManager* renderManager; }; \ No newline at end of file diff --git a/include/SoundEffects.h b/include/SoundEffects.h new file mode 100644 index 0000000..609b9a5 --- /dev/null +++ b/include/SoundEffects.h @@ -0,0 +1,3 @@ +#pragma once + +enum class SoundEffects; diff --git a/include/SoundManager.h b/include/SoundManager.h index 8663d34..22d1c71 100644 --- a/include/SoundManager.h +++ b/include/SoundManager.h @@ -1,11 +1,13 @@ #pragma once -#include +#include #include #include #include "ECS.h" #include "TextureManager.h" +#include "BackgroundMusic.h" +#include "SoundEffects.h" class GameInternal; @@ -17,8 +19,17 @@ class GameInternal; */ class SoundManager { + public: - SoundManager() {} + + SoundManager() { + if (this_instance == nullptr) { + this_instance = this; + } + else { + throw std::runtime_error("SoundManager instance already exists!"); + } + } ~SoundManager() { for (auto& it : this->sound_cache) { Mix_FreeChunk(it.second); @@ -32,35 +43,32 @@ class SoundManager SoundManager(SoundManager const&) = delete; void operator=(SoundManager const&) = delete; - std::map music_cache; - std::map sound_cache; - - /*! + /* * \brief Loads music from a file (mp3) * \returns a pointer to Mix_Music * \sa AssetManager::AddMusic(std::string id, const char* path) - */ + Mix_Music* loadMusic(const char* fileName); /*! * \brief Loads sound effects from a file (wav) * \returns a pointer to Mix_Chunk * \sa AssetManager::AddSound(std::string id, const char* path) - */ - Mix_Chunk* loadSound(const char* fileName); + Mix_Chunk* loadSound(const char* fileName); + */ /*! * \brief Handles playing of sound effects * * Handles if sounds can overlap, how often they can loop, as well as the volume at which the specified sound effect should play * and on which channel the soundeffect should play. */ - static void playSound(GameInternal* game, std::string sound, bool canOverlap, int loops, int volume, int channel); + static void playSound(SoundEffects sound, bool canOverlap, int loops, int volume, int channel); /*! * \brief Handles playing of music * * Handles how often track can loop, as well as the volume at which the specified track should play and if it fades in. */ - static void playMusic(GameInternal* game, std::string sound, int loops, int volume, int ms); + static void playMusic(BackgroundMusic sound, int loops, int volume, int milliseconds); static void setSoundVolume(int volume, int channel); //!< Volume handling for sound effects (either all or on a specific channel) static void setMusicVolume(int volume); //!< Volume handling for music track @@ -73,5 +81,29 @@ class SoundManager static void fadeOutMusic(int ms); //!< Handles fading out a music track + /*! + * \brief Initializes sound-effects and adds them to a cache + * + */ + static void addSoundEffects(const std::map &effects); + + /*! + * \brief Initializes background-music and adds them to a cache + * + */ + static void addBackgroundMusic(const std::map &backgroundMusic); + + static SoundManager* getInstance() { + return this_instance; + } + + private: + + std::map music_cache; + std::map sound_cache; + static SoundManager* this_instance; + + static void addSingleBackgroundMusic(BackgroundMusic backgroundMusic, const char* path); + static void addSingleSoundEffect(SoundEffects soundEffect, const char* path); }; \ No newline at end of file diff --git a/include/SpriteComponent.h b/include/SpriteComponent.h index 0676549..247a063 100644 --- a/include/SpriteComponent.h +++ b/include/SpriteComponent.h @@ -1,10 +1,11 @@ #pragma once #include -#include +#include #include #include +#include "Textures.h" #include "AnimationHandler.h" #include "Component.h" #include "Direction.h" @@ -22,9 +23,9 @@ public: private: TransformComponent* transform; SDL_Texture* texture; - SDL_Rect srcRect, destRect; + SDL_FRect srcRect, destRect; - const char* texturePath; + Textures textureEnum; bool animated = false; uint8_t frames = 0; @@ -34,21 +35,32 @@ private: int textureXOffset; int textureYOffset; + //there should be a better solution as this variable is only used for the loading of the tmx map + //TODO: improve this in the future and also remove it from the scope of the developer + const char* path; //!< empty string if texture has a texture enum value, otherwise the path of the texture + public: - SpriteComponent(const char* path, int zIndex); + //debug + Textures getTexture() { return this->textureEnum; } + + + + SpriteComponent(Textures texture, int zIndex); + SpriteComponent(Textures texture, int xOffset, int yOffset, int zIndex); SpriteComponent(const char* path, int xOffset, int yOffset, int zIndex); SpriteComponent( - const char* path, + Textures texture, bool isAnimated, std::map>* animationList, std::string defaultAnimation, int zIndex); ~SpriteComponent(); - void setTexture(const char* path); + void setTexture(Textures texture); + void setMapTileTexture(const char* path); void init() override; - void update() override; + void update(uint_fast16_t diffTime) override; void draw() override; void playAnimation(std::string type); void setDirection(Direction direction); diff --git a/include/StatEffectsComponent.h b/include/StatEffectsComponent.h index bc93f2b..698fc6d 100644 --- a/include/StatEffectsComponent.h +++ b/include/StatEffectsComponent.h @@ -3,11 +3,15 @@ #include "Constants.h" #include #include +#include -enum class Stats -{ - MOVEMENT_SPEED, - ATTACK_SPEED +/** + * @brief Struct to hold the duration, reset function and start time of a stat effect + */ +struct StatEffect { + uint32_t duration; //!< Duration of the effect in milliseconds + std::function resetFunction; //!< Function to reset the effect, will be called on expiry of duration + uint32_t startTime; }; class StatEffectsComponent : public Component{ @@ -16,13 +20,14 @@ public: ~StatEffectsComponent() {}; void init() override; - void update() override; - - void modifyStatDur(Stats stat, int duration, int value); - - void modifyStatValue(Stats stat, int modifier); - void resetStatValue(Stats stat); + void update(uint_fast16_t diffTime) override; + /** + * @brief Add a stat effect to the entity + * @param duration The duration of the effect in milliseconds + * @param resetFunction The function to reset the effect, will be called on expiry of duration + */ + void addEffect(uint32_t duration, std::function resetFunction); private: - std::array buffs = { 0 }; + std::vector effects = {}; }; \ No newline at end of file diff --git a/include/TextureManager.h b/include/TextureManager.h index 3e4f1c4..a7f1e29 100644 --- a/include/TextureManager.h +++ b/include/TextureManager.h @@ -1,11 +1,25 @@ #pragma once #include "ECS.h" -#include +#include "SDL3/SDL_surface.h" +#include #include #include #include #include +#include "Textures.h" + +/*! + * \class TextureManager + * \brief A manager for loading, caching, and drawing textures. + * + * The `TextureManager` class is responsible for handling texture loading, caching, + * and rendering in the engine. It provides functions to add, load, and draw textures + * from files, as well as manage sprite sheets. + * + * \sa Textures are used to identify textures within the engine. + * It is expected that they are implemented within the games scope. + */ class TextureManager { @@ -15,13 +29,69 @@ class TextureManager for (auto& it : this->texture_cache) { SDL_DestroyTexture(it.second); } + for (auto& it : this->mapTile_texture_cache) { + SDL_DestroyTexture(it.second); + } } - std::map texture_cache; + /*! + * \brief Adds a single texture to the cache. + * \param texture The texture identifier. + * \param filePath The file path to the texture file. + * \throws std::runtime_error Is thrown if the texture could not be loaded correctly + * + * This function loads the texture from the specified file and stores it in + * a cache. + */ + void addSingleTexture(Textures texture, const char* filePath); - SDL_Texture* loadTexture(const char* fileName); + /*! + * \brief Adds multiple textures to the cache. + * \param textures A map of texture identifiers and corresponding file paths. + * + * This function iterates over the provided map of textures and loads each + * texture using `addSingleTexture`. It allows for several + * textures to be added at once. + */ + void addTextures(const std::map& textures); + + /*! + * \brief Loads a texture from the cache. + * \param texture The texture identifier. + * \return A pointer to the `SDL_Texture` if found, or `nullptr` if not found. + * + * This function looks up a texture within the cache and returns the + * corresponding `SDL_Texture*`. If the texture is not found, it logs an error + * message and returns `nullptr`. + */ + SDL_Texture* loadTexture(Textures texture); static std::vector splitSpriteSheet(SDL_Texture* spriteSheet, int width, int height, int spritesOnSheet); - static void draw(SDL_Renderer* renderer, SDL_Texture* texture, SDL_Rect src, SDL_Rect dest, bool flipped = false); + static void draw(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect src, SDL_FRect dest, bool flipped = false); + + void setScaleMode(SDL_ScaleMode scaleMode) { this->scaleMode = scaleMode; } + + /*! + * \brief Loads a map tile texture from the file system and caches it. + * \param path The file path to the texture. + * \return `SDL_Texture*` representing the map tile. + * \throws std::runtime_error Is thrown if the texture could not be loaded correctly + * + * This function checks if the map tile texture is already cached. If not, it + * loads the texture from the file system and stores it in the cache. + * + * \todo should not be usable for the developer and only be accessed by the map class + */ + SDL_Texture* loadMapTileTexture(const char* path); + + std::string getTexturePath(Textures texture) { + return this->texture_references.at(texture); + } + private: + SDL_ScaleMode scaleMode = SDL_SCALEMODE_NEAREST; Manager* manager; + std::map texture_cache; + std::map mapTile_texture_cache; + + std::map texture_references; }; \ No newline at end of file diff --git a/include/Textures.h b/include/Textures.h new file mode 100644 index 0000000..eb63dfd --- /dev/null +++ b/include/Textures.h @@ -0,0 +1,14 @@ +#pragma once + +/*! + * \class Textures + * \brief Forward declaration of the \c Textures enum class. + * + * The \c Textures enum class is intended to be implemented within the game scope. + * This allows for customized texture entries to be defined based on the specific needs of the project. + * The base declaration ensures that the enum class can be referenced and used consistently throughout + * the engine while leaving the details of the texture identifiers up to the implementation. + * \sa \ref TextureManager "TextureManager" for how the enum is used. + */ + +enum class Textures; \ No newline at end of file diff --git a/include/TileComponent.h b/include/TileComponent.h index 8844daa..b877bf1 100644 --- a/include/TileComponent.h +++ b/include/TileComponent.h @@ -1,10 +1,11 @@ #pragma once -#include +#include #include #include #include "Component.h" +#include "Textures.h" class SpriteComponent; class TransformComponent; @@ -17,17 +18,19 @@ public: SDL_Rect tileRect; int tileID; - const char* path; + Textures texture; TileComponent() = default; - TileComponent(int x, int y, int w, int h, int id, const std::map>* textureDict); + TileComponent(int x, int y, int w, int h, int id, const std::map>* textureDict); ~TileComponent() = default; void init() override; - bool hasCollision(){return this->collision;} - std::string getName(){return this->tileName;} + bool hasCollision() { + return this->collision; + } + + private: bool collision; - std::string tileName; }; \ No newline at end of file diff --git a/include/TransformComponent.h b/include/TransformComponent.h index 7a15aa4..886e314 100644 --- a/include/TransformComponent.h +++ b/include/TransformComponent.h @@ -3,6 +3,7 @@ #include "Component.h" #include "Vector2D.h" #include "Constants.h" +#include "DataComponent.h" class TransformComponent : public Component { @@ -14,22 +15,14 @@ public: int width = 32; int scale = 1; - int getSpeed() { return speed + speedMod; }; - void resetSpeedMod() { speedMod = 0; }; - - TransformComponent(); - explicit TransformComponent(int scale); - TransformComponent(float x, float y); - TransformComponent(float x, float y, int scale); - TransformComponent(float x, float y, int w, int h, int scale); + explicit TransformComponent(int scale = 1); + TransformComponent(float x, float y, int scale = 1); + TransformComponent(float x, float y, int w, int h, int scale = 1); void init() override; /*! TODO: document usage of collision handler */ - void update() override; + void update(uint_fast16_t diffTime) override; void setPositionAfterCollision(Vector2D& positionChange); - void modifySpeed(int8_t modifier); + int getSpeed(); -private: - int speed = 3; - int speedMod = 0; }; diff --git a/include/VEGO_Event.h b/include/VEGO_Event.h new file mode 100644 index 0000000..a90d46a --- /dev/null +++ b/include/VEGO_Event.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace vego { + extern Uint32 VEGO_Event_Interaction; +} \ No newline at end of file diff --git a/include/Vector2D.h b/include/Vector2D.h index bc2d652..9b38e18 100644 --- a/include/Vector2D.h +++ b/include/Vector2D.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include class Vector2D { diff --git a/internalAssets/iconImage.bmp b/internalAssets/iconImage.bmp new file mode 100644 index 0000000..6eddbef Binary files /dev/null and b/internalAssets/iconImage.bmp differ diff --git a/src/AssetManager.cpp b/src/AssetManager.cpp deleted file mode 100644 index 3841cf3..0000000 --- a/src/AssetManager.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "AssetManager.h" - -#include "TextureManager.h" -#include "SoundManager.h" -#include "ProjectileComponent.h" -#include "GameInternal.h" - -#include "TransformComponent.h" - -#include "CollisionHandler.h" -#include "ColliderComponent.h" -#include "Constants.h" -#include "Entity.h" -#include "Vector2D.h" -#include "PowerupComponent.h" -#include -#include - -AssetManager::AssetManager(Manager* manager) : man(manager) {} - -AssetManager::~AssetManager() {} - -void AssetManager::addTexture(std::string id, const char* path) { - textures.emplace(id, this->man->getGame()->textureManager->loadTexture(path)); -} - -void AssetManager::addSoundEffect(std::string id, const char* path) -{ - soundEffects.emplace(id, this->man->getGame()->soundManager->loadSound(path)); -} - -void AssetManager::addMusic(std::string id, const char* path) -{ - music.emplace(id, this->man->getGame()->soundManager->loadMusic(path)); -} - -SDL_Texture* AssetManager::getTexture(std::string id) { - return textures.at(id); -} - -Mix_Chunk* AssetManager::getSound(std::string id) { - return soundEffects.at(id); -} - -Mix_Music* AssetManager::getMusic(std::string id) -{ - return music.at(id); -} - -void AssetManager::createProjectile(Vector2D pos, Vector2D velocity, int scale, int range, int speed, const char* texturePath, Entity* owner) { - - auto& projectile(man->addEntity()); - projectile.addComponent(pos.x, pos.y, 32, 32, scale); //32x32 is standard size for objects - projectile.addComponent(texturePath, 4); - projectile.addComponent(range, speed, velocity, owner); - projectile.addComponent("projectile", 0.6f); - projectile.addGroup((size_t)Entity::GroupLabel::PROJECTILE); -} - -void AssetManager::createPowerup(Vector2D pos, std::function pickupFunc, std::string texturePath) { - - auto& powerups(man->addEntity()); - powerups.addComponent(pos.x, pos.y, 32, 32, 1); //32x32 is standard size for objects - - try { - powerups.addComponent(texturePath.c_str(), 3); - } - catch (std::runtime_error e) { - std::cout << e.what() << std::endl; - } - - powerups.addComponent("powerup", 0.6f); - powerups.addComponent(pickupFunc); - powerups.addGroup((size_t)Entity::GroupLabel::POWERUPS); -} - - -Vector2D AssetManager::calculateSpawnPosition(Vector2D size, Vector2D spawnArea) -{ - Vector2D spawnPos = Vector2D(-1, -1); - - for(int i = 0; i <= SPAWN_ATTEMPTS; i++) - { - - SDL_Rect spawnRect = { - rand() % (int)(spawnArea.x - size.x), - rand() % (int)(spawnArea.y - size.y), - size.x, - size.y - }; - - std::vector colliders = this->man->getGame()->collisionHandler->getColliders({Entity::GroupLabel::MAPTILES}); - bool conflict = std::any_of(colliders.begin(), colliders.end(), - [&](const auto& cc) { - return SDL_HasIntersection(&spawnRect, &cc->collider);} ); - - if(!conflict) - { - spawnPos = Vector2D(spawnRect.x, spawnRect.y); - break; - } - } - - return spawnPos; -} - -template -T AssetManager::calculateRandomType(int amount) -{ - T type = T(rand() % amount); - return type; -} diff --git a/src/ColliderComponent.cpp b/src/ColliderComponent.cpp index 2275163..8cb2ad6 100644 --- a/src/ColliderComponent.cpp +++ b/src/ColliderComponent.cpp @@ -31,10 +31,10 @@ void ColliderComponent::init() } transform = &entity->getComponent(); - this->update(); + this->update(0); } -void ColliderComponent::update() +void ColliderComponent::update(uint_fast16_t diffTime) { collider.x = transform->position.x - (transform->width - transform->width * transform->scale * this->hitboxScale) / 2; collider.y = transform->position.y - (transform->width - transform->width * transform->scale * this->hitboxScale) / 2; diff --git a/src/CollisionHandler.cpp b/src/CollisionHandler.cpp index 5477c6c..d153d0c 100644 --- a/src/CollisionHandler.cpp +++ b/src/CollisionHandler.cpp @@ -6,10 +6,11 @@ #include "Manager.h" #include "Vector2D.h" -#include +#include #include #include #include +#include IntersectionBitSet CollisionHandler::getIntersection(Entity* entityA, Entity* entityB, Vector2D posModA, Vector2D posModB) { @@ -26,7 +27,7 @@ IntersectionBitSet CollisionHandler::getIntersection(Entity* entityA, Entity* en colliderB.x += posModB.x; colliderB.y += posModB.y; - if (!SDL_HasIntersection( + if (!SDL_HasRectIntersection( &colliderA, &colliderB)) return std::bitset(); @@ -66,20 +67,20 @@ IntersectionBitSet CollisionHandler::getIntersectionWithBounds(Entity* entity, V // all 4 directions and both sides to allow checking for fully out of bounds if (collider->x + posMod.x < 0 || - collider->x + posMod.x > SCREEN_SIZE_WIDTH) { + collider->x + posMod.x > VEGO_Game().config->getFinalConfig().at("screen_width")) { intersections.set((size_t) Direction::LEFT); } if (collider->x + collider->w + posMod.x < 0 || - collider->x + collider->w + posMod.x > SCREEN_SIZE_WIDTH) + collider->x + collider->w + posMod.x > VEGO_Game().config->getFinalConfig().at("screen_width")) intersections.set((size_t) Direction::RIGHT); if (collider->y + posMod.y < 0 || - collider->y + posMod.y > SCREEN_SIZE_HEIGHT) + collider->y + posMod.y > VEGO_Game().config->getFinalConfig().at("screen_height")) intersections.set((size_t) Direction::UP); if (collider->y + collider->h + posMod.y < 0 || - collider->y + collider->h + posMod.y > SCREEN_SIZE_HEIGHT) + collider->y + collider->h + posMod.y > VEGO_Game().config->getFinalConfig().at("screen_height")) intersections.set((size_t) Direction::DOWN); return intersections; @@ -152,7 +153,7 @@ Entity* CollisionHandler::getAnyIntersection( if (!entity->hasComponent()) return nullptr; for (auto& collider : getColliders(groupLabels, excludedEntities)) { SDL_Rect rect = entity->getComponent().collider + posMod; - if (SDL_HasIntersection(&rect, &collider->collider)) { + if (SDL_HasRectIntersection(&rect, &collider->collider)) { return collider->entity; } } @@ -175,7 +176,7 @@ bool CollisionHandler::getAnyIntersection( if (!entity->hasComponent()) return false; for (auto& collider : getColliders(groupLabels, excludedEntities)) { SDL_Rect rect = entity->getComponent().collider + posMod; - if (SDL_HasIntersection(&rect, &collider->collider)) { + if (SDL_HasRectIntersection(&rect, &collider->collider)) { return true; } } diff --git a/src/ConfigLoader.cpp b/src/ConfigLoader.cpp new file mode 100644 index 0000000..9e9911c --- /dev/null +++ b/src/ConfigLoader.cpp @@ -0,0 +1,51 @@ +#include "ConfigLoader.h" + +#include + +ConfigLoader::ConfigLoader() {} + +ConfigLoader::~ConfigLoader() {} + +void ConfigLoader::init() { + //TODO: look into adaptive paths for better handling as this requires the implemented game + // to have ./engine in the root folder (very low prio) + const json baseConfig = loadConfigFromJSON("./engine/config.json"); + + if (!customConfigPath.has_value()) { + finalConfig = baseConfig; + return; + } + + finalConfig = mergeConfigs(baseConfig, loadConfigFromJSON(customConfigPath.value())); +} + +json ConfigLoader::loadConfigFromJSON(const std::string& path) { + std::ifstream config_file(path); + json config; + + if (!config_file.is_open()) { + throw std::runtime_error(std::string("Could not load config file at: " + path)); + } + + config_file >> config; + return config; +} + + +void ConfigLoader::setCustomConfig(const std::optional& path) { + customConfigPath = path; +} + +json ConfigLoader::mergeConfigs(json baseConfig, json customConfig) { + for (auto& entry : customConfig.items()) { + baseConfig[entry.key()] = entry.value(); + } + return baseConfig; +} + +json ConfigLoader::getFinalConfig() { + return finalConfig; +} + + + diff --git a/src/Entity.cpp b/src/Entity.cpp index 60455c1..8a4a0e9 100644 --- a/src/Entity.cpp +++ b/src/Entity.cpp @@ -4,9 +4,11 @@ #include "Component.h" #include -void Entity::update() const +void Entity::update(uint_fast16_t diffTime) const { - for (auto const& c : components) c->update(); + for (auto const& c : components) + if (c) + c->update(diffTime); } bool Entity::hasGroup(Group mGroup) diff --git a/src/EventManager.cpp b/src/EventManager.cpp new file mode 100644 index 0000000..717f072 --- /dev/null +++ b/src/EventManager.cpp @@ -0,0 +1,44 @@ +#include "EventManager.h" +#include "SDL3/SDL_events.h" +#include "SDL3/SDL_init.h" +#include "SDL3/SDL_stdinc.h" +#include "VEGO_Event.h" +#include +#include +#include +#include + +Uint32 vego::VEGO_Event_Interaction; + +EventManager::EventManager() +{ + /// \TODO: from c++26 you (should be able to) can get the amount of name values in an enum + vego::VEGO_Event_Interaction = SDL_RegisterEvents(1); // TODO: error handling +} + + +void EventManager::registerListener(EventListener listener, std::initializer_list eventTypes) +{ + std::ranges::for_each(eventTypes.begin(), eventTypes.end(), [this, &listener](const Uint32& eventType) { + if (!this->eventListeners.contains(eventType)) { + this->eventListeners.insert({eventType, std::vector()}); + } + this->eventListeners.at(eventType).emplace_back(listener); + }); +} + +SDL_AppResult EventManager::handleEvent(SDL_Event* event) +{ + SDL_EventType type = (SDL_EventType) event->type; + if (this->eventListeners.contains(type)) { + std::vector results; + for (auto& listener : this->eventListeners.at(type)) { + results.emplace_back(listener((SDL_EventType) event->type, event)); + } + if (std::ranges::contains(results, SDL_APP_FAILURE)) + return SDL_APP_FAILURE; + if (std::ranges::contains(results, SDL_APP_SUCCESS)) + return SDL_APP_SUCCESS; + } + return SDL_APP_CONTINUE; +} \ No newline at end of file diff --git a/src/GameInternal.cpp b/src/GameInternal.cpp index 7f1ac27..e0b8058 100644 --- a/src/GameInternal.cpp +++ b/src/GameInternal.cpp @@ -1,159 +1,182 @@ #include "GameInternal.h" -#include - #include "CollisionHandler.h" -#include "AssetManager.h" +#include "EventManager.h" +#include "InputManager.h" +#include "InteractionManager.h" #include "RenderManager.h" -#include "SDL_mixer.h" +#include +#include "SDL3/SDL_events.h" +#include "SDL3/SDL_init.h" +#include "SDL3/SDL_oldnames.h" #include "SoundManager.h" -#include "TileComponent.h" -#include "Direction.h" #include "Entity.h" #include "HealthComponent.h" #include "Map.h" #include "TextureManager.h" -#include "StatEffectsComponent.h" -#include "Constants.h" #include "Game.h" #include "GameFactory.h" +#include +#include +#include + +#include "ConfigLoader.h" + GameInternal::GameInternal() : - manager(this), - renderManager(), - tiles(manager.getGroup((size_t)Entity::GroupLabel::MAPTILES)), - players(manager.getGroup((size_t)Entity::GroupLabel::PLAYERS)), - projectiles(manager.getGroup((size_t)Entity::GroupLabel::PROJECTILE)), - hearts(manager.getGroup((size_t)Entity::GroupLabel::HEARTS)), - powerups(manager.getGroup((size_t)Entity::GroupLabel::POWERUPS)) + manager(this), + tiles(manager.getGroup((size_t)Entity::GroupLabel::MAPTILES)), + players(manager.getGroup((size_t)Entity::GroupLabel::PLAYERS)), + projectiles(manager.getGroup((size_t)Entity::GroupLabel::PROJECTILE)), + hearts(manager.getGroup((size_t)Entity::GroupLabel::HEARTS)), + powerups(manager.getGroup((size_t)Entity::GroupLabel::POWERUPS)) {}; GameInternal::~GameInternal() = default; -void GameInternal::init(const char* title, int xpos, int ypos, int width, int height, bool fullscreen) +SDL_AppResult GameInternal::init() { - GameInternal::assets = new AssetManager(&manager); - GameInternal::textureManager = new TextureManager(&manager); - GameInternal::soundManager = new SoundManager(); - GameInternal::collisionHandler = new CollisionHandler(manager); // why does this use a referrence, but AssetManager a pointer? - - int flags = 0; - if (fullscreen) - { - flags = SDL_WINDOW_FULLSCREEN; - } + config = new ConfigLoader(); - if (SDL_Init(SDL_INIT_EVERYTHING) != 0) - { - std::cout << "ERROR. Subsystem couldnt be initialized! " << SDL_GetError() << std::endl; - SDL_ClearError(); - return; - } + this->gameInstance = GameFactory::instance().create(this); + config->setCustomConfig(this->gameInstance->setConfigFilePath()); + config->init(); - if (Mix_Init(MIX_INIT_MP3) != MIX_INIT_MP3) { - std::cout << "ERROR. Subsystem couldnt be initialized!" << std::endl; - return; - } + json finalConfig = config->getFinalConfig(); + + GameInternal::pickupManager = new PickupManager(&manager); + GameInternal::textureManager = new TextureManager(&manager); + GameInternal::soundManager = new SoundManager(); + GameInternal::collisionHandler = new CollisionHandler(manager); // why does this use a referrence, but AssetManager a pointer? + GameInternal::inputManager = new InputManager(); + GameInternal::inputManager->initKeyMap(); - window = SDL_CreateWindow(title, xpos, ypos, width, height, flags); - if (!window) - { - std::cout << "ERROR: Window couldnt be created! " << SDL_GetError() << std::endl; - SDL_ClearError(); - return; - } + GameInternal::renderManager = new RenderManager(); + GameInternal::eventManager = new EventManager(); + GameInternal::interactionManager = new InteractionManager(); + + this->eventManager->registerListener(std::bind_front(&InputManager::handleEvent, this->inputManager), { SDL_EVENT_KEY_DOWN, SDL_EVENT_KEY_UP }); + this->eventManager->registerListener(std::bind_front(&InteractionManager::handleInteract, VEGO_Game().interactionManager), { vego::VEGO_Event_Interaction }); + + int flags = 0; + if (finalConfig.at("fullscreen")) + { + flags = SDL_WINDOW_FULLSCREEN; + } + + if (!SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO)) + { + std::cout << "ERROR. Subsystem couldnt be initialized! " << SDL_GetError() << std::endl; + SDL_ClearError(); + return SDL_APP_FAILURE; + } + + if (Mix_Init(MIX_INIT_MP3) != MIX_INIT_MP3) { + std::cout << "ERROR. Subsystem couldnt be initialized!" << std::endl; + return SDL_APP_FAILURE; + } + + window = SDL_CreateWindow(finalConfig.at("title").get().c_str(), + finalConfig.at("screen_width"), finalConfig.at("screen_height"), flags); + + if (!window) + { + std::cout << "ERROR: Window couldnt be created! " << SDL_GetError() << std::endl; + SDL_ClearError(); + return SDL_APP_FAILURE; + } // bad SDL_Surface* icon; - if((icon = SDL_LoadBMP("assets/iconImage.bmp"))) + if((icon = SDL_LoadBMP(finalConfig.at("icon").get().c_str()))) { - SDL_SetWindowIcon(window, icon); + SDL_SetWindowIcon(window, icon); } SDL_SetWindowIcon(window, icon); - renderer = SDL_CreateRenderer(window, -1, 0); - if (!renderer) - { - std::cout << "ERROR: Renderer couldnt be created! " << SDL_GetError() << std::endl; - SDL_ClearError(); - return; - } - SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + renderer = SDL_CreateRenderer(window, NULL); + if (!renderer) + { + std::cout << "ERROR: Renderer couldnt be created! " << SDL_GetError() << std::endl; + SDL_ClearError(); + return SDL_APP_FAILURE; + } + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); - if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) - { - std::cout << "ERROR: Mixer couldnt be initialized! " << SDL_GetError() << std::endl; - SDL_ClearError(); - return; - } + if (!Mix_OpenAudio(0, NULL)) + { + std::cout << "ERROR: Mixer couldnt be initialized! " << SDL_GetError() << std::endl; + SDL_ClearError(); + return SDL_APP_FAILURE; + } - Mix_Volume(-1, MIX_MAX_VOLUME); - Mix_AllocateChannels(16); + Mix_Volume(-1, MIX_MAX_VOLUME); + Mix_AllocateChannels(16); - map = new Map(); + // loading sounds + // assets->addSoundEffect("throw_egg", "assets/sound/throw_egg.wav"); + // assets->addSoundEffect("steps", "assets/sound/steps.wav"); - // loading sounds - // assets->addSoundEffect("throw_egg", "assets/sound/throw_egg.wav"); - // assets->addSoundEffect("steps", "assets/sound/steps.wav"); + // loading music + // assets->addMusic("background_music", "assets/sound/background_music.mp3"); - // loading music - // assets->addMusic("background_music", "assets/sound/background_music.mp3"); + this->gameInstance->init(); - this->gameInstance = GameFactory::instance().create(this); - this->gameInstance->init(); + return SDL_APP_CONTINUE; } -void GameInternal::handleEvents() -{ - SDL_PollEvent(&event); +SDL_AppResult GameInternal::handleEvent(SDL_Event* event) { + SDL_AppResult result = this->eventManager->handleEvent(event); - switch (event.type) - { - case SDL_QUIT: this->setRunning(false); - break; + if (event->type == SDL_EVENT_QUIT) { + this->clean(); + return result == SDL_APP_FAILURE ? SDL_APP_FAILURE : SDL_APP_SUCCESS; + } - default: - break; - } + return result; } -void GameInternal::update() +void GameInternal::update(Uint64 frameTime) { - manager.refresh(); - manager.update(); + manager.refresh(); - this->gameInstance->update(); // TODO: this might have to be split up into two update functions, before and after manager... + uint_fast16_t diffTime = frameTime - this->lastFrameTime; + manager.update(diffTime); + + this->gameInstance->update(diffTime); // TODO: this might have to be split up into two update functions, before and after manager... + + this->lastFrameTime = frameTime; } void GameInternal::render() { - SDL_RenderClear(renderer); - this->renderManager.renderAll(); - SDL_RenderPresent(renderer); + SDL_RenderClear(renderer); + this->renderManager->renderAll(); + SDL_RenderPresent(renderer); } void GameInternal::clean() { - delete(textureManager); - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - SDL_Quit(); - std::cout << "Game Cleaned!" << std::endl; + delete(textureManager); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + std::cout << "Game Cleaned!" << std::endl; } bool GameInternal::isRunning() const { - return running; + return running; } void GameInternal::setRunning(bool running) //TODO: might be depracted { - this->running = running; + this->running = running; } void GameInternal::stopGame() { - this->running = false; + this->running = false; } diff --git a/src/InputComponent.cpp b/src/InputComponent.cpp index ac26b32..8f31ad4 100644 --- a/src/InputComponent.cpp +++ b/src/InputComponent.cpp @@ -10,7 +10,7 @@ InputComponent::~InputComponent() = default; void InputComponent::init(){} -void InputComponent::update() +void InputComponent::update(uint_fast16_t diffTime) { SDL_PumpEvents(); } diff --git a/src/InputManager.cpp b/src/InputManager.cpp new file mode 100644 index 0000000..519df21 --- /dev/null +++ b/src/InputManager.cpp @@ -0,0 +1,303 @@ +#include "InputManager.h" +#include "InteractionEventdataStruct.h" +#include "SDL3/SDL_events.h" +#include "SDL3/SDL_init.h" +#include +#include "SDL3/SDL_stdinc.h" +#include "VEGO.h" +#include "VEGO_Event.h" + +std::ostream& operator<<(std::ostream& os, InputManager::Key key) { + static const std::unordered_map keyToString { + {InputManager::Key::UP, "UP"}, + {InputManager::Key::DOWN, "DOWN"}, + {InputManager::Key::LEFT, "LEFT"}, + {InputManager::Key::RIGHT, "RIGHT"}, + {InputManager::Key::SPACE, "SPACE"}, + {InputManager::Key::ENTER, "ENTER"}, + {InputManager::Key::ESCAPE, "ESCAPE"}, + {InputManager::Key::TAB, "TAB"}, + {InputManager::Key::BACKSPACE, "BACKSPACE"}, + {InputManager::Key::DELETE, "DELETE"}, + {InputManager::Key::HOME, "HOME"}, + {InputManager::Key::END, "END"}, + {InputManager::Key::PAGE_UP, "PAGE_UP"}, + {InputManager::Key::PAGE_DOWN, "PAGE_DOWN"}, + {InputManager::Key::INSERT, "INSERT"}, + {InputManager::Key::CAPS_LOCK, "CAPS_LOCK"}, + {InputManager::Key::LEFT_SHIFT, "LEFT_SHIFT"}, + {InputManager::Key::RIGHT_SHIFT, "RIGHT_SHIFT"}, + {InputManager::Key::LEFT_CTRL, "LEFT_CTRL"}, + {InputManager::Key::RIGHT_CTRL, "RIGHT_CTRL"}, + {InputManager::Key::LEFT_ALT, "LEFT_ALT"}, + {InputManager::Key::RIGHT_ALT, "RIGHT_ALT"}, + {InputManager::Key::F1, "F1"}, + {InputManager::Key::F2, "F2"}, + {InputManager::Key::F3, "F3"}, + {InputManager::Key::F4, "F4"}, + {InputManager::Key::F5, "F5"}, + {InputManager::Key::F6, "F6"}, + {InputManager::Key::F7, "F7"}, + {InputManager::Key::F8, "F8"}, + {InputManager::Key::F9, "F9"}, + {InputManager::Key::F10, "F10"}, + {InputManager::Key::F11, "F11"}, + {InputManager::Key::F12, "F12"}, + {InputManager::Key::A, "A"}, + {InputManager::Key::B, "B"}, + {InputManager::Key::C, "C"}, + {InputManager::Key::D, "D"}, + {InputManager::Key::E, "E"}, + {InputManager::Key::F, "F"}, + {InputManager::Key::G, "G"}, + {InputManager::Key::H, "H"}, + {InputManager::Key::I, "I"}, + {InputManager::Key::J, "J"}, + {InputManager::Key::K, "K"}, + {InputManager::Key::L, "L"}, + {InputManager::Key::M, "M"}, + {InputManager::Key::N, "N"}, + {InputManager::Key::O, "O"}, + {InputManager::Key::P, "P"}, + {InputManager::Key::Q, "Q"}, + {InputManager::Key::R, "R"}, + {InputManager::Key::S, "S"}, + {InputManager::Key::T, "T"}, + {InputManager::Key::U, "U"}, + {InputManager::Key::V, "V"}, + {InputManager::Key::W, "W"}, + {InputManager::Key::X, "X"}, + {InputManager::Key::Y, "Y"}, + {InputManager::Key::Z, "Z"}, + {InputManager::Key::NUM_0, "NUM_0"}, + {InputManager::Key::NUM_1, "NUM_1"}, + {InputManager::Key::NUM_2, "NUM_2"}, + {InputManager::Key::NUM_3, "NUM_3"}, + {InputManager::Key::NUM_4, "NUM_4"}, + {InputManager::Key::NUM_5, "NUM_5"}, + {InputManager::Key::NUM_6, "NUM_6"}, + {InputManager::Key::NUM_7, "NUM_7"}, + {InputManager::Key::NUM_8, "NUM_8"}, + {InputManager::Key::NUM_9, "NUM_9"}, + {InputManager::Key::LEFT_BRACKET, "LEFT_BRACKET"}, + {InputManager::Key::RIGHT_BRACKET, "RIGHT_BRACKET"}, + {InputManager::Key::SEMICOLON, "SEMICOLON"}, + {InputManager::Key::APOSTROPHE, "APOSTROPHE"}, + {InputManager::Key::COMMA, "COMMA"}, + {InputManager::Key::PERIOD, "PERIOD"}, + {InputManager::Key::SLASH, "SLASH"}, + {InputManager::Key::BACKSLASH, "BACKSLASH"}, + {InputManager::Key::GRAVE, "GRAVE"} + }; + + auto it = keyToString.find(key); + if (it != keyToString.end()) { + os << it->second; + } else { + os << "UNKNOWN_KEY"; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, const InputManager::InputAction& action) { + os << action.name << " with binding(s): "; + for (auto& binding : action.bindings) { + os << binding << ", "; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, const InputManager::InputAction* action) { + if (action) { + os << *action; + } else { + os << "NULL_ACTION"; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, const std::vector& actions) { + os << "Actions: "; + if (actions.empty()) { + os << "None"; + } else { + for (size_t i = 0; i < actions.size(); ++i) { + os << actions[i]; + if (i < actions.size() - 1) { + os << " | "; + } + } + } + return os; +} + +// TODO: find out why it doesnt work?? +std::ostream& operator<<(std::ostream& os, const std::vector& actions) { + os << "Actions: "; + if (actions.empty()) { + os << "None"; + } else { + for (size_t i = 0; i < actions.size(); ++i) { + if (actions[i]) { + os << *actions[i]; + } else { + os << "NULL_ACTION"; + } + if (i < actions.size() - 1) { + os << " | "; + } + } + } + return os; +} + + + + +// overloads end -------------------------------------------------------------------------------------- + + + + + +InputManager::InputManager() : activeContext("Default") {} + +InputManager::~InputManager() {} + +void InputManager::initKeyMap() { + keyMap = { + {Key::UP, SDL_SCANCODE_UP}, + {Key::DOWN, SDL_SCANCODE_DOWN}, + {Key::LEFT, SDL_SCANCODE_LEFT}, + {Key::RIGHT, SDL_SCANCODE_RIGHT}, + {Key::SPACE, SDL_SCANCODE_SPACE}, + {Key::ENTER, SDL_SCANCODE_RETURN}, + {Key::ESCAPE, SDL_SCANCODE_ESCAPE}, + {Key::TAB, SDL_SCANCODE_TAB}, + {Key::BACKSPACE, SDL_SCANCODE_BACKSPACE}, + {Key::DELETE, SDL_SCANCODE_DELETE}, + {Key::HOME, SDL_SCANCODE_HOME}, + {Key::END, SDL_SCANCODE_END}, + {Key::PAGE_UP, SDL_SCANCODE_PAGEUP}, + {Key::PAGE_DOWN, SDL_SCANCODE_PAGEDOWN}, + {Key::INSERT, SDL_SCANCODE_INSERT}, + {Key::CAPS_LOCK, SDL_SCANCODE_CAPSLOCK}, + {Key::LEFT_SHIFT, SDL_SCANCODE_LSHIFT}, + {Key::RIGHT_SHIFT, SDL_SCANCODE_RSHIFT}, + {Key::LEFT_CTRL, SDL_SCANCODE_LCTRL}, + {Key::RIGHT_CTRL, SDL_SCANCODE_RCTRL}, + {Key::LEFT_ALT, SDL_SCANCODE_LALT}, + {Key::RIGHT_ALT, SDL_SCANCODE_RALT}, + {Key::F1, SDL_SCANCODE_F1}, + {Key::F2, SDL_SCANCODE_F2}, + {Key::F3, SDL_SCANCODE_F3}, + {Key::F4, SDL_SCANCODE_F4}, + {Key::F5, SDL_SCANCODE_F5}, + {Key::F6, SDL_SCANCODE_F6}, + {Key::F7, SDL_SCANCODE_F7}, + {Key::F8, SDL_SCANCODE_F8}, + {Key::F9, SDL_SCANCODE_F9}, + {Key::F10, SDL_SCANCODE_F10}, + {Key::F11, SDL_SCANCODE_F11}, + {Key::F12, SDL_SCANCODE_F12}, + {Key::A, SDL_SCANCODE_A}, + {Key::B, SDL_SCANCODE_B}, + {Key::C, SDL_SCANCODE_C}, + {Key::D, SDL_SCANCODE_D}, + {Key::E, SDL_SCANCODE_E}, + {Key::F, SDL_SCANCODE_F}, + {Key::G, SDL_SCANCODE_G}, + {Key::H, SDL_SCANCODE_H}, + {Key::I, SDL_SCANCODE_I}, + {Key::J, SDL_SCANCODE_J}, + {Key::K, SDL_SCANCODE_K}, + {Key::L, SDL_SCANCODE_L}, + {Key::M, SDL_SCANCODE_M}, + {Key::N, SDL_SCANCODE_N}, + {Key::O, SDL_SCANCODE_O}, + {Key::P, SDL_SCANCODE_P}, + {Key::Q, SDL_SCANCODE_Q}, + {Key::R, SDL_SCANCODE_R}, + {Key::S, SDL_SCANCODE_S}, + {Key::T, SDL_SCANCODE_T}, + {Key::U, SDL_SCANCODE_U}, + {Key::V, SDL_SCANCODE_V}, + {Key::W, SDL_SCANCODE_W}, + {Key::X, SDL_SCANCODE_X}, + {Key::Y, SDL_SCANCODE_Y}, + {Key::Z, SDL_SCANCODE_Z}, + {Key::NUM_0, SDL_SCANCODE_0}, + {Key::NUM_1, SDL_SCANCODE_1}, + {Key::NUM_2, SDL_SCANCODE_2}, + {Key::NUM_3, SDL_SCANCODE_3}, + {Key::NUM_4, SDL_SCANCODE_4}, + {Key::NUM_5, SDL_SCANCODE_5}, + {Key::NUM_6, SDL_SCANCODE_6}, + {Key::NUM_7, SDL_SCANCODE_7}, + {Key::NUM_8, SDL_SCANCODE_8}, + {Key::NUM_9, SDL_SCANCODE_9}, + {Key::LEFT_BRACKET, SDL_SCANCODE_LEFTBRACKET}, + {Key::RIGHT_BRACKET, SDL_SCANCODE_RIGHTBRACKET}, + {Key::SEMICOLON, SDL_SCANCODE_SEMICOLON}, + {Key::APOSTROPHE, SDL_SCANCODE_APOSTROPHE}, + {Key::COMMA, SDL_SCANCODE_COMMA}, + {Key::PERIOD, SDL_SCANCODE_PERIOD}, + {Key::SLASH, SDL_SCANCODE_SLASH}, + {Key::BACKSLASH, SDL_SCANCODE_BACKSLASH}, + {Key::GRAVE, SDL_SCANCODE_GRAVE} + }; +} + +void InputManager::registerAction(const std::string& actionName, const std::vector& keys, std::function callback, const std::string& context) { + InputAction* storedAction = new InputAction{actionName, keys, callback}; + for (const auto& key : keys) { + actionsByContextAndKey[context][key].emplace_back(storedAction); + } + std::cout << "Registered action: " << storedAction << " in context: " << context << std::endl; +} + +std::vector InputManager::getActionsByKey(const Key key) const { + std::vector result; + + for (const auto& [context, keyMap] : actionsByContextAndKey) { + auto it = keyMap.find(key); + if (it != keyMap.end()) { + result.insert(result.end(), it->second.begin(), it->second.end()); + } + } + + return result; +} + +void InputManager::setActiveContext(const std::string& context) { + activeContext = context; + std::cout << "Active context set to: " << activeContext << std::endl; +} + +std::string InputManager::getActiveContext() const { + return activeContext; +} + +SDL_AppResult InputManager::handleEvent(SDL_EventType type, SDL_Event* const event) { + if (event->key.repeat) { + return SDL_APP_CONTINUE; + } + auto keyIt = std::ranges::find_if(keyMap, [&](const auto& pair) + { return pair.second == event->key.scancode; }); + + if (keyIt != keyMap.end()) { + std::cout << "in != keymap.end" << std::endl; + + Key pressedKey = keyIt->first; + auto keyActions = actionsByContextAndKey[activeContext]; + auto it = keyActions.find(pressedKey); + if (it != keyActions.end()) { + for (auto& action : it->second) { + std::cout << "Action triggered: " << action->name << " in context: " << activeContext << std::endl; + action->callback(type == SDL_EVENT_KEY_UP); + } + } + } + + return SDL_APP_CONTINUE; +} \ No newline at end of file diff --git a/src/InteractionComponent.cpp b/src/InteractionComponent.cpp new file mode 100644 index 0000000..bd69ecb --- /dev/null +++ b/src/InteractionComponent.cpp @@ -0,0 +1,22 @@ +#include "InteractionComponent.h" + +#include "VEGO.h" + +InteractionComponent::InteractionComponent(std::function callback) : interactionCallback(callback) +{ + VEGO_Game().interactionManager->registerListener(this->entity->getComponentAsPointer()); +} + +void InteractionComponent::interact(void* actor, void* data) +{ + if (interactionCallback) { + interactionCallback(actor, data); + } +} +std::shared_ptr InteractionComponent::getPosition() // required for targeting strategy, return null to only allow explicit targeting +{ + if (entity->hasComponent()) { + return std::make_shared(entity->getComponent().position); + } + return nullptr; +} \ No newline at end of file diff --git a/src/InteractionEventdataStruct.cpp b/src/InteractionEventdataStruct.cpp new file mode 100644 index 0000000..bdbb81a --- /dev/null +++ b/src/InteractionEventdataStruct.cpp @@ -0,0 +1,16 @@ +#include "InteractionEventdataStruct.h" + +#include "VEGO.h" +#include "VEGO_Event.h" + +void InteractionEventdataStruct::triggerEvent() +{ + // TODO: if target is null && strategy is 0, error + + SDL_Event event; + SDL_zero(event); + event.type = vego::VEGO_Event_Interaction; + event.user.data1 = this; + + SDL_PushEvent(&event); +} \ No newline at end of file diff --git a/src/InteractionManager.cpp b/src/InteractionManager.cpp new file mode 100644 index 0000000..5c74ae9 --- /dev/null +++ b/src/InteractionManager.cpp @@ -0,0 +1,90 @@ +#include "InteractionManager.h" + +#include "InteractionEventdataStruct.h" +#include "InteractionListener.h" +#include "SDL3/SDL_init.h" +#include "VEGO_Event.h" +#include "Vector2D.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +InteractionManager::InteractionManager() +{ + this->targetingFuncs.fill(nullptr); + this->targetingFuncs.at(static_cast::type>(InteractionManager::TargetingStrategy::closest)) = [](Vector2D* reference, std::vector> input) { + return std::shared_ptr(); + }; + this->targetingFuncs.at(static_cast::type>(InteractionManager::TargetingStrategy::closest)) = [](Vector2D* reference, std::vector> input) { + auto min = std::ranges::min_element(input, [&reference](std::shared_ptr& a, std::shared_ptr& b) { + std::shared_ptr coordA = a->getPosition(); + std::shared_ptr coordB = b->getPosition(); + + if (coordB == nullptr) return true; + if (coordA == nullptr) return false; + + return std::sqrt(std::pow(coordA->x - reference->x, 2) + std::pow(coordA->y - reference->y, 2)) < std::sqrt(std::pow(coordB->x - reference->x, 2) + std::pow(coordB->y - reference->y, 2)); + }); + return min == std::ranges::end(input) ? nullptr : *min; + }; + this->targetingFuncs.at(static_cast::type>(InteractionManager::TargetingStrategy::manhattenDistance)) = [](Vector2D* reference, std::vector> input) { + auto min = std::ranges::min_element(input, [&reference](std::shared_ptr& a, std::shared_ptr& b) { + std::shared_ptr coordA = a->getPosition(); + std::shared_ptr coordB = b->getPosition(); + + if (coordB == nullptr) return true; + if (coordA == nullptr) return false; + + return (std::abs(coordA->x - reference->x) + std::abs(coordA->y - reference->y)) < (std::abs(coordB->x - reference->x) + std::abs(coordB->y - reference->y)); + }); + return min == std::ranges::end(input) ? nullptr : *min; + }; +} + +SDL_AppResult InteractionManager::handleInteract(SDL_EventType type, SDL_Event* const event) +{ + if (type != vego::VEGO_Event_Interaction) { // error handling + return SDL_APP_CONTINUE; + } + + InteractionEventdataStruct* data = static_cast(event->user.data1); + + std::shared_ptr listener = data->target.lock(); + + if (data->strategy != static_cast::type>(InteractionManager::TargetingStrategy::none)) { + listener = this->targetingFuncs.at(data->strategy)( + data->targetingReference.get(), + this->listeners + | std::views::transform(&std::weak_ptr::lock) + | std::views::filter(&std::shared_ptr::operator bool) + | std::ranges::to() + ); + } + + if (listener) { + listener->interact(data->actor, data->data); + } + + return SDL_APP_CONTINUE; +} + +void InteractionManager::registerListener(std::weak_ptr listener) +{ + this->listeners.emplace_back(listener); +} + +uint8_t InteractionManager::registerTargetingFunc(TargetingFunc func) +{ + auto it = std::ranges::find_if(this->targetingFuncs, [](const auto& func) { + return !func; + }); + (*it) = func; + return std::distance(this->targetingFuncs.begin(), it); +} \ No newline at end of file diff --git a/src/Manager.cpp b/src/Manager.cpp index 09eea97..31e42a4 100644 --- a/src/Manager.cpp +++ b/src/Manager.cpp @@ -27,9 +27,9 @@ void Manager::refresh() std::end(entities)); } -void Manager::update() +void Manager::update(uint_fast16_t diffTime) { - for (auto& e : entities) e->update(); + for (auto& e : entities) e->update(diffTime); } void Manager::addToGroup(Entity* mEntity, Group mGroup) diff --git a/src/Map.cpp b/src/Map.cpp index 7204be2..fe18dd5 100644 --- a/src/Map.cpp +++ b/src/Map.cpp @@ -2,170 +2,184 @@ #include #include +#include #include -#include -#include -#include -#include +#include +#include +#include #include -#include -#include +#include +#include #include #include #include #include #include +#include -#include "Constants.h" +#include "ColliderComponent.h" #include "GameInternal.h" #include "SpriteComponent.h" #include "TextureManager.h" #include "TileComponent.h" #include "VEGO.h" -#include "tmxlite/Types.hpp" -void Map::loadMap(const char* path, int sizeX, int sizeY, GameInternal* game, const std::map>* textureDict /* backreference */) -{ - std::string tileIDstr; - char singleChar = 0; - std::ifstream mapFile; - mapFile.open(path); - if (!mapFile.is_open()) { - SDL_SetError("Error loading map: Couldn't open map file!"); - std::cout << "ERROR: Map couldnt be loaded! " << SDL_GetError() << std::endl; - SDL_ClearError(); - } +template<> std::optional Map::getLayerProperty(const std::vector& properties, std::string propertyName) { + auto zIndexIterator = std::ranges::find_if(properties, [propertyName](const tmx::Property& property) { + return property.getName().compare(propertyName) == 0; + }); - int x = 0, y = 0; // needed outside for-loop for error handling - for (; !mapFile.eof(); mapFile.get(singleChar)) - { - if (singleChar == ',' || singleChar == '\n') { - if (tileIDstr.empty()) - continue; - Map::addTile(std::stoi(tileIDstr), x * TILE_SIZE, y * TILE_SIZE, game, textureDict); - tileIDstr.clear(); - x++; - if (singleChar == '\n') { - if (x != sizeX) { - SDL_SetError("Error loading map: specified x size doesn't match map file!"); - std::cout << "ERROR: Map couldnt be loaded! " << SDL_GetError() << std::endl; - SDL_ClearError(); - } - x = 0; - y++; - continue; - } - continue; - } - if (!std::isdigit(singleChar)) continue; - tileIDstr += singleChar; - } - if (y != sizeY) { - SDL_SetError("Error loading map: specified y size doesn't match map file!"); - std::cout << "ERROR: Map couldnt be loaded! " << SDL_GetError() << std::endl; - SDL_ClearError(); - } - - mapFile.close(); -} - -void Map::addTile(unsigned long id, int x, int y, GameInternal* game, const std::map>* textureDict) // tile entity -{ - auto& tile(game->manager.addEntity()); - tile.addComponent(x, y, TILE_SIZE, TILE_SIZE, id, textureDict); - - if(tile.getComponent().hasCollision()) tile.addComponent("tile"/*tile.getComponent().getName().data()*/); - tile.addGroup((size_t)Entity::GroupLabel::MAPTILES); -} - -void Map::loadMapTmx(const char* path) -{ - tmx::Map map; - if (!map.load(path)) { - // TODO: log to console + if (zIndexIterator != properties.end() && zIndexIterator->getType() == tmx::Property::Type::Boolean) { + return zIndexIterator->getBoolValue(); } - const std::vector& tileSets = map.getTilesets(); + return std::nullopt; +} - const std::vector& mapLayers = map.getLayers(); - const auto mapSize = map.getTileCount(); - const auto mapTileSize = map.getTileSize(); +template<> std::optional Map::getLayerProperty(const std::vector& properties, std::string propertyName) +{ + auto zIndexIterator = std::ranges::find_if(properties, [propertyName](const tmx::Property& property) { + return property.getName().compare(propertyName) == 0; + }); + + if (zIndexIterator != properties.end() && zIndexIterator->getType() == tmx::Property::Type::Int) { + return zIndexIterator->getIntValue(); + } + + return std::nullopt; +} + +Map::Map(const char* path) +{ + if (!this->map.load(path)) { + // TODO: log to console + // TODO: error handling + } std::vector texturePaths = {}; - for (auto tileSet : tileSets) { + for (const auto& tileSet : map.getTilesets()) { texturePaths.emplace_back(tileSet.getImagePath()); } - for (auto& layer : mapLayers) { + this->mapData = { + &map.getTilesets(), + &map.getLayers(), + &map.getTileCount(), + &map.getTileSize(), + &texturePaths + }; + + + for (auto& layer : *this->mapData.mapLayers) { if (layer->getType() == tmx::Layer::Type::Tile) { - auto& tileLayer = layer->getLayerAs(); - - int zIndex = 0; - - const std::vector& properties = layer->getProperties(); - auto zIndexIterator = std::find_if(properties.begin(), properties.end(), [](const tmx::Property& property) { - return property.getName() == "zIndex"; - }); - - if (zIndexIterator != properties.end() && std::is_nothrow_convertiblegetType()), int>::value) { - zIndex = zIndexIterator->getIntValue(); - } - - const auto& tiles = tileLayer.getTiles(); - - for (auto i = 0u; i < tileSets.size(); i++) { - auto tilesetTexture = VEGO_Game().textureManager->loadTexture(texturePaths.at(i).c_str()); - tmx::Vector2i textureSize; - SDL_QueryTexture(tilesetTexture, nullptr, nullptr, &(textureSize.x), &(textureSize.y)); - - const auto tileCountX = textureSize.x / mapTileSize.x; - const auto tileCountY = textureSize.y / mapTileSize.y; - - for (auto idx = 0ul; idx < mapSize.x * mapSize.y; idx++) { - - if (idx >= tiles.size() || tiles[idx].ID < tileSets.at(i).getFirstGID() - || tiles[idx].ID >= (tileSets.at(i).getFirstGID() + tileSets.at(i).getTileCount())) { - continue; - } - - const auto x = idx % mapSize.x; - const auto y = idx / mapSize.x; - - auto idIndex = (tiles[idx].ID - tileSets.at(i).getFirstGID()); - - int u = idIndex % tileCountX; - int v = idIndex / tileCountY; - u *= mapTileSize.x; //TODO we should be using the tile set size, as this may be different from the map's grid size - v *= mapTileSize.y; - - //normalise the UV - u /= textureSize.x; - v /= textureSize.y; - - //vert pos - const float tilePosX = static_cast(x) * mapTileSize.x; - const float tilePosY = (static_cast(y) * mapTileSize.y); - - Map::addTile(tilePosX, tilePosY, mapTileSize, u, v, zIndex, texturePaths.at(i).c_str()); - } - } - if (layer->getType() == tmx::Layer::Type::Object) { - // spawn objects - continue; - } + loadTileLayer(layer->getLayerAs()); + continue; + } + if (layer->getType() == tmx::Layer::Type::Object) { + // spawn objects + continue; } } } -void Map::addTile(float x, float y, const tmx::Vector2u& mapTileSize, int u, int v, int zIndex, const char* texturePath) +void Map::loadTileLayer(const tmx::TileLayer& layer) +{ + const std::vector& properties = layer.getProperties(); + int zIndex = getLayerProperty(properties, "zIndex").value_or(0); + bool collision = getLayerProperty(properties, "collision").value_or(false); + + const auto& tiles = layer.getTiles(); + + // for each tile set + auto tileConstructorRange = std::views::iota(0) + | std::views::take(this->mapData.tileSets->size()) + // return the tile set metadata + | std::views::transform([&](uint16_t i) { + const char* texturePath = this->mapData.texturePaths->at(i).c_str(); + + tmx::Vector2f textureSize; + SDL_GetTextureSize( + VEGO_Game().textureManager->loadMapTileTexture(texturePath), + &(textureSize.x), + &(textureSize.y) + ); + + tmx::Vector2u tileCount2D = { static_cast(textureSize.x / this->mapData.mapTileSize->x), static_cast(textureSize.y / this->mapData.mapTileSize->y) }; + + uint32_t tileCount = this->mapData.tileSets->at(i).getTileCount(); + uint32_t firstGID = this->mapData.tileSets->at(i).getFirstGID(); + + return TileSetData { texturePath, textureSize, tileCount, tileCount2D, firstGID }; + }) + | std::views::transform([=, this](const TileSetData& data) { + // for each tile on the tile set + return std::views::iota(0) + | std::views::take(this->mapData.mapSize->x * this->mapData.mapSize->y) + // only take tiles that are on the ID range of the tile set + | std::views::filter([=](uint16_t idx) { + return + idx < tiles.size() + && tiles[idx].ID >= data.firstGID + && tiles[idx].ID < (data.firstGID + data.tileCount); + }) + // extract tile data + | std::views::transform([=, this](uint16_t idx) { + const auto x = idx % this->mapData.mapSize->x; + const auto y = idx / this->mapData.mapSize->x; + + const auto idIndex = (tiles[idx].ID - data.firstGID); + + uint32_t u = idIndex % data.tileCount2D.x; + uint32_t v = idIndex / data.tileCount2D.y; + u *= this->mapData.mapTileSize->x; // TODO: we should be using the tile set size, as this may be different from the map's grid size + v *= this->mapData.mapTileSize->y; + + // normalise the UV + u /= data.textureSize.x; + v /= data.textureSize.y; + + // vert pos + const float tilePosX = static_cast(x) * this->mapData.mapTileSize->x; + const float tilePosY = (static_cast(y) * this->mapData.mapTileSize->y); + + // return tile data as a function to spawn said tile + return std::function( + [tilePosX, tilePosY, capture0 = *this->mapData.mapTileSize, u, v, zIndex, capture1 = data.texturePath, collision] { + Map::addTile(tilePosX, tilePosY, capture0, u, v, zIndex, capture1, collision); + } + ); + }); + }) + // 2D view to 1D vector; might be better keep as view with scene management + | std::views::join + | std::ranges::to(); + + this->tileConstructors.insert(this->tileConstructors.end(), tileConstructorRange.begin(), tileConstructorRange.end()); +} + +void Map::addTile(float x, float y, const tmx::Vector2u& mapTileSize, int u, int v, int zIndex, std::string texturePath, bool hasCollision) { auto& tile(VEGO_Game().manager.addEntity()); tile.addComponent(x, y, mapTileSize.x, mapTileSize.y, 1); - tile.addComponent(texturePath, v, u, zIndex); // why does uv need to be reversed? + tile.addComponent(texturePath.c_str(), v, u, zIndex); // why does uv need to be reversed? + //TODO: also implement updated map stuff for this + + if (hasCollision) { + // tag currently does not have a clear purposes, TODO: figure out appropriate tag name + tile.addComponent("hello I am a collider of a tile!"); + tile.addGroup((size_t)Entity::GroupLabel::MAPTILES); + } +} + +void Map::generateTiles() +{ + std::ranges::for_each(this->tileConstructors, [](auto& function) { + function(); + }); } \ No newline at end of file diff --git a/src/PowerupComponent.cpp b/src/PickupComponent.cpp similarity index 66% rename from src/PowerupComponent.cpp rename to src/PickupComponent.cpp index 5c1df71..616d375 100644 --- a/src/PowerupComponent.cpp +++ b/src/PickupComponent.cpp @@ -1,18 +1,22 @@ -#include "PowerupComponent.h" +#include "PickupComponent.h" #include "GameInternal.h" #include "CollisionHandler.h" #include "Entity.h" #include "HealthComponent.h" +#include "SpriteComponent.h" #include "StatEffectsComponent.h" #include "Constants.h" +#include "TextureManager.h" +#include "TransformComponent.h" #include +#include "VEGO.h" -PowerupComponent::PowerupComponent(std::function func) +PickupComponent::PickupComponent(std::function func) { this->pickupFunc = func; } -void PowerupComponent::update() +void PickupComponent::update(uint_fast16_t diffTime) { Entity* player; if ((player = this->entity->getManager().getGame()->collisionHandler->getAnyIntersection( diff --git a/src/PickupManager.cpp b/src/PickupManager.cpp new file mode 100644 index 0000000..21fe1ff --- /dev/null +++ b/src/PickupManager.cpp @@ -0,0 +1,65 @@ +#include "PickupManager.h" + +#include "TextureManager.h" +#include "SoundManager.h" +#include "ProjectileComponent.h" +#include "GameInternal.h" + +#include "TransformComponent.h" + +#include "CollisionHandler.h" +#include "ColliderComponent.h" +#include "Constants.h" +#include "Entity.h" +#include "Vector2D.h" +#include "PickupComponent.h" +#include +#include + +#include "Textures.h" + +PickupManager::PickupManager(Manager* manager) : man(manager) {} + +PickupManager::~PickupManager() {} + +void PickupManager::createPowerup(Vector2D pos, std::function pickupFunc, Textures texture) { + + auto& powerups(man->addEntity()); + powerups.addComponent(pos.x, pos.y, 32, 32, 1); //32x32 is standard size for objects + + try { + powerups.addComponent(texture, 3); + } + catch (std::runtime_error e) { + std::cout << e.what() << std::endl; + } + + powerups.addComponent("powerup", 0.6f); + powerups.addComponent(pickupFunc); + powerups.addGroup((size_t)Entity::GroupLabel::POWERUPS); +} + +Vector2D PickupManager::calculateSpawnPosition() +{ + Vector2D spawnPos = Vector2D(-1, -1); + bool conflict = false; + for (int i = 0; i <= SPAWN_ATTEMPTS; i++) + { + SDL_Rect spawnRect; + spawnRect.h = spawnRect.w = 32; + spawnRect.x = rand() % (VEGO_Game().config->getFinalConfig().at("screen_width").get() - spawnRect.w); + spawnRect.y = rand() % (VEGO_Game().config->getFinalConfig().at("screen_height").get() - spawnRect.h); + conflict = false; + for (auto cc : this->man->getGame()->collisionHandler->getColliders({ Entity::GroupLabel::MAPTILES })) + { + if (SDL_HasRectIntersection(&spawnRect, &cc->collider) && strcmp(cc->tag, "projectile")) + { + conflict = true; + break; + } + } + if (conflict) continue; + spawnPos = Vector2D(spawnRect.x, spawnRect.y); + } + return spawnPos; +} \ No newline at end of file diff --git a/src/ProjectileComponent.cpp b/src/ProjectileComponent.cpp index 11599c8..3f5f13c 100644 --- a/src/ProjectileComponent.cpp +++ b/src/ProjectileComponent.cpp @@ -14,12 +14,12 @@ void ProjectileComponent::init() { transformComponent = &entity->getComponent(); transformComponent->direction = direction; - SoundManager::playSound(this->entity->getManager().getGame(), "throw_egg", true, PLAY_ONCE, MAX_VOLUME, -1); + SoundManager::playSound(this->soundEffect, true, PLAY_ONCE, MAX_VOLUME, -1); } -void ProjectileComponent::update() +void ProjectileComponent::update(uint_fast16_t diffTime) { - distance += speed; + distance += speed * diffTime * (1.f/1000); IntersectionBitSet boundsIntersection = this->entity->getManager().getGame()->collisionHandler->getIntersectionWithBounds(entity); diff --git a/src/RenderObject.cpp b/src/RenderObject.cpp index 5c21ee9..897940e 100644 --- a/src/RenderObject.cpp +++ b/src/RenderObject.cpp @@ -1,10 +1,10 @@ #include "RenderObject.h" #include "RenderManager.h" -RenderObject::RenderObject(int zIndex, RenderManager& renderManager) : zIndex(zIndex), renderManager(renderManager) { - renderManager.add(this); +RenderObject::RenderObject(int zIndex, RenderManager* renderManager) : zIndex(zIndex), renderManager(renderManager) { + renderManager->add(this); } RenderObject::~RenderObject() { - this->renderManager.remove(this); + this->renderManager->remove(this); } \ No newline at end of file diff --git a/src/SoundManager.cpp b/src/SoundManager.cpp index 2954b9f..df683c2 100644 --- a/src/SoundManager.cpp +++ b/src/SoundManager.cpp @@ -1,15 +1,16 @@ #include "SoundManager.h" -#include #include #include -#include "GameInternal.h" -#include "AssetManager.h" +#include +#include "GameInternal.h" + +/* Mix_Music* SoundManager::loadMusic(const char* fileName) { - auto it = this->music_cache.find(fileName); + //auto it = this->music_cache.find(fileName); if (it != this->music_cache.end()) { return it->second; @@ -47,14 +48,21 @@ Mix_Chunk* SoundManager::loadSound(const char* fileName) return sound; } -void SoundManager::playSound(GameInternal* game, std::string sound, bool canOverlap, int loops, int volume, int channel) +*/ + +void SoundManager::playSound(SoundEffects sound, bool canOverlap, int loops, int volume, int channel) { + if (!this_instance->sound_cache.contains(sound)) { + std::cerr << "Error playing Sound-Effect: sound effect not found" << std::endl; + return; + } + if(!canOverlap) { // dev needs to specify a channel for this check to work, if they set it to -1 and let sdl pick the first available // channel mix_getchunk() won't work if (Mix_Playing(channel) != 0 && - Mix_GetChunk(channel) == game->assets->getSound(sound) && + Mix_GetChunk(channel) == this_instance->sound_cache.at(sound) && channel != -1) { return; @@ -63,36 +71,36 @@ void SoundManager::playSound(GameInternal* game, std::string sound, bool canOver Mix_HaltChannel(channel); } - if(Mix_VolumeChunk(game->assets->getSound(sound), volume) == -1) + if(Mix_VolumeChunk(this_instance->sound_cache.at(sound), volume) == -1) { - std::cerr << "Error adjusting volume: " << Mix_GetError() << std::endl; + std::cerr << "Error adjusting volume: " << SDL_GetError() << std::endl; } - if (Mix_PlayChannel(channel, game->assets->getSound(sound), loops) == -1) + if (Mix_PlayChannel(channel, this_instance->sound_cache.at(sound), loops) == -1) { - std::cerr << "Error playing sound '" << sound << "': " << Mix_GetError() << std::endl; + std::cerr << "Error playing sound " << ": " << SDL_GetError() << std::endl; } } -void SoundManager::playMusic(GameInternal* game, std::string music, int loops, int volume, int ms) +void SoundManager::playMusic(BackgroundMusic music, int loops, int volume, int milliseconds) { + if (!this_instance->music_cache.contains(music)) { + std::cerr << "Error playing music: music not found" << std::endl; + return; + } + if (Mix_PlayingMusic() != 0 || Mix_Fading() == Mix_Fading::MIX_FADING_IN) return; - if(ms > 0) + if(milliseconds > 0) { - Mix_FadeInMusic(game->assets->getMusic(music), loops, ms); + Mix_FadeInMusic(this_instance->music_cache.at(music), loops, milliseconds); return; } if(Mix_VolumeMusic(volume) == -1) { - std::cerr << "Error adjusting volume: " << Mix_GetError() << std::endl; - } - - if (Mix_PlayMusic(game->assets->getMusic(music), loops) == -1) - { - std::cerr << "Error playing music '" << music << "': " << Mix_GetError() << std::endl; + std::cerr << "Error adjusting volume: " << SDL_GetError() << std::endl; } } @@ -133,3 +141,51 @@ void SoundManager::fadeOutMusic(int ms) Mix_FadeOutMusic(ms); } + +void SoundManager::addSingleSoundEffect(SoundEffects soundEffect, const char *path) { + if (this_instance->sound_cache.contains(soundEffect)) { + std::cerr << "Error when adding Sound-Effect: sound-effect with that key already in cache" << std::endl; + return; + } + + Mix_Chunk* sound = Mix_LoadWAV(path); + + if (sound == nullptr) { + std::cerr << "Error when loading Sound-Effect: could not load sound effect from " << path << std::endl; + return; + } + + this_instance->sound_cache.emplace(soundEffect, sound); +} + +void SoundManager::addSingleBackgroundMusic(BackgroundMusic backgroundMusic, const char *path) { + if (this_instance->music_cache.contains(backgroundMusic)) { + std::cerr << "Error when adding Sound-Effect: sound-effect with that key already in cache" << std::endl; + return; + } + + Mix_Music* music = Mix_LoadMUS(path); + + if (music == nullptr) { + std::cerr << "Error when loading Sound-Effect: could not load sound effect from " << path << std::endl; + return; + } + + this_instance->music_cache.emplace(backgroundMusic, music); +} + +void SoundManager::addSoundEffects(const std::map &effects) { + for (auto effect : effects) + addSingleSoundEffect(effect.first, effect.second); +} + +void SoundManager::addBackgroundMusic(const std::map &backgroundMusic) { + for (auto track : backgroundMusic) + addSingleBackgroundMusic(track.first, track.second); +} + +SoundManager* SoundManager::this_instance = nullptr; + + + + diff --git a/src/SpriteComponent.cpp b/src/SpriteComponent.cpp index ae27ea7..8bd341a 100644 --- a/src/SpriteComponent.cpp +++ b/src/SpriteComponent.cpp @@ -1,6 +1,6 @@ #include "SpriteComponent.h" -#include +#include #include #include @@ -15,18 +15,25 @@ #include "Manager.h" #include "VEGO.h" -SpriteComponent::SpriteComponent(const char* path, int zIndex) : RenderObject(zIndex, VEGO_Game().renderManager), textureXOffset(0), textureYOffset(0) +SpriteComponent::SpriteComponent(Textures texture, int zIndex) : RenderObject(zIndex, VEGO_Game().renderManager), textureXOffset(0), textureYOffset(0) { - this->texturePath = path; + this->textureEnum = texture; + this->path = ""; } -SpriteComponent::SpriteComponent(const char* path, int xOffset, int yOffset, int zIndex) : RenderObject(zIndex, VEGO_Game().renderManager), textureXOffset(xOffset), textureYOffset(yOffset) +SpriteComponent::SpriteComponent(Textures texture, int xOffset, int yOffset, int zIndex) : RenderObject(zIndex, VEGO_Game().renderManager), textureXOffset(xOffset), textureYOffset(yOffset) { - this->texturePath = path; + this->textureEnum = texture; + this->path = ""; +} + +SpriteComponent::SpriteComponent(const char* path, int xOffset, int yOffset, int zIndex) : RenderObject(zIndex, VEGO_Game().renderManager), textureXOffset(xOffset), textureYOffset(yOffset) { + + this->path = path; } SpriteComponent::SpriteComponent( - const char* path, + Textures texture, bool isAnimated, std::map>* animationMap, std::string defaultAnimation, @@ -38,19 +45,26 @@ SpriteComponent::SpriteComponent( playAnimation(defaultAnimation); - this->texturePath = path; + this->textureEnum = texture; + + this->path = ""; } SpriteComponent::~SpriteComponent() {} -void SpriteComponent::setTexture(const char* path) +void SpriteComponent::setTexture(Textures texture) { - this->texture = VEGO_Game().textureManager->loadTexture(path); + this->texture = VEGO_Game().textureManager->loadTexture(texture); } void SpriteComponent::init() { - setTexture(this->texturePath); + if (this->path == "") { + setTexture(this->textureEnum); + } + else { + setMapTileTexture(this->path); + } this->transform = &entity->getComponent(); @@ -59,14 +73,14 @@ void SpriteComponent::init() this->srcRect.x = this->textureXOffset * this->srcRect.w; this->srcRect.y = this->textureYOffset * this->srcRect.h;; - this->update(); + this->update(0); } -void SpriteComponent::update() +void SpriteComponent::update(uint_fast16_t diffTime) { // This code is not compatible for animated tiles if (animated) { - srcRect.x = srcRect.w * static_cast((SDL_GetTicks() / speed) % frames); + srcRect.x = srcRect.w * static_cast((SDL_GetTicks() / speed) % frames); // TODO: should not call SDL_GetTicks() but use diffTime srcRect.y = animationIndex * transform->height; } @@ -92,4 +106,8 @@ void SpriteComponent::playAnimation(std::string type) void SpriteComponent::setDirection(Direction direction) { this->flipped = direction == Direction::RIGHT; -} \ No newline at end of file +} + +void SpriteComponent::setMapTileTexture(const char *path) { + this->texture = VEGO_Game().textureManager->loadMapTileTexture(path); +} diff --git a/src/StatEffectsComponent.cpp b/src/StatEffectsComponent.cpp index c66e206..5de70c9 100644 --- a/src/StatEffectsComponent.cpp +++ b/src/StatEffectsComponent.cpp @@ -1,56 +1,26 @@ #include "StatEffectsComponent.h" #include "Entity.h" #include "TransformComponent.h" -// #include "KeyboardController.h" #include #include void StatEffectsComponent::init() {} -void StatEffectsComponent::update() +void StatEffectsComponent::update(uint_fast16_t diffTime) { - for (int i = 0; i < MAX_STATS; i++) - { - if (this->buffs.at(i) == 0) continue; - if (this->buffs.at(i) - 1 == 0) - { - this->resetStatValue((Stats)i); + for (auto it = effects.begin(); it != effects.end(); ) { + it->duration -= diffTime; + + if (it->duration <= 0) { + it->resetFunction(); + it = effects.erase(it); + continue; } - this->buffs.at(i) -= 1; - } + it++; + } } -void StatEffectsComponent::modifyStatDur(Stats stat, int duration, int value) -{ - if(this->buffs.at((uint8_t)stat) == 0) this->modifyStatValue(stat, value); - this->buffs.at((uint8_t)stat) += duration; -} - -void StatEffectsComponent::modifyStatValue(Stats stat, int modifier) //modifier is basically there so the modifyfuncs in the components know if stats should be increased or decreased -{ - switch (stat) - { - case Stats::MOVEMENT_SPEED: - this->entity->getComponent().modifySpeed(modifier); - break; - case Stats::ATTACK_SPEED: - // this->entity->getComponent().modifyAtkSpeed(modifier); - break; - default: break; - } -} - -void StatEffectsComponent::resetStatValue(Stats stat) -{ - switch (stat) - { - case Stats::MOVEMENT_SPEED: - this->entity->getComponent().resetSpeedMod(); - break; - case Stats::ATTACK_SPEED: - // this->entity->getComponent().resetAtkSpeedMod(); - break; - default: break; - } +void StatEffectsComponent::addEffect(uint32_t duration, std::function resetFunction) { + effects.push_back({duration, resetFunction}); } \ No newline at end of file diff --git a/src/TextureManager.cpp b/src/TextureManager.cpp index 3e46d46..02707a7 100644 --- a/src/TextureManager.cpp +++ b/src/TextureManager.cpp @@ -1,26 +1,62 @@ #include "TextureManager.h" -#include #include #include +#include #include "GameInternal.h" -SDL_Texture* TextureManager::loadTexture(const char* fileName) -{ - auto it = this->texture_cache.find(fileName); - if (it != this->texture_cache.end()) { - return it->second; - } - auto texture = IMG_LoadTexture(this->manager->getGame()->renderer, fileName); - if (texture == NULL) throw std::runtime_error(std::string("Couldn't load texture '") + fileName + "'"); - this->texture_cache.emplace(std::string(fileName), texture); - printf("Loaded texture at '%s'\n", fileName); - return texture; + +void TextureManager::addSingleTexture(Textures texture, const char* filePath) { + auto sdlTexture = IMG_LoadTexture(VEGO_Game().renderer, filePath); + + if (sdlTexture == nullptr) + throw std::runtime_error(std::string("Couldn't load texture '") + filePath + "'"); + + SDL_SetTextureScaleMode(sdlTexture, this->scaleMode); // linear scaling results in blurry images + this->texture_cache.emplace(texture, sdlTexture); + if (filePath != nullptr) { + this->texture_references.emplace(texture, std::string(filePath)); + } + std::cout << "Loaded texture at " << filePath << std::endl; } -void TextureManager::draw(SDL_Renderer* renderer, SDL_Texture* texture, SDL_Rect src, SDL_Rect dest, bool flipped) +void TextureManager::addTextures(const std::map &textures) { + for (auto texture : textures) { + addSingleTexture(texture.first, texture.second); + } +} + + +SDL_Texture* TextureManager::loadTexture(Textures texture) { + auto it = this->texture_cache.find(texture); + + if (it != this->texture_cache.end()) + return it->second; + + std::cout << "ERROR: Couldn't load texture!" << std::endl; + return nullptr; +} + +void TextureManager::draw(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect src, SDL_FRect dest, bool flipped) { - SDL_RendererFlip flip = flipped ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE; - SDL_RenderCopyEx(renderer, texture, &src, &dest, 0, NULL, flip); -} \ No newline at end of file + SDL_FlipMode flip = flipped ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE; + SDL_RenderTextureRotated(renderer, texture, &src, &dest, 0, NULL, flip); +} + +SDL_Texture* TextureManager::loadMapTileTexture(const char *path) { + + //returns tile if it exists already + if(mapTile_texture_cache.contains(std::string(path))) + return mapTile_texture_cache.find(std::string(path))->second; + + auto newTexture = IMG_LoadTexture(VEGO_Game().renderer, path); + + if (newTexture == nullptr) + throw std::runtime_error(std::string("Couldn't load texture '") + path + "'"); + + SDL_SetTextureScaleMode(newTexture, this->scaleMode); // linear scaling results in blurry images + this->mapTile_texture_cache.emplace(std::string(path), newTexture); + + return newTexture; +} diff --git a/src/TileComponent.cpp b/src/TileComponent.cpp index 4c33bf7..324c621 100644 --- a/src/TileComponent.cpp +++ b/src/TileComponent.cpp @@ -5,9 +5,9 @@ #include "Entity.h" #include "TransformComponent.h" #include "SpriteComponent.h" -#include "TileComponent.h" -TileComponent::TileComponent(int x, int y, int w, int h, int id, const std::map>* textureDict) + +TileComponent::TileComponent(int x, int y, int w, int h, int id, const std::map>* textureDict) { this->tileRect.x = x; this->tileRect.y = y; @@ -22,8 +22,7 @@ TileComponent::TileComponent(int x, int y, int w, int h, int id, const std::map< } this->collision = it->second.second; - this->tileName = it->second.first; - this->path = it->second.first.data(); + this->texture = it->second.first; } void TileComponent::init() @@ -31,7 +30,7 @@ void TileComponent::init() this->entity->addComponent(this->tileRect.x, this->tileRect.y, this->tileRect.w, this->tileRect.h, 1); this->transform = &entity->getComponent(); - this->entity->addComponent(this->path, 0); + this->entity->addComponent(this->texture, 0); this->sprite = &entity->getComponent(); } diff --git a/src/TransformComponent.cpp b/src/TransformComponent.cpp index e8e299b..a67dd1d 100644 --- a/src/TransformComponent.cpp +++ b/src/TransformComponent.cpp @@ -9,26 +9,16 @@ #include #include #include +#include #include "SoundManager.h" -TransformComponent::TransformComponent() -{ - position.zero(); -} - TransformComponent::TransformComponent(int scale) { position.zero(); this->scale = scale; } -TransformComponent::TransformComponent(float x, float y) -{ - this->position.x = x; - this->position.y = y; -} - TransformComponent::TransformComponent(float x, float y, int scale) { this->position.x = x; @@ -50,12 +40,15 @@ void TransformComponent::init() direction.zero(); } -void TransformComponent::update() +void TransformComponent::update(uint_fast16_t diffTime) { + direction.x = direction.x > 0 ? 1 : direction.x < 0 ? -1 : 0; + direction.y = direction.y > 0 ? 1 : direction.y < 0 ? -1 : 0; + float multiplier = direction.x != 0 && direction.y != 0 ? 0.707 : 1; // normalizes vector; only works if directions are in increments of 45° Vector2D positionChange( - direction.x * this->getSpeed() * multiplier, - direction.y * this->getSpeed() * multiplier + direction.x * this->getSpeed() * multiplier * diffTime * (1.f/1000), + direction.y * this->getSpeed() * multiplier * diffTime * (1.f/1000) ); if (this->entity->hasGroup((size_t)Entity::GroupLabel::PLAYERS)){ @@ -65,9 +58,11 @@ void TransformComponent::update() position += positionChange; } -void TransformComponent::modifySpeed(int8_t modifier) -{ - this->speedMod += modifier; +int TransformComponent::getSpeed() +{ + return (this->entity->hasComponent() + ? this->entity->getComponent().getEntry("speed").value_or(0) + : 0); } void TransformComponent::setPositionAfterCollision(Vector2D& positionChange) diff --git a/src/Vector2D.cpp b/src/Vector2D.cpp index 6368ff3..530e316 100644 --- a/src/Vector2D.cpp +++ b/src/Vector2D.cpp @@ -1,5 +1,5 @@ #include "Vector2D.h" -#include "SDL_rect.h" +#include Vector2D::Vector2D() { diff --git a/src/_Init.cpp b/src/_Init.cpp new file mode 100644 index 0000000..1ea2523 --- /dev/null +++ b/src/_Init.cpp @@ -0,0 +1,49 @@ +#include "SDL3/SDL_init.h" +#include +#define SDL_MAIN_USE_CALLBACKS +#include + +#include +#include + +#include "VEGO.h" +#include "Entity.h" +#include "GameInternal.h" +#include "Constants.h" + +GameInternal* vego::game = nullptr; + +SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { + srand(time(NULL)); + bool playing = true; + + *appstate = vego::game = new GameInternal(); + + return vego::game->init(); +} + +SDL_AppResult SDL_AppIterate(void *appstate) { + if (!vego::game->isRunning()) { + return SDL_APP_SUCCESS; + } + + //vego::game->handleEvents(); // bad + + Uint64 frameStart = SDL_GetTicks(); + + vego::game->update(frameStart); + vego::game->render(); + + int frameTime = SDL_GetTicks() - frameStart; + + return SDL_APP_CONTINUE; +} + +// triggers upon every event +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { + return vego::game->handleEvent(event); +} + +void SDL_AppQuit(void *appstate, SDL_AppResult result) { + vego::game->clean(); +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index da7f8a6..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include - -#include "VEGO.h" -#include "Entity.h" -#include "GameInternal.h" -#include "Constants.h" - -GameInternal* vego::game = nullptr; - -int main(int argc, char* argv[]) -{ - srand(time(NULL)); - bool playing = true; - - const int frameDelay = 1000 / FPS; - - Uint32 frameStart; - int frameTime; - - vego::game = new GameInternal(); - - vego::game->init("No_Name_Chicken_Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_SIZE_WIDTH, SCREEN_SIZE_HEIGHT, false); - while (vego::game->isRunning()) { - frameStart = SDL_GetTicks(); - - vego::game->handleEvents(); - vego::game->update(); - vego::game->render(); - - frameTime = SDL_GetTicks() - frameStart; - - if (frameDelay > frameTime) { - SDL_Delay(frameDelay - frameTime); - } - } - - vego::game->clean(); - - return 0; -} \ No newline at end of file