/* =========================================================================== * Saland Adventures Copyright (C) 2014-2018 Poul Sander This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/ Source information and contacts persons can be found at https://github.com/salandgame/saland =========================================================================== */ #include #include #include #include "sago/SagoDataHolder.hpp" #include "sago/SagoSpriteHolder.hpp" #include "sago/GameStateInterface.hpp" #include "sago/SagoTextField.hpp" #include #include #include #include #include #include #include #include #include #include "saland/globals.hpp" #include "saland/Game.hpp" #include "sago/SagoMisc.hpp" #include "platform_folders.h" #include "sagotmx/tmx_struct.h" #include "sago/SagoTextBox.hpp" #include "MenuSystem.h" #include "help/HelpAboutState.hpp" #include "common.h" #include "os.hpp" #include "version.h" #include "fmt/core.h" //if SHAREDIR is not used we look in current directory #ifndef SHAREDIR #define SHAREDIR "." #endif #ifndef LOCALEDIR #define LOCALEDIR SHAREDIR"/locale" #endif #ifndef PACKAGE #define PACKAGE "salandgame" #endif GlobalData globalData; class TitleScreen : public sago::GameStateInterface { public: TitleScreen() { textField.SetHolder(globalData.dataHolder); textField.SetFontSize(30); textBox.SetHolder(globalData.dataHolder); textBox.SetFontSize(16); testBox.SetHolder(globalData.dataHolder); testBox.SetFontSize(16); } virtual bool IsActive() override { return isActive; } virtual void Draw(SDL_Renderer* target) override { textField.SetText("Saland Adventures - The game that has a very long subtitle to test the outline"); textField.SetOutline(3, SDL_Color{255,165,0,255}); textField.Draw(target, 10, 10); textBox.SetText("This is an attempt to create 2d rpg with a modifiable world.\nLet's see how it goes!"); textBox.SetOutline(1, SDL_Color{255,0,0,255}); textBox.SetMaxWidth(800); textBox.Draw(target, 300,300); testBox.SetText("a\xe6\x97\xa5\xd1\x88\xc3\x86"); testBox.SetMaxWidth(1); testBox.Draw(target, 300,500); circleRGBA(target, 150, 150, 75, 0, 0, 255, 255); const sago::SagoSprite& s = globalData.spriteHolder->GetSprite("male_walkcycle_E"); s.Draw(globalData.screen, SDL_GetTicks(), 100, 100); const sago::SagoSprite& sc = globalData.spriteHolder->GetSprite("female_spellcast_E"); sc.Draw(globalData.screen, SDL_GetTicks(), 200, 100); const sago::SagoSprite& bee = globalData.spriteHolder->GetSprite("bee_E"); bee.Draw(globalData.screen, SDL_GetTicks(), 100, 200); const sago::SagoSprite& bat = globalData.spriteHolder->GetSprite("bat_W"); bat.Draw(globalData.screen, SDL_GetTicks(), 200, 200); const sago::SagoSprite& potato = globalData.spriteHolder->GetSprite("item_food_potato"); potato.Draw(globalData.screen, SDL_GetTicks(), 240, 200); } virtual void ProcessInput(const SDL_Event& event, bool& processed) override { if ( event.type == SDL_KEYDOWN ) { if (event.key.keysym.sym == SDLK_RETURN) { isActive = false; processed = true; } } } virtual void Update() override { } private: bool isActive = true; sago::SagoTextField textField; sago::SagoTextBox textBox; sago::SagoTextBox testBox; }; std::string pathToScreenShots() { Config::getInstance()->setDefault("screenshot_dir", "screenshots"); std::string screenshot_dir = Config::getInstance()->getString("screenshot_dir"); if (OsPathIsRelative(screenshot_dir)) { return getPathToSaveFiles() + "/" + screenshot_dir; } return screenshot_dir; } static SDL_Window* win = nullptr; //writeScreenShot saves the screen as a bmp file, it uses the time to get a unique filename void writeScreenShot() { if (globalData.verboseLevel) { std::cout << "Saving screenshot" << "\n"; } int rightNow = (int)time(nullptr); SDL_Surface* infoSurface = SDL_GetWindowSurface(win); if (!infoSurface) { std::cerr << "Could not get infoSurface. No screenshot written. Be aware that the screenshot feature only works with software render\n"; return; } std::vector pixels(infoSurface->w * infoSurface->h * infoSurface->format->BytesPerPixel); int errorCode = SDL_RenderReadPixels(globalData.screen, &infoSurface->clip_rect, infoSurface->format->format, static_cast(pixels.data()), infoSurface->w * infoSurface->format->BytesPerPixel); if (errorCode) { SDL_FreeSurface(infoSurface); std::cerr << "Could not do SDL_RenderReadPixels. Error code: " << errorCode << ". No screenshot written\n"; return; } SDL_Surface* sreenshotSurface = SDL_CreateRGBSurfaceFrom(static_cast(pixels.data()), infoSurface->w, infoSurface->h, infoSurface->format->BitsPerPixel, infoSurface->w * infoSurface->format->BytesPerPixel, infoSurface->format->Rmask, infoSurface->format->Gmask, infoSurface->format->Bmask, infoSurface->format->Amask); SDL_FreeSurface(infoSurface); if (!sreenshotSurface) { std::cerr << "Could not get sreenshotSurface. No screenshot written\n"; return; } OsCreateFolder(pathToScreenShots()); std::string buf = pathToScreenShots() + "/screenshot"+std::to_string(rightNow)+".bmp"; SDL_SaveBMP(sreenshotSurface, buf.c_str()); SDL_FreeSurface(sreenshotSurface); if (!globalData.NoSound) { if (globalData.SoundEnabled) { Mix_PlayChannel(1, globalData.dataHolder->getSoundHandler("cameraclick").get(), 0); } } } /** * This function reads the mouse coordinates from a relevant event. * Unlike SDL_GetMouseState this works even if SDL_RenderSetLogicalSize is used * @param event * @param mousex * @param mousey */ void UpdateMouseCoordinates(const SDL_Event& event, int& mousex, int& mousey) { switch (event.type) { case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: mousex = event.button.x; mousey = event.button.y; break; case SDL_MOUSEMOTION: mousex = event.motion.x; mousey = event.motion.y; break; default: break; } } void ResetFullscreen(); void RunGameState(sago::GameStateInterface& state ) { bool done = false; //We are done! while (!done && !globalData.isShuttingDown) { if (globalData.resetVideo) { ResetFullscreen(); globalData.resetVideo = false; } SDL_SetRenderDrawColor(globalData.screen, 0, 0, 0, 0); SDL_RenderClear(globalData.screen); state.Draw(globalData.screen); SDL_Delay(1); SDL_Event event; bool mustWriteScreenshot = false; while ( SDL_PollEvent(&event) ) { if ( event.type == SDL_QUIT ) { globalData.isShuttingDown = true; done = true; } if (event.type == SDL_WINDOWEVENT) { if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { std::cout << event.window.data1 << ", " << event.window.data2 << "\n"; SDL_GetRendererOutputSize(globalData.screen, &globalData.xsize, &globalData.ysize); } } if (event.type == SDL_KEYDOWN) { if (!globalData.resetVideo && event.key.keysym.sym == SDLK_RETURN && event.key.keysym.mod & KMOD_LALT) { globalData.fullscreen = !globalData.fullscreen; globalData.resetVideo = true; } if ( event.key.keysym.sym == SDLK_F9 ) { mustWriteScreenshot = true; } } if (mustWriteScreenshot) { writeScreenShot(); } bool processed = false; state.ProcessInput(event, processed); } state.Update(); SDL_RenderPresent(globalData.screen); if (!state.IsActive()) { done = true; } } } void runStartGame() { std::string playername = Config::getInstance()->getString("player"); if (playername == "player2") { globalData.player.race = "male"; } Game g; RunGameState(g); } class ButtonConfigValue : public Button { private: std::string config_key; std::string config_value; public: ButtonConfigValue() { setPopOnRun(true); } void setConfigValues(const std::string& key, const std::string& value) { config_key = key; config_value = value; } virtual void doAction() override { Config::getInstance()->setString(config_key, config_value); } }; void runWorldSelect() { Menu m(globalData.screen, true); ButtonConfigValue bWorld1; bWorld1.setLabel("World 1"); bWorld1.setConfigValues("world", "world1"); m.addButton(&bWorld1); ButtonConfigValue bWorld2; bWorld2.setLabel("World 2"); bWorld1.setConfigValues("world", "world2"); m.addButton(&bWorld2); ButtonConfigValue bWorld3; bWorld3.setLabel("World 3"); bWorld1.setConfigValues("world", "world3"); m.addButton(&bWorld3); ButtonConfigValue bWorld4; bWorld4.setLabel("World 4"); bWorld1.setConfigValues("world", "world4"); m.addButton(&bWorld4); RunGameState(m); } void runPlayerSelect() { Menu m(globalData.screen, true); ButtonConfigValue bPlayer1; bPlayer1.setLabel("Player 1"); bPlayer1.setConfigValues("player", "player1"); m.addButton(&bPlayer1); ButtonConfigValue bPlayer2; bPlayer2.setLabel("Player 2"); bPlayer2.setConfigValues("player", "player2"); m.addButton(&bPlayer2); RunGameState(m); } void ResetFullscreen() { sago::SagoDataHolder& dataHolder = *globalData.dataHolder; Mix_HaltMusic(); //We need to reload all data in case the screen type changes. Music must be stopped before unload. if (globalData.fullscreen) { SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN_DESKTOP); } else { SDL_SetWindowFullscreen(win, 0); } dataHolder.invalidateAll(globalData.screen); globalData.spriteHolder.reset(new sago::SagoSpriteHolder( dataHolder ) ); SDL_ShowCursor(SDL_ENABLE); SDL_GetRendererOutputSize(globalData.screen, &globalData.xsize, &globalData.ysize); } void toggleFullscreen() { globalData.fullscreen = !globalData.fullscreen; ResetFullscreen(); } void runMenuOptions() { Menu m(globalData.screen, true); Button bFullscreen; bFullscreen.setLabel("Fullscreen"); bFullscreen.setAction(toggleFullscreen); m.addButton(&bFullscreen); RunGameState(m); } static void runHelpAbout() { HelpAboutState helpAbout; RunGameState(helpAbout); } class WorldSelectButton : public Button { private: void UpdateLabel() { this->setLabel(fmt::format("World: {}", Config::getInstance()->getString("world") )); } public: WorldSelectButton() { UpdateLabel(); } virtual void doAction() override { runWorldSelect(); UpdateLabel(); } }; class PlayerSelectButton : public Button { private: void UpdateLabel() { this->setLabel(fmt::format("Player: {}", Config::getInstance()->getString("player") )); } public: PlayerSelectButton() { UpdateLabel(); } virtual void doAction() override { runPlayerSelect(); UpdateLabel(); } }; void runGame() { int width = 1280, height = 800; SDL_Init(SDL_INIT_VIDEO); IMG_Init(IMG_INIT_PNG); TTF_Init(); Mix_Init(MIX_INIT_OGG); if (!globalData.NoSound) { //If sound has not been disabled, then load the sound system if (Mix_OpenAudio(44100, AUDIO_S16SYS, 2, 2048) < 0) { std::cerr << "Warning: Couldn't set 44100 Hz 16-bit audio - Reason: " << SDL_GetError() << "\n" << "Sound will be disabled!" << "\n"; globalData.NoSound = true; //Tries to stop all sound from playing/loading } } int rendererFlags = 0; globalData.fullscreen = Config::getInstance()->getInt("fullscreen"); if (Config::getInstance()->getInt("always-software")) { rendererFlags |= SDL_RENDERER_SOFTWARE; } SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2"); win = SDL_CreateWindow("Saland Adventures", 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; ResetFullscreen(); TitleScreen ts; RunGameState(ts); standardButton.setSurfaces(); Menu m(globalData.screen, false); Button bStart; bStart.setLabel("Start"); bStart.setAction(runStartGame); m.addButton(&bStart); WorldSelectButton bWorldSelect; m.addButton(&bWorldSelect); PlayerSelectButton bPlayerSelect; m.addButton(&bPlayerSelect); Button bOptions; bOptions.setLabel("Options"); bOptions.setAction(runMenuOptions); m.addButton(&bOptions); Button bAbout; bAbout.setLabel(_("About")); bAbout.setAction(runHelpAbout); m.addButton(&bAbout); if (!globalData.isShuttingDown) { RunGameState(m); } SDL_DestroyRenderer(globalData.screen); SDL_DestroyWindow(win); SDL_Quit(); } int main(int argc, char* argv[]) { PHYSFS_init(argv[0]); PHYSFS_mount((std::string(SHAREDIR)+"/packages/saland-0001.data").c_str(), nullptr, 0); PHYSFS_mount((std::string(PHYSFS_getBaseDir())+"/packages/saland-0001.data").c_str(), nullptr, 0); PHYSFS_mount((std::string(PHYSFS_getBaseDir())+"/data").c_str(), nullptr, 0); std::string savepath = getPathToSaveFiles(); OsCreateSaveFolder(); PHYSFS_mount(savepath.c_str(), nullptr, 0); PHYSFS_setWriteDir(savepath.c_str()); boost::program_options::options_description desc("Options"); desc.add_options() ("version", "Print version information and quit") ("help,h", "Print basic usage information to stdout and quit") ("fullscreen", "Run in fullscreen") ("no-fullscreen", "Run in window") ("software-renderer", boost::program_options::value(), "Asks SDL2 to use software renderer") ; boost::program_options::variables_map vm; boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc), vm); boost::program_options::notify(vm); if (vm.count("help")) { std::cout << desc << "\n"; return 0; } if (vm.count("version")) { std::cout << "saland " << VERSION_NUMBER << "\n"; return 0; } if (vm.count("fullscreen")) { Config::getInstance()->setInt("fullscreen", 1); } if (vm.count("no-fullscreen")) { Config::getInstance()->setInt("fullscreen", 0); } if (vm.count("software-renderer")) { Config::getInstance()->setInt("always-software", vm["software-renderer"].as()); } Config::getInstance()->setDefault("fullscreen", "0"); Config::getInstance()->setDefault("draw_collision", "0"); Config::getInstance()->setDefault("world", "world1"); Config::getInstance()->setDefault("player", "player1"); runGame(); Config::getInstance()->save(); return 0; }