diff --git a/CMakeLists.txt b/CMakeLists.txt index 424da05..d2aad18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,30 @@ cmake_minimum_required(VERSION 2.8.9) project (sago_multi_scrambler_puzzle2) find_package(Boost COMPONENTS program_options REQUIRED) -add_executable(sago_multi_scrambler_puzzle2 src/sago_multi_scrambler_puzzle2.cpp) + +#Setup SDL2 +find_package(SDL2 REQUIRED CONFIG) +include_directories(${SDL2_INCLUDE_DIRS}) + +#Setup things that use pkg-config +find_package(PkgConfig REQUIRED) +pkg_search_module(SDL2MIXER REQUIRED SDL2_mixer) +include_directories(${SDL2MIXER_INCLUDE_DIRS}) +pkg_search_module(SDL2IMAGE REQUIRED SDL2_image) +include_directories(${SDL2IMAGE_INCLUDE_DIRS}) +pkg_search_module(SDL2TTF REQUIRED SDL2_ttf) +include_directories(${SDL2TTF_INCLUDE_DIRS}) +pkg_search_module(SDL2GFX REQUIRED SDL2_gfx) +include_directories(${SDL2GFX_INCLUDE_DIRS}) + + +include_directories(SYSTEM "src/Libs/include") + + +file(GLOB SOURCES "src/*.cpp" "src/*/*.cpp" "src/*/*/*.cpp" "src/Libs/*.c") + +add_executable(sago_multi_scrambler_puzzle2 ${SOURCES}) TARGET_LINK_LIBRARIES( sago_multi_scrambler_puzzle2 ${Boost_LIBRARIES} ) +target_link_libraries( sago_multi_scrambler_puzzle2 ${SDL2_LIBRARIES}) +target_link_libraries( sago_multi_scrambler_puzzle2 physfs) +target_link_libraries( sago_multi_scrambler_puzzle2 ${SDL2MIXER_LIBRARIES} ${SDL2IMAGE_LIBRARIES} ${SDL2TTF_LIBRARIES} ${SDL2GFX_LIBRARIES}) \ No newline at end of file diff --git a/data/textures/fallback.png b/data/textures/fallback.png new file mode 100644 index 0000000..a4ca1d5 Binary files /dev/null and b/data/textures/fallback.png differ diff --git a/src/sago/GameStateInterface.hpp b/src/sago/GameStateInterface.hpp new file mode 100644 index 0000000..3cb0067 --- /dev/null +++ b/src/sago/GameStateInterface.hpp @@ -0,0 +1,56 @@ +/* +Copyright (c) 2016 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef GAMESTATEINTERFACE_HPP +#define GAMESTATEINTERFACE_HPP + +#include "SDL.h" + +namespace sago { + +class GameStateInterface { +public: + /** + * Is the state active. If this returns false then the State-manager will pop the state object + * @return true if active + */ + virtual bool IsActive() = 0; + + /** + * Tells the state to draw itself to target + * @param target The RenderWindow to draw to + */ + virtual void Draw(SDL_Renderer* target) = 0; + + virtual void ProcessInput(const SDL_Event& event, bool &processed) = 0; + + virtual void Update() {} + + virtual ~GameStateInterface() {} +}; + +} //sago + +#endif /* GAMESTATEINTERFACE_HPP */ + diff --git a/src/sago/LICENSE b/src/sago/LICENSE new file mode 100644 index 0000000..fe6d772 --- /dev/null +++ b/src/sago/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/src/sago/Makefile.sago b/src/sago/Makefile.sago new file mode 100644 index 0000000..700f529 --- /dev/null +++ b/src/sago/Makefile.sago @@ -0,0 +1,4 @@ + + +SAGO_BASE_LIBS+= -lphysfs +SAGO_O_FILES+= sago/SagoDataHolder.o sago/SagoSprite.o sago/SagoSpriteHolder.o sago/SagoMisc.o \ No newline at end of file diff --git a/src/sago/SagoDataHolder.cpp b/src/sago/SagoDataHolder.cpp new file mode 100644 index 0000000..9fc5a5f --- /dev/null +++ b/src/sago/SagoDataHolder.cpp @@ -0,0 +1,307 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "SagoDataHolder.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "SagoMiscSdl2.hpp" +#include "SagoMisc.hpp" + +#if PHYSFS_VER_MAJOR < 3 +#define PHYSFS_readBytes(X,Y,Z) PHYSFS_read(X,Y,1,Z) +#define PHYSFS_writeBytes(X,Y,Z) PHYSFS_write(X,Y,1,Z) +#endif + +namespace sago { + +struct SagoDataHolder::SagoDataHolderData { + std::map textures; + std::map > fonts; //font, ptsize + std::map music; + std::map sounds; + std::vector rwOpsToFree; + std::vector> dataToFree; + bool verbose = false; + Uint64 version = 1; + SDL_Renderer* renderer = nullptr; +}; + +static void printFileWeLoad(const std::string& value) { + std::cout << "Loading " << value << "\n"; +} + +SagoDataHolder::SagoDataHolder() { + data = new SagoDataHolderData(); +} + +SagoDataHolder::SagoDataHolder(SDL_Renderer* renderer) { + data = new SagoDataHolderData(); + data->renderer = renderer; +} + +void SagoDataHolder::invalidateAll(SDL_Renderer* renderer) { + invalidateAll(); + data->renderer = renderer; +} + +void SagoDataHolder::invalidateAll() { + data->version++; + for (auto& item : data->textures) { + SDL_DestroyTexture(item.second); + } + data->textures.clear(); + for (auto& item : data->music) { + Mix_FreeMusic(item.second); + } + data->music.clear(); + for (auto& item : data->sounds) { + Mix_FreeChunk(item.second); + } + data->sounds.clear(); + for (auto& item : data->fonts) { + for (auto& item2 : item.second) { + TTF_CloseFont(item2.second); + } + } + data->fonts.clear(); + for (auto& item : data->rwOpsToFree) { + SDL_FreeRW(item); + } + data->rwOpsToFree.clear(); +} + +SagoDataHolder::~SagoDataHolder() { + invalidateAll(); + delete data; +} + +SDL_Texture* SagoDataHolder::getTexturePtr(const std::string& textureName) const { + if (!data->renderer) { + throw std::runtime_error("SagoDataHolder used before setting the renderer"); + } + SDL_Texture* ret = data->textures[textureName]; + if (ret) { + return ret; + } + std::string path = "textures/"+textureName+".png"; + if (data->verbose) { + printFileWeLoad(path); + } + if (!PHYSFS_exists(path.c_str())) { + sago::SagoFatalErrorF("getTextureFailed - Texture does not exist: %s", path.c_str()); + } + unsigned int m_size = 0; + std::unique_ptr m_data; + ReadBytesFromFile(path.c_str(), m_data, m_size); + SDL_RWops* rw = SDL_RWFromMem (m_data.get(), m_size); + //The above might fail an return null. + if (!rw) { + std::cerr << "Error. Corrupt data file!\n"; + return NULL; + } + SDL_Surface* surface = IMG_Load_RW(rw,true); + + ret = SDL_CreateTextureFromSurface(data->renderer, surface); + + if (!ret) { + std::cerr << "getTextureFailed to load " << path << "\n"; + } + SDL_FreeSurface(surface); + data->textures[textureName] = ret; + return ret; +} + +TTF_Font* SagoDataHolder::getFontPtr(const std::string& fontName, int ptsize) const { + TTF_Font* ret = data->fonts[fontName][ptsize]; + if (ret) { + return ret; + } + std::string path = "fonts/"+fontName+".ttf"; + if (data->verbose) { + printFileWeLoad(path); + } + if (!PHYSFS_exists(path.c_str())) { + std::cerr << "getFontPtr - Font does not exists: " << path << "\n"; + return ret; + } + unsigned int m_size = 0; + std::unique_ptr m_data; + ReadBytesFromFile(path.c_str(), m_data, m_size); + + SDL_RWops* rw = SDL_RWFromMem (m_data.get(), m_size); + + //The above might fail an return null. + if (!rw) { + std::cerr << "Error: Corrupt data file!\n"; + return ret; + } + + ret = TTF_OpenFontRW(rw, SDL_FALSE, ptsize); + if (!ret) { + std::cerr << "Error openening font: " << fontName << " because: " << TTF_GetError() << "\n"; + } + data->fonts[fontName][ptsize] = ret; + data->dataToFree.push_back(std::move(m_data)); + data->rwOpsToFree.push_back(rw); + return ret; +} + +Mix_Music* SagoDataHolder::getMusicPtr(const std::string& musicName) const { + Mix_Music* ret = data->music[musicName]; + if (ret) { + return ret; + } + std::string path = "music/"+musicName+".ogg"; + if (data->verbose) { + printFileWeLoad(path); + } + if (!PHYSFS_exists(path.c_str())) { + std::cerr << "getMusicPtr - Music file does not exists: " << path << "\n"; + return ret; + } + unsigned int m_size = 0; + std::unique_ptr m_data; + ReadBytesFromFile(path.c_str(), m_data, m_size); + SDL_RWops* rw = SDL_RWFromMem (m_data.get(), m_size); + + //The above might fail an return null. + if (!rw) { + std::cerr << "Error. Corrupt data file!\n"; + return NULL; + } + + ret = Mix_LoadMUS_RW(rw, SDL_TRUE); //SDL_TRUE causes rw to be freed + + if (!ret) { + std::cerr << "getMusicPtr to load " << path << " because: " << Mix_GetError() << "\n"; + } + data->music[musicName] = ret; + data->dataToFree.push_back(std::move(m_data)); + return ret; +} + + +Mix_Chunk* SagoDataHolder::getSoundPtr(const std::string& soundName) const { + Mix_Chunk* ret = data->sounds[soundName]; + if (ret) { + return ret; + } + std::string path = "sounds/"+soundName+".ogg"; + if (data->verbose) { + printFileWeLoad(path); + } + if (!PHYSFS_exists(path.c_str())) { + std::cerr << "getSoundPtr - Sound file does not exists: " << path << "\n"; + return ret; + } + unsigned int m_size = 0; + std::unique_ptr m_data; + ReadBytesFromFile(path.c_str(), m_data, m_size); + SDL_RWops* rw = SDL_RWFromMem (m_data.get(), m_size); + + //The above might fail an return null. + if (!rw) { + std::cerr << "Error. Corrupt data file!\n"; + return NULL; + } + + ret = Mix_LoadWAV_RW(rw, SDL_TRUE); + data->sounds[soundName] = ret; + data->dataToFree.push_back(std::move(m_data)); + return ret; +} + +void SagoDataHolder::setVerbose(bool value) { + data->verbose = value; +} + +Uint64 SagoDataHolder::getVersion() const { + return data->version; +} + +TextureHandler::TextureHandler(const SagoDataHolder* holder, const std::string &textureName) { + this->holder = holder; + this->version = 0; + this->textureName = textureName; + this->data = nullptr; +} + +SDL_Texture* TextureHandler::get() { + if (version != holder->getVersion()) { + //The holder has been invalidated + this->data = this->holder->getTexturePtr(textureName); + } + return data; +} + + +MusicHandler::MusicHandler(const SagoDataHolder* holder, const std::string& musicName) { + this->holder = holder; + this->version = 0; + this->musicName = musicName; + this->data = nullptr; +} + +Mix_Music* MusicHandler::get() { + if (version != holder->getVersion()) { + //The holder has been invalidated + this->data = this->holder->getMusicPtr(musicName); + } + return data; +} + +SoundHandler::SoundHandler(const SagoDataHolder* holder, const std::string& soundName) { + this->holder = holder; + this->version = 0; + this->soundName = soundName; + this->data = nullptr; +} + +Mix_Chunk* SoundHandler::get() { + if (version != holder->getVersion()) { + //The holder has been invalidated + this->data = this->holder->getSoundPtr(soundName); + } + return data; +} + + +TextureHandler SagoDataHolder::getTextureHandler(const std::string &textureName) const { + return TextureHandler(this, textureName); +} + +MusicHandler SagoDataHolder::getMusicHandler(const std::string &musicName) const { + return MusicHandler(this, musicName); +} + +SoundHandler SagoDataHolder::getSoundHandler(const std::string &soundName) const { + return SoundHandler(this, soundName); +} + +} //name space sago diff --git a/src/sago/SagoDataHolder.hpp b/src/sago/SagoDataHolder.hpp new file mode 100644 index 0000000..9fbd9cd --- /dev/null +++ b/src/sago/SagoDataHolder.hpp @@ -0,0 +1,132 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "SDL.h" +#include //Used for sound & music +#include //To load PNG images! +#include +#include //Abstract file system. To use containers +#include + +#ifndef TEXTUREHOLDER_HPP +#define TEXTUREHOLDER_HPP + +namespace sago { + +class SagoDataHolder; + +class TextureHandler { +public: + TextureHandler() {}; + TextureHandler(const SagoDataHolder* holder, const std::string &textureName); + SDL_Texture* get(); +private: + std::string textureName; + const SagoDataHolder* holder = nullptr; + SDL_Texture* data = nullptr; + Uint64 version = 0; +}; + + +class MusicHandler final { +public: + MusicHandler() {}; + MusicHandler(const SagoDataHolder* holder, const std::string &musicName); + Mix_Music* get(); +private: + std::string musicName; + const SagoDataHolder* holder = nullptr; + Mix_Music* data = nullptr; + Uint64 version = 0; +}; + + +class SoundHandler final { +public: + SoundHandler() {}; + SoundHandler(const SagoDataHolder* holder, const std::string &soundName); + Mix_Chunk* get(); +private: + std::string soundName; + const SagoDataHolder* holder = nullptr; + Mix_Chunk* data = nullptr; + Uint64 version = 0; +}; + +class SagoDataHolder final { +public: + /** + * The renderer must be set before requesting a texture. + * If the constructor without elements is used then invalidateAll(SDL_Renderer*) must be called before getTexturePtr + */ + SagoDataHolder(); + explicit SagoDataHolder(SDL_Renderer* renderer); + + /** + * Return a pointer to the given texture. The pointer is valid for the lifetime of SagoDataHolder object it was taken from or until invalidateAll is called. + * @param textureName Name of the texture + * @return Pointer to the loaded texture + */ + SDL_Texture* getTexturePtr(const std::string &textureName) const; + + TextureHandler getTextureHandler(const std::string &textureName) const; + TTF_Font* getFontPtr(const std::string &fontName, int ptsize) const; + Mix_Music* getMusicPtr(const std::string &musicName) const; + MusicHandler getMusicHandler(const std::string &musicName) const; + Mix_Chunk* getSoundPtr(const std::string &soundName) const; + SoundHandler getSoundHandler(const std::string &soundName) const; + void setVerbose(bool value); + + /** + * Invalidates all pointers returned by any of the get variables + */ + void invalidateAll(); + + /** + * Invalidates all pointers returned by any of the get variables. + * Also sets a new renderer. + * + * Setting a new renderer might cause all old textures to no longer match the renderer format. + */ + void invalidateAll(SDL_Renderer* renderer); + + /** + * The version number. Changes everytime the pointers are invalidated. + * Can be used to determen if it is neccecary to get a new pointer. + * @return A globally unique number. + */ + Uint64 getVersion() const; + + ~SagoDataHolder(); +private: + SagoDataHolder(const SagoDataHolder& base) = delete; + SagoDataHolder& operator=(const SagoDataHolder& base) = delete; + struct SagoDataHolderData; + mutable SagoDataHolderData *data; +}; + +} //namespace sago + +#endif /* TEXTUREHOLDER_HPP */ + diff --git a/src/sago/SagoMisc.cpp b/src/sago/SagoMisc.cpp new file mode 100644 index 0000000..ce21e09 --- /dev/null +++ b/src/sago/SagoMisc.cpp @@ -0,0 +1,118 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "SagoMisc.hpp" +#include +#include +#include +#include + +#if PHYSFS_VER_MAJOR < 3 +#define PHYSFS_readBytes(X,Y,Z) PHYSFS_read(X,Y,1,Z) +#define PHYSFS_writeBytes(X,Y,Z) PHYSFS_write(X,Y,1,Z) +#endif + +namespace sago { + + +std::vector GetFileList(const char* dir) { + std::vector ret; + char** rc = PHYSFS_enumerateFiles(dir); + for (char** i = rc; *i != NULL; i++) { + ret.push_back(*i); + } + PHYSFS_freeList(rc); + return ret; +} + +bool FileExists(const char* filename) { + return PHYSFS_exists(filename); +} + +void ReadBytesFromFile(const char* filename, std::unique_ptr& dest, unsigned int& bytes) { + bytes = 0; + if (!PHYSFS_exists(filename)) { + std::cerr << "ReadBytesFromFile - File does not exists: " << filename << "\n"; + return; + } + PHYSFS_file* myfile = PHYSFS_openRead(filename); + unsigned int m_size = PHYSFS_fileLength(myfile); + std::unique_ptr m_data(new char[m_size]); + int length_read = PHYSFS_readBytes (myfile, m_data.get(), m_size); + if (length_read != (int)m_size) { + PHYSFS_close(myfile); + std::cerr << "Error: Curropt data file: " << filename << "\n"; + return; + } + PHYSFS_close(myfile); + std::swap(m_data, dest); + bytes = m_size; +} + +std::string GetFileContent(const char* filename) { + std::string ret; + if (!PHYSFS_exists(filename)) { + std::cerr << "GetFileContent - File does not exists: " << filename << "\n"; + return ret; + } + unsigned int m_size = 0; + std::unique_ptr m_data; + ReadBytesFromFile(filename, m_data, m_size); + //Now create a std::string + ret = std::string(m_data.get(), m_data.get()+m_size); + return ret; +} + +static void CreatePathToFile(const std::string& path) { + size_t end_of_path = path.find_last_of("/"); + if (end_of_path == std::string::npos) { + //No path + return; + } + std::string path2dir = path.substr(0, end_of_path); + PHYSFS_mkdir(path2dir.c_str()); +} + +void WriteFileContent(const char* filename, const std::string& content) { + CreatePathToFile(filename); + PHYSFS_file* myfile = PHYSFS_openWrite(filename); + if (!myfile) { +#if PHYSFS_VER_MAJOR > 2 + PHYSFS_ErrorCode code = PHYSFS_getLastErrorCode(); + std::cerr << "Failed to open file for writing, " << PHYSFS_getErrorByCode(code) << " (" << code << ")\n"; +#else + std::cerr << "Failed to open file for writing, " << PHYSFS_getLastError() << "\n"; +#endif + return; + } + PHYSFS_writeBytes(myfile, content.c_str(), sizeof(char)*content.length()); + PHYSFS_close(myfile); +} + +long int StrToLong(const char* c_string) { + auto ret = strtol(c_string, nullptr, 10); + return ret; +} + +} diff --git a/src/sago/SagoMisc.hpp b/src/sago/SagoMisc.hpp new file mode 100644 index 0000000..347bc75 --- /dev/null +++ b/src/sago/SagoMisc.hpp @@ -0,0 +1,83 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef SAGOMISC_HPP +#define SAGOMISC_HPP + +#include +#include +#include + +namespace sago { + + /** + * Returns a vector with all filenames in a given directory. + * PHYSFS must be setup before hand. The directory is relative to the PHYSFS base + * @param dir The directory to list + * @return A vector with the filenames in the given directory. If empty the directory was empty or did not exist + */ + std::vector GetFileList(const char* dir); + + /** + * Reads an entire file into memory. + * PHYSFS must be setup before hand + * @param filename The file to read + * @param dest The unique pointer in which the bytes will be written + * @param bytes Number of bytes written + * @return The content of the file. If empty either the file was empty, did not exist or could not be opened + */ + void ReadBytesFromFile(const char* filename, std::unique_ptr& dest, unsigned int& bytes); + + /** + * Reads an entire file into memory. + * PHYSFS must be setup before hand + * @param filename The file to read + * @return The content of the file. If empty either the file was empty, did not exist or could not be opened + */ + std::string GetFileContent(const char* filename); + + /** + * Reads an entire file into memory. + * PHYSFS must be setup before hand + * @param filename The file to read + * @return The content of the file. If empty either the file was empty, did not exist or could not be opened + */ + inline std::string GetFileContent(const std::string& filename) { return GetFileContent(filename.c_str()); }; + + bool FileExists(const char* filename); + + void WriteFileContent(const char* filename, const std::string& content); + + /** + * This functions converts a string on a best effort basis + * Unlike atol this does NOT cause undefined behavior if out of range + * @param c_string A string that may contain a number + * @return A number between LONG_MIN and LONG_MAX (both inclusive) + */ + long int StrToLong(const char* c_string); + +} //namespace sago + +#endif /* SAGOMISC_HPP */ + diff --git a/src/sago/SagoMiscSdl2.cpp b/src/sago/SagoMiscSdl2.cpp new file mode 100644 index 0000000..8a3d294 --- /dev/null +++ b/src/sago/SagoMiscSdl2.cpp @@ -0,0 +1,54 @@ +/* +Copyright (c) 2016 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "SagoMiscSdl2.hpp" +#include "SDL.h" + +void sago::SagoFatalError(const char* errorMsg) { + const SDL_MessageBoxButtonData buttons[] = { + { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Ok" }, + }; + const SDL_MessageBoxData messageboxdata = { + SDL_MESSAGEBOX_INFORMATION, /* .flags */ + nullptr, /* .window */ + "Fatal error", /* .title */ + errorMsg, /* .message */ + SDL_arraysize(buttons), /* .numbuttons */ + buttons, /* .buttons */ + nullptr /* .colorScheme */ + }; + int buttonid; + SDL_ShowMessageBox(&messageboxdata, &buttonid); + abort(); +} + +void sago::SagoFatalErrorF(const char* fmt, ...) { + char buffer[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, args); + SagoFatalError(buffer); + va_end(args); +} + diff --git a/src/sago/SagoMiscSdl2.hpp b/src/sago/SagoMiscSdl2.hpp new file mode 100644 index 0000000..1bc115b --- /dev/null +++ b/src/sago/SagoMiscSdl2.hpp @@ -0,0 +1,46 @@ +/* +Copyright (c) 2016 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef SAGOMISCSDL2_HPP +#define SAGOMISCSDL2_HPP + +namespace sago { + + /** + * Writes an error message to the screen and aborts the program + * @param errorMsg The message displayed in a pop-up box to the user. + */ + void SagoFatalError(const char* errorMsg); + + /** + * Writes an error message to the screen and aborts the program + * @param fmt A printf-style format string + * @param ... Parameters to the format string + */ + void SagoFatalErrorF(const char* fmt, ...) __attribute__ ((format (printf, 1, 2))); + +} + +#endif /* SAGOMISCSDL2_HPP */ + diff --git a/src/sago/SagoSprite.cpp b/src/sago/SagoSprite.cpp new file mode 100644 index 0000000..c116e3e --- /dev/null +++ b/src/sago/SagoSprite.cpp @@ -0,0 +1,176 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "SagoSprite.hpp" +#include + +#ifndef M_PI +// M_PI is a custom extension that most C++ toolchains provide. Out Windows compiler does not provide it. +# define M_PI 3.14159265358979323846 +#endif + +namespace sago { + +struct SagoSprite::SagoSpriteData { + TextureHandler tex; + SDL_Rect imgCord = {}; + SDL_Rect origin = {}; + int aniFrames = 0; + int aniFrameTime = 0; +}; + +SagoSprite::SagoSprite() { + data = new SagoSpriteData(); +} + +SagoSprite::SagoSprite(const SagoDataHolder& texHolder, const std::string& texture,const SDL_Rect& initImage,const int animationFrames, const int animationFrameLength) { + data = new SagoSpriteData(); + data->tex = texHolder.getTextureHandler(texture); + data->imgCord = initImage; + data->aniFrames = animationFrames; + data->aniFrameTime = animationFrameLength; +} + +SagoSprite::SagoSprite(const SagoSprite& base) : data(new SagoSpriteData(*base.data)) { + +} + +SagoSprite& SagoSprite::operator=(const SagoSprite& base) { + *data = *base.data; + return *this; +} + +SagoSprite::~SagoSprite() { + delete data; +} + +void SagoSprite::Draw(SDL_Renderer* target, Sint32 frameTime, int x, int y) const { + DrawScaled(target, frameTime, x, y, data->imgCord.w, data->imgCord.h); +} + +void SagoSprite::DrawRotated(SDL_Renderer* target, Sint32 frameTime, int x, int y, const double angleRadian) const { + SDL_Point center = {this->data->origin.x, this->data->origin.y}; + DrawScaledAndRotated(target, frameTime, x, y, data->imgCord.w, data->imgCord.h, angleRadian, ¢er, SDL_FLIP_NONE); +} + +void SagoSprite::DrawScaled(SDL_Renderer* target, Sint32 frameTime, int x, int y, int w, int h) const { + DrawScaledAndRotated(target, frameTime, x, y, w, h, 0.0, nullptr, SDL_FLIP_NONE); +} + +void SagoSprite::DrawScaledAndRotated(SDL_Renderer* target, Sint32 frameTime, int x, int y, int w, int h, const double angleRadian, const SDL_Point* center, const SDL_RendererFlip flip) const { + if (!data->tex.get()) { + std::cerr << "Texture is null!\n"; + } + SDL_Rect rect = data->imgCord; + rect.x+=rect.w*((frameTime/data->aniFrameTime)%data->aniFrames); + SDL_Rect pos = rect; + pos.x = x - this->data->origin.x; + pos.y = y - this->data->origin.y; + if (w > 0) { + pos.w = w; + } + if (h > 0) { + pos.h = h; + } + double angleDegress = angleRadian/M_PI*180.0; + SDL_RenderCopyEx(target, data->tex.get(), &rect, &pos, angleDegress, center, flip); +} + +void SagoSprite::Draw(SDL_Renderer* target, Sint32 frameTime, int x, int y, const SDL_Rect& part) const { + SDL_Rect rect = data->imgCord; + rect.x+=rect.w*((frameTime/data->aniFrameTime)%data->aniFrames); + rect.x += part.x; + rect.y += part.y; + rect.w = part.w; + rect.h = part.h; + SDL_Rect pos = rect; + pos.x = x - this->data->origin.x; + pos.y = y - this->data->origin.y; + SDL_RenderCopy(target, data->tex.get(), &rect, &pos); +} + +void SagoSprite::DrawProgressive(SDL_Renderer* target, float progress, int x, int y) const { + Sint32 frameNumber = progress*data->aniFrames; + Sint32 frameTime = frameNumber*data->aniFrameTime; + Draw(target, frameTime, x, y); +} + +void SagoSprite::DrawBounded(SDL_Renderer* target, Sint32 frameTime, int x, int y, const SDL_Rect& bounds) const { + SDL_Rect rect = data->imgCord; + rect.x+=rect.w*((frameTime/data->aniFrameTime)%data->aniFrames); + SDL_Rect pos = rect; + pos.x = x; + pos.y = y; + if (pos.x > bounds.x+bounds.w) { + return; + } + if (pos.y > bounds.y+bounds.h) { + return; + } + if (pos.x+pos.w < bounds.x) { + return; + } + if (pos.y+pos.h < bounds.y) { + return; + } + if (pos.x < bounds.x) { + Sint16 absDiff = bounds.x-pos.x; + pos.x+=absDiff; + rect.x+=absDiff; + pos.w-=absDiff; + rect.w-=absDiff; + } + if (pos.y < bounds.y) { + Sint16 absDiff = bounds.y-pos.y; + pos.y+=absDiff; + rect.y+=absDiff; + pos.h-=absDiff; + rect.h-=absDiff; + } + if (pos.x+pos.w > bounds.x+bounds.w) { + Sint16 absDiff = pos.x+pos.w-(bounds.x+bounds.w); + pos.w -= absDiff; + rect.w -= absDiff; + } + if (pos.y+pos.h > bounds.y+bounds.h) { + Sint16 absDiff = pos.y+pos.h-(bounds.y+bounds.h); + pos.h -= absDiff; + rect.h -= absDiff; + } + + SDL_RenderCopy(target, data->tex.get(), &rect, &pos); +} + +void SagoSprite::SetOrigin(const SDL_Rect& newOrigin) { + data->origin = newOrigin; +} + +int SagoSprite::GetWidth() const { + return data->imgCord.w; +} +int SagoSprite::GetHeight() const { + return data->imgCord.h; +} + +} //namespace sago diff --git a/src/sago/SagoSprite.hpp b/src/sago/SagoSprite.hpp new file mode 100644 index 0000000..742cf76 --- /dev/null +++ b/src/sago/SagoSprite.hpp @@ -0,0 +1,107 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef SAGOSPRITE_HPP +#define SAGOSPRITE_HPP + +#include "SagoDataHolder.hpp" + +namespace sago { + +class SagoSprite final { +public: + SagoSprite(); + SagoSprite(const SagoDataHolder &texHolder, const std::string &texture,const SDL_Rect& initImage,const int animationFrames, const int animationFrameLength); + + /** + * Draws the sprite to a given render window + * @param target The render window to draw on + * @param frameTime The time in milliseonds since gamestart. Used to determen the place in the animation + * @param x Place to draw the sprite + * @param y Place to draw the sprite + */ + void Draw(SDL_Renderer* target, Sint32 frameTime, int x, int y) const; + + /** + * Draws the sprite to a given render window + * @param target The render window to draw on + * @param frameTime The time in milliseonds since gamestart. Used to determen the place in the animation + * @param x Place to draw the sprite + * @param y Place to draw the sprite + * @param angleRadian Angle to rotate the sprite around origin before drawing + */ + void DrawRotated(SDL_Renderer* target, Sint32 frameTime, int x, int y, const double angleRadian) const; + + /** + * Draws part of the sprite to a given render window + * @param target The render window to draw on + * @param frameTime The time in milliseonds since gamestart. Used to determen the place in the animation + * @param x Place to draw the sprite + * @param y Place to draw the sprite + * @param part the part of the sprite that should be drawn. + */ + void Draw(SDL_Renderer* target, Sint32 frameTime, int x, int y, const SDL_Rect& part) const; + + /** + * Draws the wprite to the given renderer but makes sure to not draw outside th bounds given + * @param target The render window to draw on + * @param frameTime The time in milliseonds since gamestart. Used to determen the place in the animation + * @param x Place to draw the sprite + * @param y Place to draw the sprite + * @param bounds A recagular area that we must not draw outside. + */ + void DrawBounded(SDL_Renderer* target, Sint32 frameTime, int x, int y, const SDL_Rect& bounds) const; + + /** + * Draws the sprite to a given render window + * @param target The render window to draw on + * @param progress A float with value from 0.0f to 1.0f. Tells how far in the animation that we got + * @param x Place to draw the sprite + * @param y Place to draw the sprite + */ + void DrawProgressive(SDL_Renderer* target, float progress, int x, int y) const; + + void DrawScaled(SDL_Renderer* target, Sint32 frameTime, int x, int y, int w, int h) const; + void DrawScaledAndRotated(SDL_Renderer* target, Sint32 frameTime, int x, int y, int w, int h, + const double angleRadian, const SDL_Point* center, const SDL_RendererFlip flip) const; + + /** + * Set a different origin. Normally it is the top left cornor. But in some cases you might want to center the origin or tranform it for other reasons + * @param newOrigin the coordinates that should be the new origin. Call with {0,0} to reset to default + */ + void SetOrigin(const SDL_Rect& newOrigin); + SagoSprite(const SagoSprite& base); + SagoSprite& operator=(const SagoSprite& base); + int GetWidth() const; + int GetHeight() const; + ~SagoSprite(); +private: + struct SagoSpriteData; + SagoSpriteData *data; +}; + +} + +#endif /* SAGOSPRITE_HPP */ + diff --git a/src/sago/SagoSpriteHolder.cpp b/src/sago/SagoSpriteHolder.cpp new file mode 100644 index 0000000..94c4c1c --- /dev/null +++ b/src/sago/SagoSpriteHolder.cpp @@ -0,0 +1,155 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "SagoSpriteHolder.hpp" +#include "SagoMisc.hpp" +#include +#include +#include "rapidjson/document.h" +#include +#include +#include + + +namespace sago { + +struct SagoSpriteHolder::SagoSpriteHolderData { + const sago::SagoDataHolder* tex; + std::unordered_map> sprites; + const sago::SagoSprite* defaultSprite; + bool verbose = false; +}; + +SagoSpriteHolder::SagoSpriteHolder(const SagoDataHolder& texHolder) { + data = new SagoSpriteHolderData(); + try { + data->tex = &texHolder; + ReadSprites(); + data->defaultSprite = new sago::SagoSprite(texHolder,"fallback", {0,0,64,64},1,100); + } + catch (...) { + delete data; + } +} + +SagoSpriteHolder::~SagoSpriteHolder() { + delete data; +} + +static int getDefaultValue(const rapidjson::Value& value, const char* name, int defaultValue) { + assert(value.IsObject()); + const auto& t = value.GetObject().FindMember(name); + if (t != value.MemberEnd() && t->value.IsInt()) { + return t->value.GetInt(); + } + return defaultValue; +} + +static std::string getDefaultValue(const rapidjson::Value& value, const char* name, std::string defaultValue) { + assert(value.IsObject()); + const auto& t = value.GetObject().FindMember(name); + if (t != value.MemberEnd() && t->value.IsString()) { + defaultValue = t->value.GetString(); + } + return defaultValue; +} + +void SagoSpriteHolder::ReadSpriteFile(const std::string& filename) { + std::string fullfile = "sprites/"+filename; + std::string content = sago::GetFileContent(fullfile.c_str()); + rapidjson::Document document; + document.Parse(content.c_str()); + if ( !document.IsObject() ) { + std::cerr << "Failed to parse: " << fullfile << "\n"; + return; + } + for (auto& m : document.GetObject()) { + const std::string& spriteName = m.name.GetString(); + if (!m.value.IsObject()) { + if (spriteName[0] != '_') { + std::cerr << "Invalid sprite: " << spriteName << "\n"; + } + continue; + } + std::string textureName = getDefaultValue(m.value, "texture", "fallback"); + int topx = getDefaultValue(m.value, "topx", 0); + int topy = getDefaultValue(m.value, "topy",0); + int height = getDefaultValue(m.value, "height",0); + int width = getDefaultValue(m.value, "width",0); + int number_of_frames = getDefaultValue(m.value, "number_of_frames",1); + int frame_time = getDefaultValue(m.value, "frame_time",1); + SDL_Rect origin = {}; + origin.x = getDefaultValue(m.value, "originx",0); + origin.y = getDefaultValue(m.value, "originy",0); + if (number_of_frames < 1) { + number_of_frames = 1; + } + if (frame_time < 1) { + frame_time = 1; + } + std::shared_ptr ptr(new SagoSprite(*(data->tex),textureName, {topx,topy,width,height},number_of_frames,frame_time)); + ptr->SetOrigin(origin); + this->data->sprites[std::string(spriteName)] = ptr; + } +} + +void SagoSpriteHolder::ReadSprites() { + std::vector spritefiles = GetFileList("sprites"); + for (std::string& item : spritefiles ) { + if (boost::algorithm::ends_with(item,".sprite")) { + if (data->verbose) { + std::cout << "Found " << item << "\n"; + } + ReadSpriteFile(item); + } + else { + if (data->verbose) { + std::cout << "Ignoreing " << item << "\n"; + } + } + } +} + +void SagoSpriteHolder::ReadSprites(const std::vector& extra_sprites) { + for (std::string item : extra_sprites) { + item+=".sprite"; + ReadSpriteFile(item); + } +} + +const sago::SagoSprite& SagoSpriteHolder::GetSprite(const std::string& spritename) const { + std::unordered_map>::const_iterator got = data->sprites.find (spritename); + if ( got == data->sprites.end() ) { + return *data->defaultSprite; + } + else { + return *(got->second); + } +} + +const SagoDataHolder& SagoSpriteHolder::GetDataHolder() const { + return *data->tex; +} + +} diff --git a/src/sago/SagoSpriteHolder.hpp b/src/sago/SagoSpriteHolder.hpp new file mode 100644 index 0000000..be76b51 --- /dev/null +++ b/src/sago/SagoSpriteHolder.hpp @@ -0,0 +1,59 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef SAGOSPRITEHOLDER_HPP +#define SAGOSPRITEHOLDER_HPP + +#include "SagoDataHolder.hpp" +#include "SagoSprite.hpp" +#include + +namespace sago { + +class SagoSpriteHolder final { +public: + explicit SagoSpriteHolder(const SagoDataHolder &texHolder); + ~SagoSpriteHolder(); + /** + * Reads all the sprites from the "sprites" directory in alphabetical order + **/ + void ReadSprites(); + /** + * Reads an additional sprites. Used to append mod data. + **/ + void ReadSprites(const std::vector& extra_sprites); + const sago::SagoSprite& GetSprite(const std::string &spritename) const; + const SagoDataHolder& GetDataHolder() const; +private: + SagoSpriteHolder(const SagoSpriteHolder& base) = delete; + SagoSpriteHolder& operator=(const SagoSpriteHolder& base) = delete; + void ReadSpriteFile(const std::string &filename); + struct SagoSpriteHolderData; + SagoSpriteHolderData *data; +}; + +} + +#endif /* SAGOSPRITEHOLDER_HPP */ + diff --git a/src/sago/SagoTextBox.cpp b/src/sago/SagoTextBox.cpp new file mode 100644 index 0000000..2f98a20 --- /dev/null +++ b/src/sago/SagoTextBox.cpp @@ -0,0 +1,192 @@ +/* +Copyright (c) 2018 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "SagoTextBox.hpp" +#include "SagoTextField.hpp" +#include +#include +#include "utf8.h" +#include + +namespace sago { + +struct SagoTextBox::SagoTextBoxData { + const sago::SagoDataHolder* tex = nullptr; + std::string fontName = "freeserif"; + SDL_Color color = { 255, 255, 255, 0 }; + SDL_Color outlineColor = { 255, 255, 0, 0 }; + int fontSize = 16; + int outline = 0; + std::string text = ""; + std::string renderedText = ""; + std::vector lines; + int maxWidth = 0; +}; + +SagoTextBox::SagoTextBox() { + data = new SagoTextBoxData(); +} + +SagoTextBox::~SagoTextBox() { + delete data; +} + +void SagoTextBox::SetHolder(const SagoDataHolder* holder) { + data->tex = holder; +} + +void SagoTextBox::SetText(const char* text) { + data->text = text; +} + +void SagoTextBox::SetText(const std::string& text) { + data->text = text; +} + +void SagoTextBox::SetColor(const SDL_Color& color) { + data->color = color; +} + +void SagoTextBox::SetFont(const char* fontName) { + data->fontName = fontName; +} + +void SagoTextBox::SetFontSize(int fontSize) { + data->fontSize = fontSize; +} + +void SagoTextBox::SetOutline(int outlineSize, const SDL_Color& color) { + data->outline = outlineSize; + data->outlineColor = color; +} + +void SagoTextBox::SetMaxWidth(int width) { + data->maxWidth = width; +} + +const std::string& SagoTextBox::GetText() const { + return data->text; +} + +void SagoTextBox::AppendLineToCache(const std::string& text) { + data->lines.resize(data->lines.size()+1); + SagoTextField& tf = data->lines.back(); + tf.SetHolder(data->tex); + tf.SetFont(data->fontName.c_str()); + tf.SetFontSize(data->fontSize); + tf.SetColor(data->color); + tf.SetOutline(data->outline, data->outlineColor); + tf.SetText(text); +} + + +void SagoTextBox::SplitAndAppendLineToCache(TTF_Font* font, const std::string& text) { + int width = data->maxWidth; + TTF_SizeUTF8(font, text.c_str(),&width, nullptr); + if (data->maxWidth <= 0 || width <= data->maxWidth || text.length() == 1) { + AppendLineToCache(text); + return; + } + std::string::const_iterator splitLocation = text.begin()+1; + bool splitDone = false; + while (!splitDone) { + std::string::const_iterator nextSearchStart = splitLocation+1; + if (nextSearchStart == text.end()) { + splitDone = true; + continue; + } + std::string::const_iterator nextSpace = std::find(nextSearchStart, text.end(), ' '); + std::string attemptSubString(text.begin(), nextSpace); + TTF_SizeUTF8(font, attemptSubString.c_str(),&width, nullptr); + if (width <= data->maxWidth && nextSpace != text.end()) { + splitLocation = nextSpace; + } + else { + splitDone = true; + } + } + if (splitLocation == text.begin()+1) { + splitLocation = text.begin(); + utf8::advance(splitLocation, 1, text.end()); + splitDone = false; + while (!splitDone && splitLocation != text.end()) { + std::string::const_iterator nextSplit = splitLocation; + utf8::advance(nextSplit, 1, text.end()); + std::string attemptSubString(text.begin(), nextSplit); + TTF_SizeUTF8(font, attemptSubString.c_str(), &width, nullptr); + if (width <= data->maxWidth) { + splitLocation = nextSplit; + } + else { + splitDone = true; + } + } + } + std::string firstPart(text.begin(), splitLocation); + AppendLineToCache(firstPart); + while (splitLocation != text.end() && *splitLocation == ' ') { + //Trim spaces after an automatic line break. + ++splitLocation; + } + if (splitLocation == text.end()) { + return; + } + std::string secondPart(splitLocation, text.end()); + SplitAndAppendLineToCache(font, secondPart); +} + +void SagoTextBox::UpdateCache() { + if (!data->tex) { + std::cerr << "FATAL: SagoTextBox::UpdateCache - DataHolder not set!\n"; + abort(); + } + TTF_Font *font = data->tex->getFontPtr(data->fontName, data->fontSize); + const char delim = '\n'; + const std::string& s = data->text; + auto start = 0U; + auto end = s.find(delim); + data->lines.clear(); + while (end != std::string::npos) + { + const std::string& theSubString = s.substr(start, end - start); + SplitAndAppendLineToCache(font, theSubString); + start = end + 1; + end = s.find(delim, start); + } + SplitAndAppendLineToCache(font, s.substr(start, end)); + data->renderedText = data->text; +} + +void SagoTextBox::Draw(SDL_Renderer* target, int x, int y, SagoTextField::Alignment alignment ) { + if (data->text != data->renderedText) { + UpdateCache(); + } + TTF_Font *font = data->tex->getFontPtr(data->fontName, data->fontSize); + int lineSkip = TTF_FontLineSkip(font); + for (size_t i = 0; i < data->lines.size(); ++i) { + data->lines[i].Draw(target, x, y+i*lineSkip, alignment); + } +} + +} //namespace sago \ No newline at end of file diff --git a/src/sago/SagoTextBox.hpp b/src/sago/SagoTextBox.hpp new file mode 100644 index 0000000..c1df5cc --- /dev/null +++ b/src/sago/SagoTextBox.hpp @@ -0,0 +1,74 @@ +/* +Copyright (c) 2018 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef SAGOTEXTBOX_HPP +#define SAGOTEXTBOX_HPP + +#include "SagoDataHolder.hpp" +#include "SagoTextField.hpp" + +namespace sago { + +class SagoTextBox final { +public: + SagoTextBox(); + ~SagoTextBox(); + void SetHolder(const SagoDataHolder* holder); + void SetText(const char* text); + void SetText(const std::string& text); + void SetColor(const SDL_Color& color); + + /** + * Set the name of the font. Must be known to the data holder. + * The name could for instance be "freeserif". + * @param fontName Name of the font as required by SagoDataHolder + */ + void SetFont(const char* fontName); + void SetFontSize(int fontSize); + void SetOutline(int outlineSize, const SDL_Color& color); + + /** + * Sets the max width to generate. SagoTextBox will insert line breaks to keep the width below this number. + * Outline is not included in the width: If you have a 2 pixels outline the rendere may go 2 pixels beyond. + * You will always need to generate at least one char per line. If max width is too low one char will be drawn per line even if it goes above max width. + * Setting this to 0 will disable the feature. + * @param width The maximum width before forcing a line break + */ + void SetMaxWidth(int width); + const std::string& GetText() const; + void Draw(SDL_Renderer* target, int x, int y, SagoTextField::Alignment alignment = SagoTextField::Alignment::left); + void UpdateCache(); +private: + void AppendLineToCache(const std::string& text); + void SplitAndAppendLineToCache(TTF_Font* font, const std::string& text); + SagoTextBox(const SagoTextBox& orig) = delete; + SagoTextBox& operator=(const SagoTextBox& base) = delete; + struct SagoTextBoxData; + SagoTextBoxData *data; +}; + +} //namespace sago + +#endif /* SAGOTEXTBOX_HPP */ + diff --git a/src/sago/SagoTextField.cpp b/src/sago/SagoTextField.cpp new file mode 100644 index 0000000..666de12 --- /dev/null +++ b/src/sago/SagoTextField.cpp @@ -0,0 +1,231 @@ +/* +Copyright (c) 2018 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "SagoTextField.hpp" +#include +#include + +namespace sago { + + class OutlineHandler { + TTF_Font* font; + int originalOutline = 0; + int targetOutline; + bool doChange = false; + public: + OutlineHandler(TTF_Font* font, int outline) : font{font}, targetOutline{outline} { + originalOutline = TTF_GetFontOutline(font); + if (originalOutline == targetOutline) { + return; + } + doChange = true; + TTF_SetFontOutline(font, targetOutline); + }; + + void reset() { + if (doChange) { + TTF_SetFontOutline(font,originalOutline); + doChange = false; + } + } + + ~OutlineHandler() { + reset(); + } + private: + OutlineHandler(const OutlineHandler& orig) = delete; + OutlineHandler& operator=(const OutlineHandler& base) = delete; + }; + +struct SagoTextField::SagoTextFieldData { + const sago::SagoDataHolder* tex = nullptr; + SDL_Surface* textSurface = nullptr; + SDL_Texture* texture = nullptr; + SDL_Surface* outlineTextSurface = nullptr; + SDL_Texture* outlineTexture = nullptr; + std::string fontName = "freeserif"; + SDL_Color color = { 255, 255, 255, 0 }; + SDL_Color outlineColor = { 255, 255, 0, 0 }; + int fontSize = 16; + int outline = 0; + std::string text = ""; + std::string renderedText = ""; + Uint64 renderedVersion = 0; +}; + +SagoTextField::SagoTextField() { + data = new SagoTextFieldData(); +} + +SagoTextField::SagoTextField(SagoTextField&& o) noexcept { + data = o.data; + o.data = nullptr; +} + +SagoTextField& SagoTextField::CopyFrom(const SagoTextField& base) { + ClearCache(); + try { + *data = *(base.data); + //Copy all data but do not reuse the cache as it would result in a double free + data->outlineTextSurface = nullptr; + data->outlineTexture = nullptr; + data->textSurface = nullptr; + data->texture = nullptr; + return *this; + } catch (...) { + delete data; + throw; + } +} + +SagoTextField::~SagoTextField() { + if(!data) { + return; + } + ClearCache(); + delete data; +} + +void SagoTextField::SetHolder(const SagoDataHolder* holder) { + data->tex = holder; +} + +void SagoTextField::SetText(const char* text) { + data->text = text; +} + +void SagoTextField::SetText(const std::string& text) { + data->text = text; +} + +void SagoTextField::SetColor(const SDL_Color& color) { + data->color = color; +} + +void SagoTextField::SetFont(const char* fontName) { + data->fontName = fontName; +} + +void SagoTextField::SetFontSize(int fontSize) { + data->fontSize = fontSize; +} + +void SagoTextField::SetOutline(int outlineSize, const SDL_Color& color) { + data->outline = outlineSize; + data->outlineColor = color; +} + +const std::string& SagoTextField::GetText() const { + return data->text; +} + +void SagoTextField::ClearCache() { + if (data->texture) { + SDL_DestroyTexture(data->texture); + data->texture = nullptr; + } + if (data->textSurface) { + SDL_FreeSurface(data->textSurface); + data->textSurface = nullptr; + } + if (data->outlineTexture) { + SDL_DestroyTexture(data->outlineTexture); + data->outlineTexture = nullptr; + } + if (data->outlineTextSurface) { + SDL_FreeSurface(data->outlineTextSurface); + data->outlineTextSurface = nullptr; + } +} + +void SagoTextField::UpdateCache(SDL_Renderer* target) { + if (!data->tex) { + std::cerr << "FATAL: DataHolder not set!\n"; + abort(); + } + ClearCache(); + TTF_Font *font = data->tex->getFontPtr(data->fontName, data->fontSize); + data->textSurface = TTF_RenderUTF8_Blended (font, data->text.c_str(), data->color); + data->texture = SDL_CreateTextureFromSurface(target, data->textSurface); + if (data->outline > 0) { + OutlineHandler oh(font, data->outline); + data->outlineTextSurface = TTF_RenderUTF8_Blended (font, data->text.c_str(), data->outlineColor); + data->outlineTexture = SDL_CreateTextureFromSurface(target, data->outlineTextSurface); + oh.reset(); + } + data->renderedText = data->text; + data->renderedVersion = data->tex->getVersion(); +} + +void SagoTextField::GetRenderedSize(const char* text, int* w, int* h) { + TTF_Font *font = data->tex->getFontPtr(data->fontName, data->fontSize); + int ret = TTF_SizeUTF8(font, text, w, h); + if (ret) { + if (w) { + *w = 0; + } + if (h) { + *h = 0; + } + std::cerr << "GetRenderedSize failed to find size of " << text << ". Error code: " << ret << "\n"; + } +} + +void SagoTextField::Draw(SDL_Renderer* target, int x, int y, Alignment alignment, VerticalAlignment verticalAlignment) { + if (data->text.empty()) { + return; + } + if (data->text != data->renderedText || data->renderedVersion != data->tex->getVersion()) { + UpdateCache(target); + } + if (!data->texture) { + return; + } + int texW = 0; + int texH = 0; + SDL_QueryTexture(data->texture, NULL, NULL, &texW, &texH); + if (alignment == Alignment::center) { + x -= texW/2; + } + if (alignment == Alignment::right) { + x -= texW; + } + if (verticalAlignment == VerticalAlignment::center) { + y -= texH/2; + } + if (verticalAlignment == VerticalAlignment::bottom) { + y -= texH; + } + SDL_Rect dstrect = { x, y, texW, texH }; + if (data->outlineTexture) { + int outlineTexW = 0; + int outlineTexH = 0; + SDL_QueryTexture(data->outlineTexture, NULL, NULL, &outlineTexW, &outlineTexH); + SDL_Rect dstrectOutline = { x-(data->outline), y-(data->outline), outlineTexW, outlineTexH }; + SDL_RenderCopy(target, data->outlineTexture, NULL, &dstrectOutline); + } + SDL_RenderCopy(target, data->texture, NULL, &dstrect); +} + +} //namespace sago \ No newline at end of file diff --git a/src/sago/SagoTextField.hpp b/src/sago/SagoTextField.hpp new file mode 100644 index 0000000..d28114c --- /dev/null +++ b/src/sago/SagoTextField.hpp @@ -0,0 +1,137 @@ +/* +Copyright (c) 2018 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef SAGOTEXTFIELD_HPP +#define SAGOTEXTFIELD_HPP + +#include "SagoDataHolder.hpp" + +namespace sago { + +/** + * This is a text field. + * It represents a line of text to be drawn on screen. It is not possible to have line breaks. + * If line breaks are needed use SagoTextBox instead. + * + * This object renderes to a texture and cahces the texture. The texture will be automatically refreshed if the text changes, the SagoDataHolder is invalidated or ClearCache is called. + * Normally all values will be set at the beginning before text is drawn. + * SetHolder MUST be called before the field is drawn! + */ +class SagoTextField final { +public: + SagoTextField(); + SagoTextField(SagoTextField&& o) noexcept; + SagoTextField& operator=(const SagoTextField&& base) = delete; + SagoTextField& operator=(const SagoTextField& base) = delete; + ~SagoTextField(); + + /** + * This method creates a copy of a given font. + * The cache will not be copied. + * This is ALMOST like the "= operator" but given its own name to prevent implicit calling. + * @param base The object to copy from + * @return A reference to this object. + */ + SagoTextField& CopyFrom(const SagoTextField& base); + + /** + * Sets the data holder. This is MANDATORY + * @param holder The data holder to fetch the fonts from + */ + void SetHolder(const SagoDataHolder* holder); + + /** + * Set the text to display. + * @param text The actual UTF-8 encoded text + */ + void SetText(const char* text); + + /** + * Set the text to display. + * @param text The actual UTF-8 encoded text + */ + void SetText(const std::string& text); + + void SetColor(const SDL_Color& color); + + /** + * Set the name of the font. Must be known to the data holder. + * The name could for instance be "freeserif". + * @param fontName Name of the font as required by SagoDataHolder + */ + void SetFont(const char* fontName); + + void SetFontSize(int fontSize); + + /** + * Enable outline against the font. + * @param outlineSize Number of pixels of outline. + * @param color The color of the outline. + */ + void SetOutline(int outlineSize, const SDL_Color& color); + + /** + * Get the text we are currently drawing + * @return The text + */ + const std::string& GetText() const; + + /** + * A Shorthand for calling TTF_SizeUTF8 on the right font + * The size is measuered WITHOUT the outline! + * Will fail silently on error (except writing to stderr) and set w and h to 0 if they are not null. + * @param text The text to check the rendered size for + * @param w Pointer to an int where the width of the text will be stored. Maybe null. + * @param h Pointer to an int where the hight of the text will be stored. Maybe null. + */ + void GetRenderedSize(const char* text, int* w = nullptr, int* h = nullptr); + + enum class Alignment { left = 0, right=1, center = 2 }; + enum class VerticalAlignment { top = 0, center = 1, bottom = 2}; + void Draw(SDL_Renderer* target, int x, int y, Alignment alignment = Alignment::left, VerticalAlignment verticalAlignment = VerticalAlignment::top); + + /** + * Updates the cache. + * You normally do not want to call this from the outside as it is done just in time. + * Unless you want to precache of course.... + * @param target Target the the text will eventually be rendered to + */ + void UpdateCache(SDL_Renderer* target); + + /** + * Clears the cache and forces the SagoTextField to render it again the next time it is drawn. + * Can be used if you have changed font, color or sizes. + * Changing the text implices a cache clear. + */ + void ClearCache(); +private: + SagoTextField(const SagoTextField& orig) = delete; + struct SagoTextFieldData; + SagoTextFieldData *data; +}; + +} //namespace sago + +#endif /* SAGOTEXTFIELD_HPP */ + diff --git a/src/sago/platform_folders.cpp b/src/sago/platform_folders.cpp new file mode 100644 index 0000000..1531e91 --- /dev/null +++ b/src/sago/platform_folders.cpp @@ -0,0 +1,448 @@ +/* +Its is under the MIT license, to encourage reuse by cut-and-paste. + +The original files are hosted here: https://github.com/sago007/PlatformFolders + +Copyright (c) 2015-2016 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "platform_folders.h" +#include +#include +#include +#include + +#ifndef _WIN32 + +#include +#include + +/** + * Retrives the effective user's home dir. + * If the user is running as root we ignore the HOME environment. It works badly with sudo. + * Writing to $HOME as root implies security concerns that a multiplatform program cannot be assumed to handle. + * @return The home directory. HOME environment is respected for non-root users if it exists. + */ +static std::string getHome() { + std::string res; + int uid = getuid(); + const char* homeEnv = std::getenv("HOME"); + if ( uid != 0 && homeEnv) { + //We only acknowlegde HOME if not root. + res = homeEnv; + return res; + } + struct passwd* pw = nullptr; + struct passwd pwd; + long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (bufsize < 0) { + bufsize = 16384; + } + std::vector buffer; + buffer.resize(bufsize); + int error_code = getpwuid_r(uid, &pwd, buffer.data(), buffer.size(), &pw); + if (error_code) { + throw std::runtime_error("Unable to get passwd struct."); + } + const char* tempRes = pw->pw_dir; + if (!tempRes) { + throw std::runtime_error("User has no home directory"); + } + res = tempRes; + return res; +} + +#endif + +#ifdef _WIN32 +// Make sure we don't bring in all the extra junk with windows.h +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +// stringapiset.h depends on this +#include +// For SUCCEEDED macro +#include +// For WideCharToMultiByte +#include +// For SHGetFolderPathW and various CSIDL "magic numbers" +#include + +namespace sago { +namespace internal { + +std::string win32_utf16_to_utf8(const wchar_t* wstr) { + std::string res; + // If the 6th parameter is 0 then WideCharToMultiByte returns the number of bytes needed to store the result. + int actualSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr); + if (actualSize > 0) { + //If the converted UTF-8 string could not be in the initial buffer. Allocate one that can hold it. + std::vector buffer(actualSize); + actualSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &buffer[0], static_cast(buffer.size()), nullptr, nullptr); + res = buffer.data(); + } + if (actualSize == 0) { + // WideCharToMultiByte return 0 for errors. + const std::string errorMsg = "UTF16 to UTF8 failed with error code: " + GetLastError(); + throw std::runtime_error(errorMsg.c_str()); + } + return res; +} + +} // namesapce internal +} // namespace sago + +class FreeCoTaskMemory { + LPWSTR pointer = NULL; +public: + explicit FreeCoTaskMemory(LPWSTR pointer) : pointer(pointer) {}; + ~FreeCoTaskMemory() { + CoTaskMemFree(pointer); + } +}; + +static std::string GetKnownWindowsFolder(REFKNOWNFOLDERID folderId, const char* errorMsg) { + LPWSTR wszPath = NULL; + HRESULT hr; + hr = SHGetKnownFolderPath(folderId, KF_FLAG_CREATE, NULL, &wszPath); + FreeCoTaskMemory scopeBoundMemory(wszPath); + + if (!SUCCEEDED(hr)) { + throw std::runtime_error(errorMsg); + } + return sago::internal::win32_utf16_to_utf8(wszPath); +} + +static std::string GetAppData() { + return GetKnownWindowsFolder(FOLDERID_RoamingAppData, "RoamingAppData could not be found"); +} + +static std::string GetAppDataCommon() { + return GetKnownWindowsFolder(FOLDERID_ProgramData, "ProgramData could not be found"); +} + +static std::string GetAppDataLocal() { + return GetKnownWindowsFolder(FOLDERID_LocalAppData, "LocalAppData could not be found"); +} +#elif defined(__APPLE__) +#else +#include +#include +#include +// For strlen and strtok +#include +#include +//Typically Linux. For easy reading the comments will just say Linux but should work with most *nixes + +static void throwOnRelative(const char* envName, const char* envValue) { + if (envValue[0] != '/') { + char buffer[200]; + std::snprintf(buffer, sizeof(buffer), "Environment \"%s\" does not start with an '/'. XDG specifies that the value must be absolute. The current value is: \"%s\"", envName, envValue); + throw std::runtime_error(buffer); + } +} + + + +static std::string getLinuxFolderDefault(const char* envName, const char* defaultRelativePath) { + std::string res; + const char* tempRes = std::getenv(envName); + if (tempRes) { + throwOnRelative(envName, tempRes); + res = tempRes; + return res; + } + res = getHome() + "/" + defaultRelativePath; + return res; +} + +static void appendExtraFolders(const char* envName, const char* defaultValue, std::vector& folders) { + const char* envValue = std::getenv(envName); + if (!envValue) { + envValue = defaultValue; + } + sago::internal::appendExtraFoldersTokenizer(envName, envValue, folders); +} + +#endif + + +namespace sago { + +#if !defined(_WIN32) && !defined(__APPLE__) +namespace internal { +void appendExtraFoldersTokenizer(const char* envName, const char* envValue, std::vector& folders) { + std::stringstream ss(envValue); + std::string value; + while (std::getline(ss, value, ':')) { + if (value[0] == '/') { + folders.push_back(value); + } + else { + //Unless the system is wrongly configured this should never happen... But of course some systems will be incorectly configured. + //The XDG documentation indicates that the folder should be ignored but that the program should continue. + std::cerr << "Skipping path \"" << value << "\" in \"" << envName << "\" because it does not start with a \"/\"\n"; + } + } +} +} +#endif + +std::string getDataHome() { +#ifdef _WIN32 + return GetAppData(); +#elif defined(__APPLE__) + return getHome()+"/Library/Application Support"; +#else + return getLinuxFolderDefault("XDG_DATA_HOME", ".local/share"); +#endif +} + +std::string getConfigHome() { +#ifdef _WIN32 + return GetAppData(); +#elif defined(__APPLE__) + return getHome()+"/Library/Application Support"; +#else + return getLinuxFolderDefault("XDG_CONFIG_HOME", ".config"); +#endif +} + +std::string getCacheDir() { +#ifdef _WIN32 + return GetAppDataLocal(); +#elif defined(__APPLE__) + return getHome()+"/Library/Caches"; +#else + return getLinuxFolderDefault("XDG_CACHE_HOME", ".cache"); +#endif +} + +void appendAdditionalDataDirectories(std::vector& homes) { +#ifdef _WIN32 + homes.push_back(GetAppDataCommon()); +#elif !defined(__APPLE__) + appendExtraFolders("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/", homes); +#endif +} + +void appendAdditionalConfigDirectories(std::vector& homes) { +#ifdef _WIN32 + homes.push_back(GetAppDataCommon()); +#elif !defined(__APPLE__) + appendExtraFolders("XDG_CONFIG_DIRS", "/etc/xdg", homes); +#endif +} + +#if !defined(_WIN32) && !defined(__APPLE__) +struct PlatformFolders::PlatformFoldersData { + std::map folders; +}; + +static void PlatformFoldersAddFromFile(const std::string& filename, std::map& folders) { + std::ifstream infile(filename.c_str()); + std::string line; + while (std::getline(infile, line)) { + if (line.length() == 0 || line.at(0) == '#' || line.substr(0, 4) != "XDG_" || line.find("_DIR") == std::string::npos) { + continue; + } + try { + std::size_t splitPos = line.find('='); + std::string key = line.substr(0, splitPos); + std::size_t valueStart = line.find('"', splitPos); + std::size_t valueEnd = line.find('"', valueStart+1); + std::string value = line.substr(valueStart+1, valueEnd - valueStart - 1); + folders[key] = value; + } + catch (std::exception& e) { + std::cerr << "WARNING: Failed to process \"" << line << "\" from \"" << filename << "\". Error: "<< e.what() << "\n"; + continue; + } + } +} + +static void PlatformFoldersFillData(std::map& folders) { + folders["XDG_DOCUMENTS_DIR"] = "$HOME/Documents"; + folders["XDG_DESKTOP_DIR"] = "$HOME/Desktop"; + folders["XDG_DOWNLOAD_DIR"] = "$HOME/Downloads"; + folders["XDG_MUSIC_DIR"] = "$HOME/Music"; + folders["XDG_PICTURES_DIR"] = "$HOME/Pictures"; + folders["XDG_PUBLICSHARE_DIR"] = "$HOME/Public"; + folders["XDG_TEMPLATES_DIR"] = "$HOME/.Templates"; + folders["XDG_VIDEOS_DIR"] = "$HOME/Videos"; + PlatformFoldersAddFromFile( getConfigHome()+"/user-dirs.dirs", folders); + for (std::map::iterator itr = folders.begin() ; itr != folders.end() ; ++itr ) { + std::string& value = itr->second; + if (value.compare(0, 5, "$HOME") == 0) { + value = getHome() + value.substr(5, std::string::npos); + } + } +} +#endif + +PlatformFolders::PlatformFolders() { +#if !defined(_WIN32) && !defined(__APPLE__) + this->data = new PlatformFolders::PlatformFoldersData(); + try { + PlatformFoldersFillData(data->folders); + } + catch (...) { + delete this->data; + throw; + } +#endif +} + +PlatformFolders::~PlatformFolders() { +#if !defined(_WIN32) && !defined(__APPLE__) + delete this->data; +#endif +} + +std::string PlatformFolders::getDocumentsFolder() const { +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_Documents, "Failed to find My Documents folder"); +#elif defined(__APPLE__) + return getHome()+"/Documents"; +#else + return data->folders["XDG_DOCUMENTS_DIR"]; +#endif +} + +std::string PlatformFolders::getDesktopFolder() const { +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_Desktop, "Failed to find Desktop folder"); +#elif defined(__APPLE__) + return getHome()+"/Desktop"; +#else + return data->folders["XDG_DESKTOP_DIR"]; +#endif +} + +std::string PlatformFolders::getPicturesFolder() const { +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_Pictures, "Failed to find My Pictures folder"); +#elif defined(__APPLE__) + return getHome()+"/Pictures"; +#else + return data->folders["XDG_PICTURES_DIR"]; +#endif +} + +std::string PlatformFolders::getPublicFolder() const { +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_Public, "Failed to find the Public folder"); +#elif defined(__APPLE__) + return getHome()+"/Public"; +#else + return data->folders["XDG_PUBLICSHARE_DIR"]; +#endif +} + +std::string PlatformFolders::getDownloadFolder1() const { +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_Downloads, "Failed to find My Downloads folder"); +#elif defined(__APPLE__) + return getHome()+"/Downloads"; +#else + return data->folders["XDG_DOWNLOAD_DIR"]; +#endif +} + +std::string PlatformFolders::getMusicFolder() const { +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_Music, "Failed to find My Music folder"); +#elif defined(__APPLE__) + return getHome()+"/Music"; +#else + return data->folders["XDG_MUSIC_DIR"]; +#endif +} + +std::string PlatformFolders::getVideoFolder() const { +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_Videos, "Failed to find My Video folder"); +#elif defined(__APPLE__) + return getHome()+"/Movies"; +#else + return data->folders["XDG_VIDEOS_DIR"]; +#endif +} + +std::string PlatformFolders::getSaveGamesFolder1() const { +#ifdef _WIN32 + //A dedicated Save Games folder was not introduced until Vista. For XP and older save games are most often saved in a normal folder named "My Games". + //Data that should not be user accessible should be placed under GetDataHome() instead + return GetKnownWindowsFolder(FOLDERID_Documents, "Failed to find My Documents folder")+"\\My Games"; +#elif defined(__APPLE__) + return getHome()+"/Library/Application Support"; +#else + return getDataHome(); +#endif +} + +std::string getDesktopFolder() { + return PlatformFolders().getDesktopFolder(); +} + +std::string getDocumentsFolder() { + return PlatformFolders().getDocumentsFolder(); +} + +std::string getDownloadFolder() { + return PlatformFolders().getDownloadFolder1(); +} + +std::string getDownloadFolder1() { + return getDownloadFolder(); +} + +std::string getPicturesFolder() { + return PlatformFolders().getPicturesFolder(); +} + +std::string getPublicFolder() { + return PlatformFolders().getPublicFolder(); +} + +std::string getMusicFolder() { + return PlatformFolders().getMusicFolder(); +} + +std::string getVideoFolder() { + return PlatformFolders().getVideoFolder(); +} + +std::string getSaveGamesFolder1() { + return PlatformFolders().getSaveGamesFolder1(); +} + +std::string getSaveGamesFolder2() { +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_SavedGames, "Failed to find Saved Games folder"); +#else + return PlatformFolders().getSaveGamesFolder1(); +#endif +} + +} //namespace sago diff --git a/src/sago/platform_folders.h b/src/sago/platform_folders.h new file mode 100644 index 0000000..4563410 --- /dev/null +++ b/src/sago/platform_folders.h @@ -0,0 +1,273 @@ +/* +Its is under the MIT license, to encourage reuse by cut-and-paste. + +The original files are hosted here: https://github.com/sago007/PlatformFolders + +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef SAGO_PLATFORM_FOLDERS_H +#define SAGO_PLATFORM_FOLDERS_H + +#include +#include + +/** + * The namespace I use for common function. Nothing special about it. + */ +namespace sago { + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace internal { +#if !defined(_WIN32) && !defined(__APPLE__) +void appendExtraFoldersTokenizer(const char* envName, const char* envValue, std::vector& folders); +#endif +#ifdef _WIN32 +std::string win32_utf16_to_utf8(const wchar_t* wstr); +#endif +} +#endif //DOXYGEN_SHOULD_SKIP_THIS + +/** + * Retrives the base folder for storing data files. + * You must add the program name yourself like this: + * @code{.cpp} + * string data_home = getDataHome()+"/My Program Name/"; + * @endcode + * On Windows this defaults to %APPDATA% (Roaming profile) + * On Linux this defaults to ~/.local/share but can be configured + * @return The base folder for storing program data. + */ +std::string getDataHome(); + +/** + * Retrives the base folder for storing config files. + * You must add the program name yourself like this: + * @code{.cpp} + * string data_home = getConfigHome()+"/My Program Name/"; + * @endcode + * On Windows this defaults to %APPDATA% (Roaming profile) + * On Linux this defaults to ~/.config but can be configured + * @return The base folder for storing config data. + */ +std::string getConfigHome(); + +/** + * Retrives the base folder for storing cache files. + * You must add the program name yourself like this: + * @code{.cpp} + * string data_home = getCacheDir()+"/My Program Name/"; + * @endcode + * On Windows this defaults to %APPDATALOCAL% + * On Linux this defaults to ~/.cache but can be configured + * @return The base folder for storing data that do not need to be backed up. + */ +std::string getCacheDir(); + +/** + * This will append extra folders that your program should be looking for data files in. + * This does not normally include the path returned by GetDataHome(). + * If you want all the folders you should do something like: + * @code{.cpp} + * vector folders; + * folders.push_back(getDataHome()); + * appendAdditionalDataDirectories(folders); + * for (string s& : folders) { + * s+="/My Program Name/"; + * } + * @endcode + * You must apply "/My Program Name/" to all the strings. + * The string at the lowest index has the highest priority. + * @param homes A vector that extra folders will be appended to. + */ +void appendAdditionalDataDirectories(std::vector& homes); + +/** + * This will append extra folders that your program should be looking for config files in. + * This does not normally include the path returned by GetConfigHome(). + * If you want all the folders you should do something like: + * @code{.cpp} + * std::vector folders; + * folders.push_back(sago::getConfigHome()); + * sago::appendAdditionalConfigDirectories(folders); + * for (std::string s& : folders) { + * s+="/My Program Name/"; + * } + * @endcode + * You must apply "/My Program Name/" to all the strings. + * The string at the lowest index has the highest priority. + * @param homes A vector that extra folders will be appended to. + */ +void appendAdditionalConfigDirectories(std::vector& homes); + +/** + * The folder that represents the desktop. + * Normally you should try not to use this folder. + * @return Absolute path to the user's desktop + */ +std::string getDesktopFolder(); + +/** + * The folder to store user documents to + * @return Absolute path to the "Documents" folder + */ +std::string getDocumentsFolder(); + +/** + * The folder where files are downloaded. + * @return Absolute path to the folder where files are downloaded to. + */ +std::string getDownloadFolder(); + +/** + * The folder where files are downloaded. + * @note This is provided for backward compatibility. Use getDownloadFolder instead. + * @return Absolute path to the folder where files are downloaded to. + */ +std::string getDownloadFolder1(); + +/** + * The folder for storing the user's pictures. + * @return Absolute path to the "Picture" folder + */ +std::string getPicturesFolder(); + +/** + * This returns the folder that can be used for sharing files with other users on the same system. + * @return Absolute path to the "Public" folder + */ +std::string getPublicFolder(); + +/** + * The folder where music is stored + * @return Absolute path to the music folder + */ +std::string getMusicFolder(); + +/** + * The folder where video is stored + * @return Absolute path to the video folder + */ +std::string getVideoFolder(); + +/** + * A base folder for storing saved games. + * You must add the program name to it like this: + * @code{.cpp} + * string saved_games_folder = sago::getSaveGamesFolder1()+"/My Program Name/"; + * @endcode + * @note Windows: This is an XP compatible version and returns the path to "My Games" in Documents. Vista and later has an official folder. + * @note Linux: XDF does not define a folder for saved games. This will just return the same as GetDataHome() + * @return The folder base folder for storing save games. + */ +std::string getSaveGamesFolder1(); + +/** + * A base folder for storing saved games. + * You must add the program name to it like this: + * @code{.cpp} + * string saved_games_folder = sago::getSaveGamesFolder2()+"/My Program Name/"; + * @endcode + * @note PlatformFolders provide different folders to for saved games as not all operating systems has support for Saved Games yet. + * It is recommended to pick the highest number (currently getSaveGamesFolder2) at the time your product enters production and stick with it + * @note Windows: This returns the "Saved Games" folder. This folder exist in Vista and later + * @note Linux: XDF does not define a folder for saved games. This will just return the same as GetDataHome() + * @return The folder base folder for storing save games. + */ +std::string getSaveGamesFolder2(); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +/** + * This class contains methods for finding the system depended special folders. + * For Windows these folders are either by convention or given by CSIDL. + * For Linux XDG convention is used. + * The Linux version has very little error checking and assumes that the config is correct + */ +class PlatformFolders { +public: + PlatformFolders(); + ~PlatformFolders(); + /** + * The folder that represents the desktop. + * Normally you should try not to use this folder. + * @return Absolute path to the user's desktop + */ + std::string getDesktopFolder() const; + /** + * The folder to store user documents to + * @return Absolute path to the "Documents" folder + */ + std::string getDocumentsFolder() const; + /** + * The folder for storing the user's pictures. + * @return Absolute path to the "Picture" folder + */ + std::string getPicturesFolder() const; + /** + * Use sago::getPublicFolder() instead! + */ + std::string getPublicFolder() const; + /** + * The folder where files are downloaded. + * @note Windows: This version is XP compatible and returns the Desktop. Vista and later has a dedicated folder. + * @return Absolute path to the folder where files are downloaded to. + */ + std::string getDownloadFolder1() const; + /** + * The folder where music is stored + * @return Absolute path to the music folder + */ + std::string getMusicFolder() const; + /** + * The folder where video is stored + * @return Absolute path to the video folder + */ + std::string getVideoFolder() const; + /** + * The base folder for storing saved games. + * You must add the program name to it like this: + * @code{.cpp} + * PlatformFolders pf; + * string saved_games_folder = pf.getSaveGamesFolder1()+"/My Program Name/"; + * @endcode + * @note Windows: This is an XP compatible version and returns the path to "My Games" in Documents. Vista and later has an official folder. + * @note Linux: XDF does not define a folder for saved games. This will just return the same as GetDataHome() + * @return The folder base folder for storing save games. + */ + std::string getSaveGamesFolder1() const; +private: + PlatformFolders(const PlatformFolders&); + PlatformFolders& operator=(const PlatformFolders&); +#if !defined(_WIN32) && !defined(__APPLE__) + struct PlatformFoldersData; + PlatformFoldersData* data; +#endif +}; + +#endif // skip doxygen + + +} //namespace sago + +#endif /* PLATFORM_FOLDERS_H */ diff --git a/src/sago_multi_scrambler_puzzle2.cpp b/src/sago_multi_scrambler_puzzle2.cpp index 898fc2f..68c61ad 100644 --- a/src/sago_multi_scrambler_puzzle2.cpp +++ b/src/sago_multi_scrambler_puzzle2.cpp @@ -1,10 +1,60 @@ #include #include +#include "sago/SagoDataHolder.hpp" +#include "sago/SagoSpriteHolder.hpp" +#include "sago/GameStateInterface.hpp" +#include "sago/SagoTextField.hpp" +#include "globals.hpp" #ifndef VERSIONNUMBER #define VERSIONNUMBER "0.1.0" #endif + +static SDL_Window* win = nullptr; +GlobalData globalData; + +void InitSagoFS(int argc, const char* argv[]) { + PHYSFS_init(argv[0]); + PHYSFS_mount((std::string(PHYSFS_getBaseDir())+"/data").c_str(), nullptr, 0); +} + + +void runGame() { + int width = 1280, height = 800; + SDL_Init(SDL_INIT_VIDEO); + IMG_Init(IMG_INIT_PNG); + TTF_Init(); + Mix_Init(MIX_INIT_OGG); + + int rendererFlags = 0; + + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2"); + win = SDL_CreateWindow("Sago's Multi Scrambler Puzzle II", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_RESIZABLE); + globalData.screen = SDL_CreateRenderer(win, -1, rendererFlags); + sago::SagoDataHolder holder(globalData.screen); + globalData.spriteHolder.reset(new sago::SagoSpriteHolder(holder)); + globalData.dataHolder = &holder; + + while (1) { + SDL_Event e; + if (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) { + break; + } + } + + SDL_RenderClear(globalData.screen); + SDL_RenderPresent(globalData.screen); + } + + SDL_DestroyRenderer(globalData.screen); + SDL_DestroyWindow(win); + + SDL_Quit(); +} + + int main(int argc, const char* argv[]) { boost::program_options::options_description desc("Options"); desc.add_options() @@ -23,12 +73,7 @@ int main(int argc, const char* argv[]) { std::cout << "sago_multi_scrambler_puzzle2 " << VERSIONNUMBER << "\n"; return 0; } - if (vm.count("somestring")) { - std::string somestring = vm["somestring"].as(); - std::cout << "Called with a parameter value of: " << somestring << "\n"; - } - else { - std::cout << "This program demonstrates boost::program_options. Try \"" << argv[0] << " --help\" or \"" << argv[0] << " --somestring hello\"\n"; - } + InitSagoFS(argc, argv); + runGame(); return 0; }