diff --git a/src/gui/menu.cpp b/src/gui/menu.cpp index 86aefee9acf..c0c6fddfa15 100644 --- a/src/gui/menu.cpp +++ b/src/gui/menu.cpp @@ -44,15 +44,17 @@ #include "gui/menu_manager.hpp" #include "gui/mousecursor.hpp" #include "math/util.hpp" +#include "supertux/error_handler.hpp" #include "supertux/gameconfig.hpp" #include "supertux/globals.hpp" #include "supertux/resources.hpp" #include "video/drawing_context.hpp" #include "video/renderer.hpp" +#include "video/surface.hpp" #include "video/video_system.hpp" #include "video/viewport.hpp" -#include "supertux/error_handler.hpp" +const float Menu::s_preview_fade_time = 0.1f; Menu::Menu() : m_pos(Vector(static_cast(SCREEN_WIDTH) / 2.0f, @@ -64,8 +66,14 @@ Menu::Menu() : m_menu_help_height(0.0f), m_items(), m_arrange_left(0), - m_active_item(-1) + m_active_item(-1), + m_has_previews(false), + m_last_preview_item(-1), + m_preview_fade_timer(), + m_preview_fade_active(false), + m_preview_fading_out(false) { + m_preview_fade_timer.start(g_config->transitions_enabled ? s_preview_fade_time : 0); } Menu::~Menu() @@ -79,6 +87,23 @@ Menu::set_center_pos(float x, float y) m_pos.y = y; } +void +Menu::align_for_previews(float x_offset) +{ + for (const auto& item : m_items) + { + if (item->get_preview()) + { + // Adjust center position to give space for displaying previews. + set_center_pos(static_cast(SCREEN_WIDTH) / 2 - get_width() / 2 - x_offset, + static_cast(SCREEN_HEIGHT) / 2); + m_has_previews = true; + return; + } + } + m_has_previews = false; +} + /* Add an item to a menu */ MenuItem& Menu::add_item(std::unique_ptr new_item) @@ -578,6 +603,7 @@ Menu::on_window_resize() calculate_width(); calculate_height(); + align_for_previews(); for (auto& item : m_items) item->on_window_resize(); @@ -600,13 +626,12 @@ Menu::draw(DrawingContext& context) const int text_width = static_cast(Resources::normal_font->get_text_width(m_items[m_active_item]->get_help())); const int text_height = static_cast(Resources::normal_font->get_text_height(m_items[m_active_item]->get_help())); - const Rectf text_rect(m_pos.x - static_cast(text_width) / 2.0f - 8.0f, + const Rectf text_rect(static_cast(SCREEN_WIDTH) / 2 - static_cast(text_width) / 2.0f - 8.0f, static_cast(SCREEN_HEIGHT) - 48.0f - static_cast(text_height) / 2.0f - 4.0f, - m_pos.x + static_cast(text_width) / 2.0f + 8.0f, + static_cast(SCREEN_WIDTH) / 2 + static_cast(text_width) / 2.0f + 8.0f, static_cast(SCREEN_HEIGHT) - 48.0f + static_cast(text_height) / 2.0f + 4.0f); - context.color().draw_filled_rect(Rectf(text_rect.p1() - Vector(4,4), - text_rect.p2() + Vector(4,4)), + context.color().draw_filled_rect(text_rect.grown(4), g_config->menuhelpbackcolor, g_config->menuroundness + 4.f, LAYER_GUI); @@ -617,9 +642,92 @@ Menu::draw(DrawingContext& context) LAYER_GUI); context.color().draw_text(Resources::normal_font, m_items[m_active_item]->get_help(), - Vector(m_pos.x, static_cast(SCREEN_HEIGHT) - 48.0f - static_cast(text_height) / 2.0f), + Vector(static_cast(SCREEN_WIDTH) / 2, static_cast(SCREEN_HEIGHT) - 48.0f - static_cast(text_height) / 2.0f), ALIGN_CENTER, LAYER_GUI); } + + if (m_has_previews) draw_preview(context); +} + +void +Menu::draw_preview(DrawingContext& context) +{ + bool valid_last_index = last_preview_index_valid(); + + // Update fade. + if (m_active_item != m_last_preview_item && !m_preview_fade_active) // Index has changed, there is no current fade. + { + if (valid_last_index) // Fade out only if the last index is valid. + m_preview_fade_timer.start(g_config->transitions_enabled ? s_preview_fade_time : 0.f); + m_preview_fading_out = true; + m_preview_fade_active = true; + } + float timeleft = m_preview_fade_timer.get_timeleft(); + if (timeleft < 0 && m_preview_fade_active) // Current fade is over. + { + m_last_preview_item = m_active_item; + valid_last_index = last_preview_index_valid(); // Repeat valid last index check + if (m_preview_fading_out) // After a fade-out, a fade-in should follow up. + { + m_preview_fade_timer.start(g_config->transitions_enabled ? s_preview_fade_time : 0.f); + timeleft = m_preview_fade_timer.get_timeleft(); + m_preview_fading_out = false; + } + else + { + m_preview_fade_active = false; + } + } + + // Set alpha according to fade. + float alpha = 1.f; + if (timeleft > 0) + { + const float alpha_val = timeleft * (1.f / s_preview_fade_time); + alpha = m_preview_fading_out ? alpha_val : 1.f - alpha_val; + } + + // Perform actions only if current index is a valid preview index. + if (valid_last_index) + { + // Draw progress preview of current item. + const Sizef preview_size(static_cast(SCREEN_WIDTH) / 2.5f, static_cast(SCREEN_HEIGHT) / 2.5f); + SurfacePtr preview = m_items[m_last_preview_item]->get_preview(); + const float width_diff = preview_size.width - static_cast(preview->get_width()); + const float height_diff = preview_size.height - static_cast(preview->get_height()); + // If the preview is smaller than the maximal size, make sure to draw it with its original size and adjust position to center. + Rectf preview_rect(Vector(static_cast(context.get_width()) * 0.73f - preview_size.width / 2 + (width_diff > 0 ? width_diff / 2 : 0), + static_cast(context.get_height()) / 2 - preview_size.height / 2 + (height_diff > 0 ? height_diff / 2 : 0)), + Sizef(width_diff > 0 ? static_cast(preview->get_width()) : preview_size.width, + height_diff > 0 ? static_cast(preview->get_height()) : preview_size.height)); + + // If the preview starts overlapping the menu, due to a smaller screen resolution, do not draw it. + // Instead, set the Y position to half the height, so preview data, if available, can still be drawn. + if (preview_rect.get_left() <= m_pos.x + m_menu_width / 2) + { + preview_rect.set_top(preview_rect.get_top() + preview_rect.get_height() / 2); + preview_rect.set_height(0.f); + } + else + { + PaintStyle style; + style.set_alpha(alpha); + context.color().draw_surface_scaled(preview, preview_rect, LAYER_GUI + 1, style); + + // Draw a border around the preview. + context.color().draw_filled_rect(preview_rect.grown(2.f), + Color(1.f, 1.f, 1.f, alpha), 2.f, LAYER_GUI); + } + + // Draw other data, alongside the preview, if available. + draw_preview_data(context, preview_rect, alpha); + } +} + +bool +Menu::last_preview_index_valid() const +{ + return m_last_preview_item > -1 && m_items[m_last_preview_item]->get_preview(); } MenuItem& diff --git a/src/gui/menu.hpp b/src/gui/menu.hpp index 954d8f188d3..341036b5312 100644 --- a/src/gui/menu.hpp +++ b/src/gui/menu.hpp @@ -22,7 +22,9 @@ #include #include "gui/menu_action.hpp" +#include "math/rectf.hpp" #include "math/vector.hpp" +#include "supertux/timer.hpp" #include "video/color.hpp" #include "video/drawing_context.hpp" @@ -54,6 +56,9 @@ class PathObject; class Menu { +protected: + static const float s_preview_fade_time; + public: Menu(); virtual ~Menu(); @@ -113,6 +118,9 @@ class Menu /** Remove all entries from the menu */ void clear(); + /** Align the menu to the left side, if any previews are available. */ + void align_for_previews(float x_offset = 30.f); + MenuItem& get_item(int index) { return *(m_items[index]); } MenuItem& get_item_by_id(int id); @@ -140,9 +148,14 @@ class Menu /** Recalculates the height for this menu */ void calculate_height(); + /** Draw additional data to accompany item previews. */ + virtual void draw_preview_data(DrawingContext& context, const Rectf& preview_rect, float alpha) {} + private: void check_controlfield_change_event(const SDL_Event& event); void draw_item(DrawingContext& context, int index, float y_pos); + void draw_preview(DrawingContext& context); + bool last_preview_index_valid() const; private: /** position of the menu (ie. center of the menu, not top/left) */ @@ -164,6 +177,13 @@ class Menu protected: int m_active_item; + /* Preview implementation variables. */ + bool m_has_previews; + int m_last_preview_item; + Timer m_preview_fade_timer; + bool m_preview_fade_active; + bool m_preview_fading_out; + private: Menu(const Menu&) = delete; Menu& operator=(const Menu&) = delete; diff --git a/src/gui/menu_filesystem.cpp b/src/gui/menu_filesystem.cpp index a8708989e11..a201f721870 100644 --- a/src/gui/menu_filesystem.cpp +++ b/src/gui/menu_filesystem.cpp @@ -28,6 +28,9 @@ #include "util/gettext.hpp" #include "util/string_util.hpp" +const size_t FileSystemMenu::s_title_max_chars = 30; +const std::vector FileSystemMenu::s_image_extensions = { ".jpg", ".png", ".surface" }; + FileSystemMenu::FileSystemMenu(std::string* filename, const std::vector& extensions, const std::string& basedir, bool path_relative_to_basedir, std::function callback, const std::function& item_processor) : @@ -65,7 +68,13 @@ FileSystemMenu::refresh_items() m_files.clear(); m_directory = FileSystem::normalize(m_directory); - add_label(m_directory); + // Make sure label doesn't get too long. + std::string title = m_directory; + const bool title_large = title.size() > s_title_max_chars; + while (title.size() > s_title_max_chars) title = title.substr(title.size() - s_title_max_chars); + if (title_large) title = "..." + title; + + add_label(title); add_hl(); int item_id = 0; @@ -108,8 +117,11 @@ FileSystemMenu::refresh_items() for (const auto& item : m_files) { MenuItem& menu_item = add_entry(item_id, item); + if (in_basedir && m_item_processor) m_item_processor(menu_item); + if (is_image(item)) + menu_item.set_preview(FileSystem::join(m_directory, item)); item_id++; } @@ -123,6 +135,7 @@ FileSystemMenu::refresh_items() // Re-center menu on_window_resize(); + align_for_previews(25.f); } bool @@ -131,12 +144,20 @@ FileSystemMenu::has_right_suffix(const std::string& file) const if (m_extensions.empty()) return true; - for (const auto& extension : m_extensions) { + for (const auto& extension : m_extensions) if (StringUtil::has_suffix(file, extension)) - { return true; - } - } + + return false; +} + +bool +FileSystemMenu::is_image(const std::string& file) const +{ + for (const auto& extension : s_image_extensions) + if (StringUtil::has_suffix(file, extension)) + return true; + return false; } diff --git a/src/gui/menu_filesystem.hpp b/src/gui/menu_filesystem.hpp index f05f6d0794a..2f3f8d6c6e9 100644 --- a/src/gui/menu_filesystem.hpp +++ b/src/gui/menu_filesystem.hpp @@ -21,6 +21,10 @@ class FileSystemMenu final : public Menu { +private: + static const size_t s_title_max_chars; + static const std::vector s_image_extensions; + public: FileSystemMenu(std::string* filename, const std::vector& extensions, const std::string& basedir, bool path_relative_to_basedir, const std::function callback = nullptr, @@ -32,6 +36,7 @@ class FileSystemMenu final : public Menu private: void refresh_items(); bool has_right_suffix(const std::string& file) const; + bool is_image(const std::string& file) const; private: std::string* m_filename; diff --git a/src/gui/menu_item.cpp b/src/gui/menu_item.cpp index 6b6e24777bd..6ccfc8561a0 100644 --- a/src/gui/menu_item.cpp +++ b/src/gui/menu_item.cpp @@ -22,6 +22,7 @@ #include "supertux/globals.hpp" #include "supertux/resources.hpp" #include "video/drawing_context.hpp" +#include "video/surface.hpp" //static const float FLICK_CURSOR_TIME = 0.5f; @@ -29,7 +30,8 @@ MenuItem::MenuItem(const std::string& text, int id) : m_id(id), m_text(text), m_help(), - m_font(Resources::normal_font) + m_font(Resources::normal_font), + m_preview() { } @@ -49,6 +51,12 @@ MenuItem::set_help(const std::string& help_text) } } +void +MenuItem::set_preview(const std::string& preview_file) +{ + m_preview = Surface::from_file(preview_file); +} + void MenuItem::draw(DrawingContext& context, const Vector& pos, int menu_width, bool active) { diff --git a/src/gui/menu_item.hpp b/src/gui/menu_item.hpp index 274c201b37f..80bb89798cd 100644 --- a/src/gui/menu_item.hpp +++ b/src/gui/menu_item.hpp @@ -20,6 +20,8 @@ #include "gui/menu.hpp" +#include "video/surface_ptr.hpp" + class MenuItem { public: @@ -37,6 +39,10 @@ class MenuItem void set_font(const FontPtr font) { m_font = font; } const FontPtr& get_font() const { return m_font; } + void set_preview(const std::string& preview_file); + void set_preview(SurfacePtr preview) { m_preview = preview; } + SurfacePtr get_preview() const { return m_preview; } + /** Draws the menu item. */ virtual void draw(DrawingContext&, const Vector& pos, int menu_width, bool active); @@ -86,6 +92,7 @@ class MenuItem std::string m_text; std::string m_help; FontPtr m_font; + SurfacePtr m_preview; private: MenuItem(const MenuItem&) = delete; diff --git a/src/supertux/gameconfig.cpp b/src/supertux/gameconfig.cpp index 810b8ef025f..3376261154b 100644 --- a/src/supertux/gameconfig.cpp +++ b/src/supertux/gameconfig.cpp @@ -83,6 +83,7 @@ Config::Config() : #else do_release_check(true), #endif + show_world_previews(true), custom_title_levels(true), #ifdef ENABLE_DISCORD enable_discord(false), @@ -155,6 +156,7 @@ Config::load() config_mapping.get("pause_on_focusloss", pause_on_focusloss); config_mapping.get("custom_mouse_cursor", custom_mouse_cursor); config_mapping.get("do_release_check", do_release_check); + config_mapping.get("show_world_previews", show_world_previews); config_mapping.get("custom_title_levels", custom_title_levels); std::optional config_integrations_mapping; @@ -374,6 +376,7 @@ Config::save() writer.write("pause_on_focusloss", pause_on_focusloss); writer.write("custom_mouse_cursor", custom_mouse_cursor); writer.write("do_release_check", do_release_check); + writer.write("show_world_previews", show_world_previews); writer.write("custom_title_levels", custom_title_levels); writer.start_list("integrations"); diff --git a/src/supertux/gameconfig.hpp b/src/supertux/gameconfig.hpp index 97cde1e6491..e72b328eedd 100644 --- a/src/supertux/gameconfig.hpp +++ b/src/supertux/gameconfig.hpp @@ -108,6 +108,7 @@ class Config final bool pause_on_focusloss; bool custom_mouse_cursor; bool do_release_check; + bool show_world_previews; bool custom_title_levels; #ifdef ENABLE_DISCORD diff --git a/src/supertux/menu/contrib_levelset_menu.cpp b/src/supertux/menu/contrib_levelset_menu.cpp index 655b77fd3f0..db3b3c4a162 100644 --- a/src/supertux/menu/contrib_levelset_menu.cpp +++ b/src/supertux/menu/contrib_levelset_menu.cpp @@ -23,7 +23,6 @@ #include "gui/item_action.hpp" #include "supertux/game_manager.hpp" #include "supertux/level_parser.hpp" -#include "supertux/levelset.hpp" #include "supertux/player_status.hpp" #include "supertux/savegame.hpp" #include "supertux/world.hpp" diff --git a/src/supertux/menu/contrib_levelset_menu.hpp b/src/supertux/menu/contrib_levelset_menu.hpp index 09c701bb24d..e6e6c55a56a 100644 --- a/src/supertux/menu/contrib_levelset_menu.hpp +++ b/src/supertux/menu/contrib_levelset_menu.hpp @@ -19,7 +19,8 @@ #include "gui/menu.hpp" -class Levelset; +#include "supertux/levelset.hpp" + class World; class ContribLevelsetMenu final : public Menu diff --git a/src/supertux/menu/options_menu.cpp b/src/supertux/menu/options_menu.cpp index c06f2eeef1c..a7180ebfe22 100644 --- a/src/supertux/menu/options_menu.cpp +++ b/src/supertux/menu/options_menu.cpp @@ -186,6 +186,9 @@ OptionsMenu::OptionsMenu(Type type, bool complete) : add_toggle(MNID_TRANSITIONS, _("Enable transitions"), &g_config->transitions_enabled) .set_help(_("Enable screen transitions and smooth menu animation")); + add_toggle(MNID_WORLD_PREVIEWS, _("Show world previews"), &g_config->show_world_previews) + .set_help(_("Show screenshot previews of the last worldmap state, when hovering over a world.")); + add_toggle(MNID_CUSTOM_TITLE_LEVELS, _("Custom title screen levels"), &g_config->custom_title_levels) .set_help(_("Allow overriding the title screen level, when loading certain worlds")); diff --git a/src/supertux/menu/options_menu.hpp b/src/supertux/menu/options_menu.hpp index 703f7120236..bdc74490da9 100644 --- a/src/supertux/menu/options_menu.hpp +++ b/src/supertux/menu/options_menu.hpp @@ -76,6 +76,7 @@ class OptionsMenu final : public Menu MNID_DEVELOPER_MODE, MNID_CHRISTMAS_MODE, MNID_TRANSITIONS, + MNID_WORLD_PREVIEWS, MNID_CUSTOM_TITLE_LEVELS, MNID_CONFIRMATION_DIALOG, MNID_PAUSE_ON_FOCUSLOSS, diff --git a/src/supertux/menu/sorted_contrib_menu.cpp b/src/supertux/menu/sorted_contrib_menu.cpp index 56f6af5ff8c..bae18820001 100644 --- a/src/supertux/menu/sorted_contrib_menu.cpp +++ b/src/supertux/menu/sorted_contrib_menu.cpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2021 mrkubax10 +// 2022-2023 Vankata453 // // 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 @@ -16,55 +17,51 @@ #include "supertux/menu/sorted_contrib_menu.hpp" -#include -#include "util/gettext.hpp" -#include "supertux/savegame.hpp" -#include "supertux/player_status.hpp" -#include "supertux/levelset.hpp" -#include "supertux/game_manager.hpp" -#include "supertux/menu/contrib_levelset_menu.hpp" -#include "gui/menu_manager.hpp" -#include "gui/menu_item.hpp" #include "gui/item_action.hpp" -SortedContribMenu::SortedContribMenu(std::vector>& worlds, const std::string& contrib_type, const std::string& title, const std::string& empty_message) : - m_world_folders() +#include "supertux/world.hpp" +#include "util/file_system.hpp" +#include "util/gettext.hpp" + +SortedContribMenu::SortedContribMenu(std::vector>& worlds, const std::string& contrib_type, + const std::string& title, const std::string& empty_message) { add_label(title); add_hl(); - int world_id = 0; - for (unsigned int i = 0; i < worlds.size(); i++) + + bool has_worlds = false; + for (const auto& world : worlds) { - if (worlds[i]->get_contrib_type() == contrib_type) + if (world->get_contrib_type() == contrib_type) { - m_world_folders.push_back(worlds[i]->get_basedir()); - std::string title_str = worlds[i]->get_title(); - if (worlds[i]->is_levelset()) - title_str = "[" + title_str + "]"; - add_entry(world_id++, title_str).set_help(worlds[i]->get_description()); + has_worlds = true; + + const auto savegame = Savegame::from_current_profile(world->get_basename()); + + ItemAction* item; + if (world->is_levelset()) + { + item = &add_world("[" + world->get_title() + "]", world->get_basedir(), + savegame->get_levelset_progress()); + } + else + { + const std::string preview_file = FileSystem::join("previews", FileSystem::strip_extension(FileSystem::basename(world->get_basename())) + ".png"); + SurfacePtr preview = find_preview(preview_file, world->get_basedir()); + + item = &add_world(world->get_title(), world->get_basedir(), + savegame->get_worldmap_progress(), preview); + } + item->set_help(world->get_description()); } } - if (world_id == 0) + if (!has_worlds) { add_inactive(empty_message); } add_hl(); add_back(_("Back")); + + align_for_previews(); } -void -SortedContribMenu::menu_action(MenuItem& item) -{ - int index = item.get_id(); - if (index >= 0) - { - std::unique_ptr world = World::from_directory(m_world_folders[index]); - if (world->is_levelset()) - { - MenuManager::instance().push_menu(std::unique_ptr(new ContribLevelsetMenu(std::move(world)))); - } - else - { - GameManager::current()->start_worldmap(*world); - } - } -} + /* EOF */ diff --git a/src/supertux/menu/sorted_contrib_menu.hpp b/src/supertux/menu/sorted_contrib_menu.hpp index e3b7417ea09..be59ac2ea7d 100644 --- a/src/supertux/menu/sorted_contrib_menu.hpp +++ b/src/supertux/menu/sorted_contrib_menu.hpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2021 mrkubax10 +// 2022-2023 Vankata453 // // 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 @@ -16,18 +17,22 @@ #ifndef HEADER_SUPERTUX_SUPERTUX_MENU_SORTED_CONTRIB_MENU_HPP #define HEADER_SUPERTUX_SUPERTUX_MENU_SORTED_CONTRIB_MENU_HPP -#include "gui/menu.hpp" -#include "supertux/world.hpp" -class SortedContribMenu : public Menu + +#include "supertux/menu/world_preview_menu.hpp" + +class World; + +class SortedContribMenu final : public WorldPreviewMenu { public: - SortedContribMenu(std::vector>& worlds, const std::string& contrib_type, const std::string& title, const std::string& empty_message); - void menu_action(MenuItem& item) override; -private: - std::vector m_world_folders; + SortedContribMenu(std::vector>& worlds, const std::string& contrib_type, + const std::string& title, const std::string& empty_message); + private: SortedContribMenu(const SortedContribMenu&) = delete; SortedContribMenu& operator=(const SortedContribMenu&) = delete; }; + #endif + /* EOF */ diff --git a/src/supertux/menu/world_preview_menu.cpp b/src/supertux/menu/world_preview_menu.cpp new file mode 100644 index 00000000000..870a282654c --- /dev/null +++ b/src/supertux/menu/world_preview_menu.cpp @@ -0,0 +1,126 @@ +// SuperTux +// Copyright (C) 2022-2023 Vankata453 +// +// 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 3 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 . + +#include "supertux/menu/world_preview_menu.hpp" + +#include + +#include +#include + +#include "gui/item_action.hpp" +#include "gui/menu_manager.hpp" +#include "gui/menu_item.hpp" +#include "supertux/game_manager.hpp" +#include "supertux/gameconfig.hpp" +#include "supertux/menu/contrib_levelset_menu.hpp" +#include "supertux/resources.hpp" +#include "supertux/world.hpp" +#include "util/file_system.hpp" +#include "video/drawing_context.hpp" +#include "video/surface.hpp" + +WorldPreviewMenu::WorldPreviewMenu() : + m_world_entries() +{ +} + +ItemAction& +WorldPreviewMenu::add_world(const std::string& title, const std::string& folder, + Savegame::Progress progress, SurfacePtr preview) +{ + ItemAction& item = add_entry(static_cast(m_world_entries.size()), title); + + if (!preview || !g_config->show_world_previews) // No preview, or previews are not enabled, so progress should be shown on the menu item. + { + if (progress.total > 0) // Only show progress, if provided. + { + item.set_text(fmt::format(fmt::runtime("{} ({}/{}, {}/{}; {}%)"), + title, progress.solved, progress.total, + progress.perfect, progress.total, progress.get_percentage())); + } + + m_world_entries.push_back({ folder, "" }); + } + else // Progress can be shown under the preview. + { + item.set_preview(preview); + + std::string progress_text; + if (progress.total > 0) // Only show progress, if provided. + progress_text = fmt::format(fmt::runtime(_("{}/{} finished, {}/{} perfected ({}%)")), + progress.solved, progress.total, progress.perfect, progress.total, progress.get_percentage()); + + m_world_entries.push_back({ folder, progress_text }); + } + + return item; +} + +SurfacePtr +WorldPreviewMenu::find_preview(const std::string& preview_file, const std::string& basedir) +{ + if (!g_config->show_world_previews) + return nullptr; + + std::string preview_path = FileSystem::join("profile" + std::to_string(g_config->profile), preview_file); + if (PHYSFS_exists(preview_path.c_str())) // A preview exists. + { + return Surface::from_file(preview_path); + } + else // Preview doesn't exist, so check for a placeholder. + { + preview_path = FileSystem::join(basedir, "preview.png"); + if (PHYSFS_exists(preview_path.c_str())) // A preview placeholder exists. + return Surface::from_file(preview_path); + } + + return nullptr; +} + +void +WorldPreviewMenu::draw_preview_data(DrawingContext& context, const Rectf& preview_rect, float alpha) +{ + const int index = m_items[m_last_preview_item]->get_id(); + if (index < 0 || index >= static_cast(m_world_entries.size())) // Not a valid world index. + return; + + // Draw world progress. + if (!m_world_entries[index].progress_text.empty()) + context.color().draw_text(Resources::normal_font, m_world_entries[index].progress_text, + Vector(preview_rect.get_left() + static_cast(SCREEN_WIDTH) / 5, preview_rect.get_bottom() * 1.03f), + ALIGN_CENTER, LAYER_GUI, Color(1, 1, 1, alpha)); +} + +void +WorldPreviewMenu::menu_action(MenuItem& item) +{ + const int index = item.get_id(); + if (index >= 0 && index < static_cast(m_world_entries.size())) // Valid world index. + { + std::unique_ptr world = World::from_directory(m_world_entries[index].folder); + if (world->is_levelset()) + { + MenuManager::instance().push_menu(std::make_unique(std::move(world))); + } + else + { + GameManager::current()->start_worldmap(*world); + } + } +} + +/* EOF */ diff --git a/src/supertux/menu/world_preview_menu.hpp b/src/supertux/menu/world_preview_menu.hpp new file mode 100644 index 00000000000..6846617b35d --- /dev/null +++ b/src/supertux/menu/world_preview_menu.hpp @@ -0,0 +1,57 @@ +// SuperTux +// Copyright (C) 2022-2023 Vankata453 +// +// 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 3 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 . + +#ifndef HEADER_SUPERTUX_SUPERTUX_MENU_WORLD_PREVIEW_MENU_HPP +#define HEADER_SUPERTUX_SUPERTUX_MENU_WORLD_PREVIEW_MENU_HPP + +#include "gui/menu.hpp" + +#include "supertux/savegame.hpp" +#include "video/surface_ptr.hpp" + +/** Represents a menu, which shows world preview screenshots and progress. */ +class WorldPreviewMenu : public Menu +{ +public: + WorldPreviewMenu(); + + void menu_action(MenuItem& item) override; + +protected: + ItemAction& add_world(const std::string& title, const std::string& folder, + Savegame::Progress progress = {}, SurfacePtr preview = nullptr); + + SurfacePtr find_preview(const std::string& preview_file, const std::string& basedir); + void draw_preview_data(DrawingContext& context, const Rectf& preview_rect, float alpha) override; + +private: + struct WorldEntry + { + const std::string folder; + const std::string progress_text; + }; + +private: + std::vector m_world_entries; + +private: + WorldPreviewMenu(const WorldPreviewMenu&) = delete; + WorldPreviewMenu& operator=(const WorldPreviewMenu&) = delete; +}; + +#endif + +/* EOF */ diff --git a/src/supertux/menu/world_set_menu.cpp b/src/supertux/menu/world_set_menu.cpp index 7723a78df5a..547bad1ac06 100644 --- a/src/supertux/menu/world_set_menu.cpp +++ b/src/supertux/menu/world_set_menu.cpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2015 Matthew +// 2022-2023 Vankata453 // // 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 @@ -18,35 +19,37 @@ #include "gui/menu_item.hpp" #include "gui/menu_manager.hpp" -#include "supertux/game_manager.hpp" #include "supertux/menu/menu_storage.hpp" -#include "supertux/world.hpp" #include "util/gettext.hpp" WorldSetMenu::WorldSetMenu() { - add_label(_("Start Game")); - add_hl(); - add_entry(WORLDSET_STORY, _("Story Mode")); - add_entry(WORLDSET_CONTRIB, _("Contrib Levels")); - add_hl(); - add_back(_("Back")); + add_label(_("Start Game")); + add_hl(); + + // Add Story Mode entry. + // Story Mode should not have its progress shown. + add_world(_("Story Mode"), "levels/world1", {}, find_preview("previews/world1.png", "levels/world1")); + + add_entry(1, _("Contrib Levels")); + add_hl(); + add_back(_("Back")); + + align_for_previews(0.5f); } -void WorldSetMenu::menu_action(MenuItem& item) +void +WorldSetMenu::menu_action(MenuItem& item) { switch (item.get_id()) { - case WORLDSET_STORY: - { - std::unique_ptr world = World::from_directory("levels/world1"); - GameManager::current()->start_worldmap(*world); - break; - } - - case WORLDSET_CONTRIB: + case 1: MenuManager::instance().push_menu(MenuStorage::CONTRIB_MENU); break; + + default: + WorldPreviewMenu::menu_action(item); + break; } } diff --git a/src/supertux/menu/world_set_menu.hpp b/src/supertux/menu/world_set_menu.hpp index ab91d1a159e..e2bf52b41c5 100644 --- a/src/supertux/menu/world_set_menu.hpp +++ b/src/supertux/menu/world_set_menu.hpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2015 Matthew +// 2022-2023 Vankata453 // // 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 @@ -17,20 +18,18 @@ #ifndef HEADER_SUPERTUX_SUPERTUX_MENU_WORLD_SET_MENU_HPP #define HEADER_SUPERTUX_SUPERTUX_MENU_WORLD_SET_MENU_HPP -#include "gui/menu.hpp" +#include "supertux/menu/world_preview_menu.hpp" -enum WorldSetMenuIDs -{ - WORLDSET_STORY, - WORLDSET_CONTRIB -}; - -class WorldSetMenu final : public Menu +class WorldSetMenu final : public WorldPreviewMenu { public: WorldSetMenu(); void menu_action(MenuItem& item) override; + +private: + WorldSetMenu(const WorldSetMenu&) = delete; + WorldSetMenu& operator=(const WorldSetMenu&) = delete; }; #endif diff --git a/src/supertux/menu/worldmap_menu.cpp b/src/supertux/menu/worldmap_menu.cpp index 2b1524c1ce9..237f2e62fb5 100644 --- a/src/supertux/menu/worldmap_menu.cpp +++ b/src/supertux/menu/worldmap_menu.cpp @@ -19,9 +19,8 @@ #include "gui/menu_item.hpp" #include "gui/menu_manager.hpp" #include "supertux/menu/menu_storage.hpp" -#include "supertux/screen_fade.hpp" -#include "supertux/screen_manager.hpp" #include "util/gettext.hpp" +#include "worldmap/worldmap.hpp" WorldmapMenu::WorldmapMenu() { @@ -43,8 +42,8 @@ WorldmapMenu::menu_action(MenuItem& item) break; case MNID_QUITWORLDMAP: - MenuManager::instance().clear_menu_stack(); - ScreenManager::current()->pop_screen(); + MenuManager::instance().clear_menu_stack(true); // Skip transition. + worldmap::WorldMap::current()->quit(); break; } } diff --git a/src/supertux/savegame.cpp b/src/supertux/savegame.cpp index a3e76bf73b2..7d490c010c2 100644 --- a/src/supertux/savegame.cpp +++ b/src/supertux/savegame.cpp @@ -25,7 +25,6 @@ #include "physfs/util.hpp" #include "squirrel/serialize.hpp" #include "squirrel/squirrel_virtual_machine.hpp" -#include "supertux/player_status.hpp" #include "supertux/profile_manager.hpp" #include "util/file_system.hpp" #include "util/log.hpp" @@ -102,6 +101,12 @@ LevelsetState::get_level_state(const std::string& filename) const } } +uint32_t +Savegame::Progress::get_percentage() const +{ + // Calculate the percentage using both the solved and perfected level counts. + return solved > 0 ? static_cast(static_cast(solved + perfect) / static_cast(total * 2) * 100) : 0; +} std::unique_ptr Savegame::from_profile(int profile, const std::string& world_name, bool base_data) @@ -245,16 +250,6 @@ Savegame::save() writer.start_list("supertux-savegame"); writer.write("version", 1); - using namespace worldmap; - if (WorldMap::current() != nullptr) - { - std::ostringstream title; - title << WorldMap::current()->get_title(); - title << " (" << WorldMap::current()->solved_level_count() - << "/" << WorldMap::current()->level_count() << ")"; - writer.write("title", title.str()); - } - writer.start_list("tux"); m_player_status->write(writer); writer.end_list("tux"); @@ -310,8 +305,21 @@ Savegame::get_worldmap_state(const std::string& name) worlds.rename(old_map_filename.c_str(), name.c_str()); } - ssq::Table levels = worlds.getOrCreateTable(name.c_str()).getOrCreateTable("levels"); - result.level_states = get_level_states(levels); + ssq::Table world = worlds.getOrCreateTable(name.c_str()); + for (const auto& [_, value] : world.convertRaw()) + { + if (value.getType() != ssq::Type::TABLE) + continue; + + ssq::Table sector = value.toTable(); + + if (sector.hasEntry("levels")) + { + ssq::Table levels = sector.findTable("levels"); + for (const auto& level_state : get_level_states(levels)) + result.level_states.push_back(std::move(level_state)); + } + } } catch(const std::exception& err) { @@ -381,4 +389,52 @@ Savegame::set_levelset_state(const std::string& basedir, } } +Savegame::Progress +Savegame::get_levelset_progress() +{ + Progress progress; + + for (const std::string& levelset : get_levelsets()) + { + for (const auto& level_state : get_levelset_state(levelset).level_states) + { + if (level_state.filename.empty()) + continue; + + if (level_state.solved) + progress.solved++; + if (level_state.perfect) + progress.perfect++; + + progress.total++; + } + } + + return progress; +} + +Savegame::Progress +Savegame::get_worldmap_progress() +{ + Progress progress; + + for (const std::string& map : get_worldmaps()) + { + for (const auto& level_state : get_worldmap_state(map).level_states) + { + if (level_state.filename.empty()) + continue; + + if (level_state.solved) + progress.solved++; + if (level_state.perfect) + progress.perfect++; + + progress.total++; + } + } + + return progress; +} + /* EOF */ diff --git a/src/supertux/savegame.hpp b/src/supertux/savegame.hpp index a36b64623b9..448b5c9d539 100644 --- a/src/supertux/savegame.hpp +++ b/src/supertux/savegame.hpp @@ -24,7 +24,8 @@ #include -class PlayerStatus; +#include "supertux/player_status.hpp" + class Profile; struct LevelState @@ -69,6 +70,20 @@ struct WorldmapState class Savegame final { public: + struct Progress final + { + Progress() : + solved(), + perfect(), + total() + {} + uint32_t solved; + uint32_t perfect; + uint32_t total; + + uint32_t get_percentage() const; + }; + static std::unique_ptr from_profile(int profile, const std::string& world_name, bool base_data = false); static std::unique_ptr from_current_profile(const std::string& world_name, bool base_data = false); @@ -92,6 +107,9 @@ class Savegame final std::vector get_worldmaps(); WorldmapState get_worldmap_state(const std::string& name); + Progress get_levelset_progress(); + Progress get_worldmap_progress(); + void save(); bool is_title_screen() const; diff --git a/src/supertux/screen_manager.cpp b/src/supertux/screen_manager.cpp index 3f3bdd269cc..06fd61784d5 100644 --- a/src/supertux/screen_manager.cpp +++ b/src/supertux/screen_manager.cpp @@ -146,7 +146,8 @@ ScreenManager::ScreenManager(VideoSystem& video_system, InputManager& input_mana m_speed(1.0), m_actions(), m_screen_fade(), - m_screen_stack() + m_screen_stack(), + m_draw_hud(true) { } @@ -268,26 +269,27 @@ ScreenManager::draw(Compositor& compositor, FPS_Stats& fps_statistics) // draw effects and hud auto& context = compositor.make_context(true); - m_menu_manager->draw(context); - if (m_screen_fade) { + if (m_screen_fade) m_screen_fade->draw(context); - } - Console::current()->draw(context); + if (m_draw_hud) + { + m_menu_manager->draw(context); + + Console::current()->draw(context); - if (g_config->mobile_controls) - m_mobile_controller.draw(context); + if (g_config->mobile_controls) + m_mobile_controller.draw(context); - if (g_config->show_fps) - draw_fps(context, fps_statistics); + if (g_config->show_fps) + draw_fps(context, fps_statistics); - if (g_config->show_controller) { - m_controller_hud->draw(context); - } + if (g_config->show_controller) + m_controller_hud->draw(context); - if (g_config->show_player_pos) { - draw_player_pos(context); + if (g_config->show_player_pos) + draw_player_pos(context); } // render everything diff --git a/src/supertux/screen_manager.hpp b/src/supertux/screen_manager.hpp index 91c7eba7e91..28fceb8749b 100644 --- a/src/supertux/screen_manager.hpp +++ b/src/supertux/screen_manager.hpp @@ -48,6 +48,8 @@ class ScreenManager final : public Currenton void run(); void quit(std::unique_ptr fade = {}); + + void set_draw_hud(bool enabled) { m_draw_hud = enabled; } void set_speed(float speed); float get_speed() const; bool has_pending_fadeout() const; @@ -102,6 +104,12 @@ class ScreenManager final : public Currenton std::unique_ptr m_screen_fade; std::vector > m_screen_stack; + + bool m_draw_hud; + +private: + ScreenManager(const ScreenManager&) = delete; + ScreenManager& operator=(const ScreenManager&) = delete; }; #endif diff --git a/src/worldmap/worldmap.cpp b/src/worldmap/worldmap.cpp index 89d1b3bc829..6c521c21997 100644 --- a/src/worldmap/worldmap.cpp +++ b/src/worldmap/worldmap.cpp @@ -18,6 +18,8 @@ #include "worldmap/worldmap.hpp" +#include + #include "audio/sound_manager.hpp" #include "gui/menu_manager.hpp" #include "physfs/util.hpp" @@ -27,6 +29,7 @@ #include "supertux/gameconfig.hpp" #include "supertux/menu/menu_storage.hpp" #include "supertux/player_status.hpp" +#include "supertux/player_status_hud.hpp" #include "supertux/screen_manager.hpp" #include "supertux/tile_manager.hpp" #include "util/file_system.hpp" @@ -35,6 +38,8 @@ #include "util/reader_document.hpp" #include "util/reader_mapping.hpp" #include "video/drawing_context.hpp" +#include "video/sdl_surface.hpp" +#include "video/sdl_surface_ptr.hpp" #include "worldmap/direction.hpp" #include "worldmap/level_tile.hpp" #include "worldmap/tux.hpp" @@ -61,7 +66,8 @@ WorldMap::WorldMap(const std::string& filename, Savegame& savegame, m_passive_message_timer(), m_enter_level(false), m_in_level(false), - m_in_world_select(false) + m_in_world_select(false), + m_screenshot_request(false) { SoundManager::current()->preload("sounds/warp.wav"); @@ -115,12 +121,32 @@ WorldMap::setup() void WorldMap::leave() { + if (m_screenshot_request) + { + take_preview_screenshot(); + ScreenManager::current()->set_draw_hud(true); + } + save_state(); m_sector->leave(); GameManager::current()->load_next_worldmap(); } +void +WorldMap::quit() +{ + if (g_config->show_world_previews) + { + // Prepare to take preview screenshot, when leaving the worldmap. + m_screenshot_request = true; + ScreenManager::current()->set_draw_hud(false); + m_sector->get_singleton_by_type().remove_me(); + } + + ScreenManager::current()->pop_screen(); +} + void WorldMap::draw(DrawingContext& context) @@ -193,6 +219,36 @@ WorldMap::process_input(const Controller& controller) } +void +WorldMap::take_preview_screenshot() +{ + SDLSurfacePtr screenshot = VideoSystem::current()->make_screenshot(); + if (!screenshot) + { + log_warning << "Error taking worldmap preview screenshot." << std::endl; + return; + } + + const std::string directory = FileSystem::join("profile" + std::to_string(g_config->profile), "previews"); + if (!PHYSFS_exists(directory.c_str()) && !PHYSFS_mkdir(directory.c_str())) + { + log_warning << "Cannot create directory '" << directory << "' for worldmap previews." << std::endl; + return; + } + + const std::string file = FileSystem::strip_extension(FileSystem::basename(m_savegame.get_filename())) + ".png"; + const std::string path = FileSystem::join(directory, file); + if (PHYSFS_exists(path.c_str()) && !PHYSFS_delete(path.c_str())) + { + log_warning << "Error deleting existing worldmap screenshot preview '" << path << "'." << std::endl; + return; + } + + if (!SDLSurface::save_png(*screenshot, path)) + log_warning << "Cannot save worldmap screenshot preview '" << path << "'." << std::endl; +} + + void WorldMap::on_escape_press() { diff --git a/src/worldmap/worldmap.hpp b/src/worldmap/worldmap.hpp index abfcc4e3c24..1a1a087d392 100644 --- a/src/worldmap/worldmap.hpp +++ b/src/worldmap/worldmap.hpp @@ -46,6 +46,7 @@ class WorldMap final : public Currenton void setup(); void leave(); + void quit(); void draw(DrawingContext& context); void update(float dt_sec); @@ -89,6 +90,8 @@ class WorldMap final : public Currenton const std::string& get_filename() const; private: + void take_preview_screenshot(); + void on_escape_press(); private: @@ -114,6 +117,7 @@ class WorldMap final : public Currenton bool m_enter_level; bool m_in_level; bool m_in_world_select; + bool m_screenshot_request; private: WorldMap(const WorldMap&) = delete; diff --git a/src/worldmap/worldmap_sector.cpp b/src/worldmap/worldmap_sector.cpp index c7d4ada2ca4..b8e61b2cb83 100644 --- a/src/worldmap/worldmap_sector.cpp +++ b/src/worldmap/worldmap_sector.cpp @@ -206,7 +206,8 @@ WorldMapSector::draw(DrawingContext& context) } } - draw_status(context); + if (!m_parent.m_screenshot_request) + draw_status(context); } void diff --git a/src/worldmap/worldmap_state.cpp b/src/worldmap/worldmap_state.cpp index 423bd21c79d..a19beb7b1a3 100644 --- a/src/worldmap/worldmap_state.cpp +++ b/src/worldmap/worldmap_state.cpp @@ -37,6 +37,7 @@ namespace worldmap { WorldMapState::WorldMapState(WorldMap& worldmap) : m_worldmap(worldmap), + m_sector(), m_position_was_reset(false) { } @@ -61,47 +62,36 @@ WorldMapState::load_state() /** Get state table for the current worldmap. **/ ssq::Table worldmap_table = worlds_table.findTable(m_worldmap.m_map_filename.c_str()); - // Load the current sector. - ssq::Table sector_table; - if (worldmap_table.hasEntry("sector")) // Load the current sector only if a "sector" property exists. - { - const std::string sector_name = worldmap_table.get("sector"); - if (!m_worldmap.m_sector) // If the worldmap doesn't have a current sector, try setting the new sector. - m_worldmap.set_sector(sector_name, "", false); - - /** Get state table for the current sector. **/ - sector_table = worldmap_table.findTable(m_worldmap.get_sector().get_name().c_str()); - } - else // Sector property does not exist, which may indicate outdated save file. + // If the "sector" property doesn't exist, that may indicate an outdated savefile. + // To support savefiles before the implementation of worldmap sectors, we load the state table for the only "main" sector. + if (!worldmap_table.hasEntry("sector")) { if (!m_worldmap.m_sector) // If the worldmap doesn't have a current sector, try setting the main one. m_worldmap.set_sector(DEFAULT_SECTOR_NAME, "", false); - sector_table = worldmap_table; - } + if (!m_worldmap.m_sector) + throw std::runtime_error("No sector set."); - if (!m_worldmap.m_sector) - { - // Quit loading worldmap state, if there is still no current sector loaded. - throw std::runtime_error("No sector set."); + m_sector = m_worldmap.m_sector; + load(worldmap_table); + return; } - - try - { - ssq::Object music = sector_table.find("music"); - auto& music_object = m_worldmap.get_sector().get_singleton_by_type(); - music_object.set_music(music.toString()); - } - catch (const ssq::NotFoundException&) + + const std::string sector_name = worldmap_table.get("sector"); + if (!m_worldmap.m_sector) // If the worldmap doesn't have a current sector, try setting the new sector. + m_worldmap.set_sector(sector_name, "", false); + + /** Load state table for all sectors in the worldmap. **/ + for (auto& sector : m_worldmap.m_sectors) { - log_debug << "Could not find \"music\" in the worldmap sector state table." << std::endl; - } + // Only load sector table, if it exists. + if (!worldmap_table.hasEntry(sector->get_name().c_str())) + continue; - /** Load objects. **/ - load_tux(sector_table); - load_levels(sector_table); - load_tilemap_visibility(sector_table); - load_sprite_change_objects(sector_table); + ssq::Table sector_table = worldmap_table.findTable(sector->get_name().c_str()); + m_sector = sector.get(); + load(sector_table); + } } catch (const std::exception& err) { @@ -119,15 +109,39 @@ WorldMapState::load_state() } +void +WorldMapState::load(const ssq::Table& table) +{ + try + { + ssq::Object music = table.find("music"); + auto& music_object = m_worldmap.get_sector().get_singleton_by_type(); + music_object.set_music(music.toString()); + } + catch (const ssq::NotFoundException&) + { + log_warning << "Couldn't find \"music\" in worldmap sector state table." << std::endl; + } + + /** Load objects. **/ + load_tux(table); + load_levels(table); + load_tilemap_visibility(table); + load_sprite_change_objects(table); +} + /** Load Tux **/ void WorldMapState::load_tux(const ssq::Table& table) { WorldMapSector& sector = m_worldmap.get_sector(); + // Indicate if Tux is being loaded for the current sector. + const bool current_sector = m_sector == &m_worldmap.get_sector(); + const ssq::Table tux = table.findTable("tux"); Vector p(0.0f, 0.0f); - if (!tux.get("x", p.x) || !tux.get("y", p.y)) + if ((!tux.get("x", p.x) || !tux.get("y", p.y)) && current_sector) { log_warning << "Player position not set, respawning." << std::endl; sector.move_to_spawnpoint(DEFAULT_SPAWNPOINT_NAME); @@ -140,7 +154,8 @@ WorldMapState::load_tux(const ssq::Table& table) sector.m_tux->set_tile_pos(p); int tile_data = sector.tile_data_at(p); - if (!(tile_data & (Tile::WORLDMAP_NORTH | Tile::WORLDMAP_SOUTH | Tile::WORLDMAP_WEST | Tile::WORLDMAP_EAST))) + if (!(tile_data & (Tile::WORLDMAP_NORTH | Tile::WORLDMAP_SOUTH | Tile::WORLDMAP_WEST | Tile::WORLDMAP_EAST)) && + current_sector) { log_warning << "Player at illegal position " << p.x << ", " << p.y << " respawning." << std::endl; sector.move_to_spawnpoint(DEFAULT_SPAWNPOINT_NAME); @@ -248,10 +263,8 @@ WorldMapState::load_sprite_change_objects(const ssq::Table& table) void -WorldMapState::save_state() const +WorldMapState::save_state() { - WorldMapSector& sector = m_worldmap.get_sector(); - ssq::VM& vm = SquirrelVirtualMachine::current()->get_vm(); try { @@ -262,53 +275,58 @@ WorldMapState::save_state() const ssq::Table worldmap_table = worlds_table.getOrCreateTable(m_worldmap.m_map_filename.c_str()); // Save the current sector. - worldmap_table.set("sector", sector.get_name()); + worldmap_table.set("sector", m_worldmap.get_sector().get_name()); + + for (auto& sector : m_worldmap.m_sectors) + { + m_sector = sector.get(); - /** Delete the table entry for the current sector and construct a new one. **/ - worldmap_table.remove(sector.get_name().c_str()); - ssq::Table sector_table = worldmap_table.addTable(sector.get_name().c_str()); + /** Delete the table entry for the current sector and construct a new one. **/ + worldmap_table.remove(sector->get_name().c_str()); + ssq::Table sector_table = worldmap_table.addTable(sector->get_name().c_str()); - /** Save Music **/ - auto& music_object = m_worldmap.get_sector().get_singleton_by_type(); - sector_table.set("music", music_object.get_music()); + /** Save music **/ + auto& music_object = m_worldmap.get_sector().get_singleton_by_type(); + sector_table.set("music", music_object.get_music()); - /** Save Tux **/ - ssq::Table tux = sector_table.addTable("tux"); - tux.set("x", sector.m_tux->get_tile_pos().x); - tux.set("y", sector.m_tux->get_tile_pos().y); - tux.set("back", direction_to_string(sector.m_tux->m_back_direction)); + /** Save Tux **/ + ssq::Table tux = sector_table.addTable("tux"); + tux.set("x", sector->m_tux->get_tile_pos().x); + tux.set("y", sector->m_tux->get_tile_pos().y); + tux.set("back", direction_to_string(sector->m_tux->m_back_direction)); - /** Save levels **/ - ssq::Table levels = sector_table.addTable("levels"); - for (const auto& level_tile : m_worldmap.get_sector().get_objects_by_type()) - { - ssq::Table level = levels.addTable(level_tile.get_level_filename().c_str()); - level.set("solved", level_tile.is_solved()); - level.set("perfect", level_tile.is_perfect()); - level_tile.get_statistics().serialize_to_squirrel(level); - } + /** Save levels **/ + ssq::Table levels = sector_table.addTable("levels"); + for (const auto& level_tile : m_worldmap.get_sector().get_objects_by_type()) + { + ssq::Table level = levels.addTable(level_tile.get_level_filename().c_str()); + level.set("solved", level_tile.is_solved()); + level.set("perfect", level_tile.is_perfect()); + level_tile.get_statistics().serialize_to_squirrel(level); + } - /** Save tilemap visibility **/ - ssq::Table tilemaps = sector_table.addTable("tilemaps"); - for (auto& tilemap : m_worldmap.get_sector().get_objects_by_type<::TileMap>()) - { - if (!tilemap.get_name().empty()) + /** Save tilemap visibility **/ + ssq::Table tilemaps = sector_table.addTable("tilemaps"); + for (auto& tilemap : m_worldmap.get_sector().get_objects_by_type<::TileMap>()) { - ssq::Table tilemap_table = tilemaps.addTable(tilemap.get_name().c_str()); - tilemap_table.set("alpha", tilemap.get_target_alpha()); + if (!tilemap.get_name().empty()) + { + ssq::Table tilemap_table = tilemaps.addTable(tilemap.get_name().c_str()); + tilemap_table.set("alpha", tilemap.get_target_alpha()); + } } - } - /** Save sprite change objects **/ - if (m_worldmap.get_sector().get_object_count() > 0) - { - ssq::Table sprite_changes = sector_table.addTable("sprite-changes"); - for (const auto& sc : m_worldmap.get_sector().get_objects_by_type()) + /** Save sprite change objects **/ + if (m_worldmap.get_sector().get_object_count() > 0) { - const std::string key = std::to_string(static_cast(sc.get_pos().x)) + "_" + - std::to_string(static_cast(sc.get_pos().y)); - ssq::Table sprite_change = sprite_changes.addTable(key.c_str()); - sprite_change.set("show-stay-action", sc.show_stay_action()); + ssq::Table sprite_changes = sector_table.addTable("sprite-changes"); + for (const auto& sc : m_worldmap.get_sector().get_objects_by_type()) + { + const std::string key = std::to_string(static_cast(sc.get_pos().x)) + "_" + + std::to_string(static_cast(sc.get_pos().y)); + ssq::Table sprite_change = sprite_changes.addTable(key.c_str()); + sprite_change.set("show-stay-action", sc.show_stay_action()); + } } } } diff --git a/src/worldmap/worldmap_state.hpp b/src/worldmap/worldmap_state.hpp index c0257d39a53..dc5bd1d26af 100644 --- a/src/worldmap/worldmap_state.hpp +++ b/src/worldmap/worldmap_state.hpp @@ -26,6 +26,7 @@ class Table; namespace worldmap { class WorldMap; +class WorldMapSector; class WorldMapState final { @@ -33,9 +34,10 @@ class WorldMapState final WorldMapState(WorldMap& worldmap); void load_state(); - void save_state() const; + void save_state(); private: + void load(const ssq::Table& table); void load_tux(const ssq::Table& table); void load_levels(const ssq::Table& table); void load_tilemap_visibility(const ssq::Table& table); @@ -43,6 +45,7 @@ class WorldMapState final private: WorldMap& m_worldmap; + WorldMapSector* m_sector; /** Variables, related to loading. **/ bool m_position_was_reset;