diff options
19 files changed, 1122 insertions, 225 deletions
diff --git a/src/openvic-simulation/GameManager.hpp b/src/openvic-simulation/GameManager.hpp index 9ee3442..6be2938 100644 --- a/src/openvic-simulation/GameManager.hpp +++ b/src/openvic-simulation/GameManager.hpp @@ -5,6 +5,7 @@ #include "openvic-simulation/country/Country.hpp" #include "openvic-simulation/economy/EconomyManager.hpp" #include "openvic-simulation/history/HistoryManager.hpp" +#include "openvic-simulation/interface/UI.hpp" #include "openvic-simulation/map/Map.hpp" #include "openvic-simulation/military/MilitaryManager.hpp" #include "openvic-simulation/misc/Define.hpp" @@ -24,6 +25,7 @@ namespace OpenVic { HistoryManager history_manager; PopManager pop_manager; CountryManager country_manager; + UIManager ui_manager; GameAdvancementHook clock; time_t session_start; /* SS-54, as well as allowing time-tracking */ @@ -47,6 +49,7 @@ namespace OpenVic { REF_GETTERS(history_manager) REF_GETTERS(pop_manager) REF_GETTERS(country_manager) + REF_GETTERS(ui_manager) REF_GETTERS(clock) bool setup(); diff --git a/src/openvic-simulation/dataloader/Dataloader.cpp b/src/openvic-simulation/dataloader/Dataloader.cpp index aefb6fc..011b524 100644 --- a/src/openvic-simulation/dataloader/Dataloader.cpp +++ b/src/openvic-simulation/dataloader/Dataloader.cpp @@ -36,8 +36,13 @@ using namespace ovdl; using StringUtils::append_string_views; -#define FILESYSTEM_CASE_INSENSITIVE (defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__))) -#define FILESYSTEM_NEEDS_FORWARD_SLASHES (!defined(_WIN32)) +#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) +#define FILESYSTEM_CASE_INSENSITIVE +#endif + +#if !defined(_WIN32) +#define FILESYSTEM_NEEDS_FORWARD_SLASHES +#endif static constexpr bool path_equals_case_insensitive(std::string_view lhs, std::string_view rhs) { constexpr auto ichar_equals = [](unsigned char l, unsigned char r) { @@ -48,7 +53,7 @@ static constexpr bool path_equals_case_insensitive(std::string_view lhs, std::st // Windows and Mac by default act like case insensitive filesystems static constexpr bool path_equals(std::string_view lhs, std::string_view rhs) { -#if FILESYSTEM_CASE_INSENSITIVE +#if defined(FILESYSTEM_CASE_INSENSITIVE) return path_equals_case_insensitive(lhs, rhs); #else return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); @@ -369,7 +374,7 @@ bool Dataloader::set_roots(path_vector_t const& new_roots) { } fs::path Dataloader::lookup_file(std::string_view path, bool print_error) const { -#if FILESYSTEM_NEEDS_FORWARD_SLASHES +#if defined(FILESYSTEM_NEEDS_FORWARD_SLASHES) /* Back-slashes need to be converted into forward-slashes */ const std::string forward_slash_path { StringUtils::make_forward_slash_path(StringUtils::remove_leading_slashes(path)) }; path = forward_slash_path; @@ -377,7 +382,7 @@ fs::path Dataloader::lookup_file(std::string_view path, bool print_error) const const fs::path filepath { path }; -#if FILESYSTEM_CASE_INSENSITIVE +#if defined(FILESYSTEM_CASE_INSENSITIVE) /* Case-insensitive filesystem */ for (fs::path const& root : roots) { const fs::path composed = root / filepath; @@ -411,34 +416,62 @@ fs::path Dataloader::lookup_file(std::string_view path, bool print_error) const return {}; } -template<typename _DirIterator, std::predicate<fs::path const&, fs::path const&> _Equiv> -Dataloader::path_vector_t Dataloader::_lookup_files_in_dir(std::string_view path, fs::path const& extension) const { -#if FILESYSTEM_NEEDS_FORWARD_SLASHES +fs::path Dataloader::lookup_image_file_or_dds(std::string_view path) const { + fs::path ret = lookup_file(path, false); + if (ret.empty()) { + // TODO - change search order so root order takes priority over extension replacement order + ret = lookup_file(append_string_views(StringUtils::remove_extension(path), ".dds"), false); + if (!ret.empty()) { + return ret; + } + Logger::error("Image lookup for ", path, " failed!"); + } + return ret; +} + +template<typename _DirIterator, typename _UniqueKey> +requires requires (_UniqueKey const& unique_key, std::string_view path) { + { unique_key(path) } -> std::convertible_to<std::string_view>; +} +Dataloader::path_vector_t Dataloader::_lookup_files_in_dir( + std::string_view path, fs::path const& extension, _UniqueKey const& unique_key +) const { +#if defined(FILESYSTEM_NEEDS_FORWARD_SLASHES) /* Back-slashes need to be converted into forward-slashes */ const std::string forward_slash_path { StringUtils::make_forward_slash_path(StringUtils::remove_leading_slashes(path)) }; path = forward_slash_path; #endif - const fs::path filepath { path }; - static constexpr _Equiv Equiv {}; + const fs::path dirpath { path }; path_vector_t ret; - size_t start_of_current_root_entries; + struct file_entry_t { + fs::path file; + fs::path const* root; + }; + string_map_t<file_entry_t> found_files; for (fs::path const& root : roots) { - start_of_current_root_entries = ret.size(); - const fs::path composed = root / filepath; + const size_t root_len = root.string().size(); + const fs::path composed = root / dirpath; std::error_code ec; for (fs::directory_entry const& entry : _DirIterator { composed, ec }) { if (entry.is_regular_file()) { - const fs::path file = entry; - if ((extension.empty() || file.extension() == extension) && !Equiv(file, {})) { - size_t index = 0; - for (; index < ret.size() && !Equiv(file, ret[index]); ++index) {} - if (index >= ret.size()) { - ret.push_back(file); - } else if (start_of_current_root_entries <= index) { - Logger::warning( - "Files in the same directory with conflicting names: ", ret[index], " (accepted) and ", file, - " (rejected)" - ); + fs::path file = entry; + if ((extension.empty() || file.extension() == extension)) { + const std::string full_path = file.string(); + std::string_view relative_path = full_path; + relative_path.remove_prefix(root_len); + relative_path = StringUtils::remove_leading_slashes(relative_path); + const std::string_view key = unique_key(relative_path); + if (!key.empty()) { + const typename decltype(found_files)::const_iterator it = found_files.find(key); + if (it == found_files.end()) { + found_files.emplace(key, file_entry_t { file, &root }); + ret.emplace_back(std::move(file)); + } else if (it->second.root == &root) { + Logger::warning( + "Files in the same directory with conflicting keys: ", it->first, " - ", it->second.file, + " (accepted) and ", key, " - ", file, " (rejected)" + ); + } } } } @@ -447,38 +480,28 @@ Dataloader::path_vector_t Dataloader::_lookup_files_in_dir(std::string_view path return ret; } -struct EquivFilename { - bool operator()(fs::path const& lhs, fs::path const& rhs) const { - return lhs.filename() == rhs.filename(); - } -}; - Dataloader::path_vector_t Dataloader::lookup_files_in_dir(std::string_view path, fs::path const& extension) const { - return _lookup_files_in_dir<fs::directory_iterator, EquivFilename>(path, extension); + return _lookup_files_in_dir<fs::directory_iterator>(path, extension, std::identity {}); } Dataloader::path_vector_t Dataloader::lookup_files_in_dir_recursive(std::string_view path, fs::path const& extension) const { - return _lookup_files_in_dir<fs::recursive_directory_iterator, EquivFilename>(path, extension); + return _lookup_files_in_dir<fs::recursive_directory_iterator>(path, extension, std::identity {}); } -struct EquivBasicIdentifierPrefix { - bool operator()(fs::path const& lhs, fs::path const& rhs) const { - const std::string lhs_str = lhs.stem().string(); - const std::string rhs_str = rhs.stem().string(); - return extract_basic_identifier_prefix(lhs_str) == extract_basic_identifier_prefix(rhs_str); - } +static std::string_view _extract_basic_identifier_prefix_from_path(std::string_view path) { + return extract_basic_identifier_prefix(StringUtils::get_filename(path)); }; Dataloader::path_vector_t Dataloader::lookup_basic_indentifier_prefixed_files_in_dir( std::string_view path, fs::path const& extension ) const { - return _lookup_files_in_dir<fs::directory_iterator, EquivBasicIdentifierPrefix>(path, extension); + return _lookup_files_in_dir<fs::directory_iterator>(path, extension, _extract_basic_identifier_prefix_from_path); } Dataloader::path_vector_t Dataloader::lookup_basic_indentifier_prefixed_files_in_dir_recursive( std::string_view path, fs::path const& extension ) const { - return _lookup_files_in_dir<fs::recursive_directory_iterator, EquivBasicIdentifierPrefix>(path, extension); + return _lookup_files_in_dir<fs::recursive_directory_iterator>(path, extension, _extract_basic_identifier_prefix_from_path); } bool Dataloader::apply_to_files(path_vector_t const& files, callback_t<fs::path const&> callback) const { @@ -555,6 +578,36 @@ csv::Windows1252Parser Dataloader::parse_csv(fs::path const& path) { return _run_ovdl_parser<csv::Windows1252Parser, &_csv_parse>(path); } +bool Dataloader::_load_interface_files(UIManager& ui_manager) const { + static constexpr std::string_view interface_directory = "interface/"; + + bool ret = apply_to_files( + lookup_files_in_dir(interface_directory, ".gfx"), + [&ui_manager](fs::path const& file) -> bool { + return ui_manager.load_gfx_file(parse_defines(file).get_file_node()); + } + ); + ui_manager.lock_sprites(); + ui_manager.lock_fonts(); + + // Hard-coded example until the mechanism for requesting them from GDScript is fleshed out + static const std::vector<std::string_view> gui_files { + "province_interface.gui", "topbar.gui" + }; + for (std::string_view const& gui_file : gui_files) { + if (!ui_manager.load_gui_file( + gui_file, + parse_defines(lookup_file(append_string_views(interface_directory, gui_file))).get_file_node() + )) { + Logger::error("Failed to load interface gui file: ", gui_file); + ret = false; + } + } + ui_manager.lock_scenes(); + + return ret; +} + bool Dataloader::_load_pop_types( PopManager& pop_manager, UnitManager const& unit_manager, GoodManager const& good_manager ) const { @@ -798,6 +851,10 @@ bool Dataloader::load_defines(GameManager& game_manager) const { bool ret = true; + if (!_load_interface_files(game_manager.get_ui_manager())) { + Logger::error("Failed to load interface files!"); + ret = false; + } if (!game_manager.get_modifier_manager().setup_modifier_effects()) { Logger::error("Failed to set up modifier effects!"); ret = false; @@ -889,7 +946,9 @@ bool Dataloader::load_defines(GameManager& game_manager) const { Logger::error("Failed to load leader traits!"); ret = false; } - if (!game_manager.get_military_manager().get_wargoal_manager().load_wargoal_file(parse_defines(lookup_file(cb_types_file)).get_file_node())) { + if (!game_manager.get_military_manager().get_wargoal_manager().load_wargoal_file( + parse_defines(lookup_file(cb_types_file)).get_file_node() + )) { Logger::error("Failed to load wargoals!"); ret = false; } diff --git a/src/openvic-simulation/dataloader/Dataloader.hpp b/src/openvic-simulation/dataloader/Dataloader.hpp index 2123469..5039582 100644 --- a/src/openvic-simulation/dataloader/Dataloader.hpp +++ b/src/openvic-simulation/dataloader/Dataloader.hpp @@ -12,6 +12,7 @@ namespace OpenVic { namespace fs = std::filesystem; struct GameManager; + class UIManager; struct PopManager; struct UnitManager; struct GoodManager; @@ -23,16 +24,22 @@ namespace OpenVic { private: path_vector_t roots; + bool _load_interface_files(UIManager& ui_manager) const; bool _load_pop_types(PopManager& pop_manager, UnitManager const& unit_manager, GoodManager const& good_manager) const; bool _load_units(UnitManager& unit_manager, GoodManager const& good_manager) const; bool _load_map_dir(GameManager& game_manager) const; bool _load_history(GameManager& game_manager, bool unused_history_file_warnings) const; - /* _DirIterator is fs::directory_iterator or fs::recursive_directory_iterator. - * _Equiv is an equivalence relation with respect to which every found file shall be unique. - * If a file is equivalent to the empty path then it is not included. */ - template<typename _DirIterator, std::predicate<fs::path const&, fs::path const&> _Equiv> - path_vector_t _lookup_files_in_dir(std::string_view path, fs::path const& extension) const; + /* _DirIterator is fs::directory_iterator or fs::recursive_directory_iterator. _UniqueKey is the type of a callable + * which converts a string_view filepath with root removed into a string_view unique key. Any path whose key is empty + * or matches an earlier found path's key is discarded, ensuring each looked up path's key is non-empty and unique. */ + template<typename _DirIterator, typename _UniqueKey> + requires requires (_UniqueKey const& unique_key, std::string_view path) { + { unique_key(path) } -> std::convertible_to<std::string_view>; + } + path_vector_t _lookup_files_in_dir( + std::string_view path, fs::path const& extension, _UniqueKey const& unique_key + ) const; public: static ovdl::v2script::Parser parse_defines(fs::path const& path); @@ -70,6 +77,8 @@ namespace OpenVic { * DAT-24 */ fs::path lookup_file(std::string_view path, bool print_error = true) const; + /* Checks alternate file endings, e.g. if "*.tga" doesn't exist then try "*.dds" */ + fs::path lookup_image_file_or_dds(std::string_view path) const; path_vector_t lookup_files_in_dir(std::string_view path, fs::path const& extension) const; path_vector_t lookup_files_in_dir_recursive(std::string_view path, fs::path const& extension) const; path_vector_t lookup_basic_indentifier_prefixed_files_in_dir(std::string_view path, fs::path const& extension) const; diff --git a/src/openvic-simulation/dataloader/NodeTools.cpp b/src/openvic-simulation/dataloader/NodeTools.cpp index 27d0f95..c412f33 100644 --- a/src/openvic-simulation/dataloader/NodeTools.cpp +++ b/src/openvic-simulation/dataloader/NodeTools.cpp @@ -76,10 +76,10 @@ node_callback_t NodeTools::expect_int_bool(callback_t<bool> callback) { return expect_identifier(expect_mapped_string(bool_map, callback)); } -node_callback_t NodeTools::expect_int64(callback_t<int64_t> callback) { - return expect_identifier([callback](std::string_view identifier) -> bool { +node_callback_t NodeTools::expect_int64(callback_t<int64_t> callback, int base) { + return expect_identifier([callback, base](std::string_view identifier) -> bool { bool successful = false; - const int64_t val = StringUtils::string_to_int64(identifier, &successful, 10); + const int64_t val = StringUtils::string_to_int64(identifier, &successful, base); if (successful) { return callback(val); } @@ -88,10 +88,10 @@ node_callback_t NodeTools::expect_int64(callback_t<int64_t> callback) { }); } -node_callback_t NodeTools::expect_uint64(callback_t<uint64_t> callback) { - return expect_identifier([callback](std::string_view identifier) -> bool { +node_callback_t NodeTools::expect_uint64(callback_t<uint64_t> callback, int base) { + return expect_identifier([callback, base](std::string_view identifier) -> bool { bool successful = false; - const uint64_t val = StringUtils::string_to_uint64(identifier, &successful, 10); + const uint64_t val = StringUtils::string_to_uint64(identifier, &successful, base); if (successful) { return callback(val); } @@ -141,6 +141,10 @@ node_callback_t NodeTools::expect_colour(callback_t<colour_t> callback) { }; } +node_callback_t NodeTools::expect_colour_hex(callback_t<colour_t> callback) { + return expect_uint(callback, 16); +} + callback_t<std::string_view> NodeTools::expect_date_str(callback_t<Date> callback) { return [callback](std::string_view identifier) -> bool { bool successful = false; @@ -188,8 +192,12 @@ NodeCallback auto _expect_vec2(Callback<vec2_t<T>> auto callback) { }; } +static node_callback_t _expect_int(callback_t<ivec2_t::type> callback) { + return expect_int(callback); +} + node_callback_t NodeTools::expect_ivec2(callback_t<ivec2_t> callback) { - return _expect_vec2<int32_t, expect_int>(callback); + return _expect_vec2<ivec2_t::type, _expect_int>(callback); } node_callback_t NodeTools::expect_fvec2(callback_t<fvec2_t> callback) { diff --git a/src/openvic-simulation/dataloader/NodeTools.hpp b/src/openvic-simulation/dataloader/NodeTools.hpp index a23fb4f..1ca6cf0 100644 --- a/src/openvic-simulation/dataloader/NodeTools.hpp +++ b/src/openvic-simulation/dataloader/NodeTools.hpp @@ -73,11 +73,11 @@ namespace OpenVic { node_callback_t expect_bool(callback_t<bool> callback); node_callback_t expect_int_bool(callback_t<bool> callback); - node_callback_t expect_int64(callback_t<int64_t> callback); - node_callback_t expect_uint64(callback_t<uint64_t> callback); + node_callback_t expect_int64(callback_t<int64_t> callback, int base = 10); + node_callback_t expect_uint64(callback_t<uint64_t> callback, int base = 10); template<std::signed_integral T> - NodeCallback auto expect_int(callback_t<T> callback) { + NodeCallback auto expect_int(callback_t<T> callback, int base = 10) { return expect_int64([callback](int64_t val) -> bool { if (static_cast<int64_t>(std::numeric_limits<T>::lowest()) <= val && val <= static_cast<int64_t>(std::numeric_limits<T>::max())) { @@ -88,11 +88,11 @@ namespace OpenVic { static_cast<int64_t>(std::numeric_limits<T>::max()), "])" ); return false; - }); + }, base); } template<std::integral T> - NodeCallback auto expect_uint(callback_t<T> callback) { + NodeCallback auto expect_uint(callback_t<T> callback, int base = 10) { return expect_uint64([callback](uint64_t val) -> bool { if (val <= static_cast<uint64_t>(std::numeric_limits<T>::max())) { return callback(val); @@ -101,12 +101,13 @@ namespace OpenVic { "Invalid uint: ", val, " (valid range: [0, ", static_cast<uint64_t>(std::numeric_limits<T>::max()), "])" ); return false; - }); + }, base); } callback_t<std::string_view> expect_fixed_point_str(callback_t<fixed_point_t> callback); node_callback_t expect_fixed_point(callback_t<fixed_point_t> callback); node_callback_t expect_colour(callback_t<colour_t> callback); + node_callback_t expect_colour_hex(callback_t<colour_t> callback); callback_t<std::string_view> expect_date_str(callback_t<Date> callback); node_callback_t expect_date(callback_t<Date> callback); diff --git a/src/openvic-simulation/history/DiplomaticHistory.cpp b/src/openvic-simulation/history/DiplomaticHistory.cpp index 6f9d73e..5cbafe7 100644 --- a/src/openvic-simulation/history/DiplomaticHistory.cpp +++ b/src/openvic-simulation/history/DiplomaticHistory.cpp @@ -164,7 +164,7 @@ bool DiplomaticHistoryManager::load_diplomacy_history_file(GameManager& game_man "start_date", ONE_EXACTLY, expect_identifier_or_string(expect_date_str(assign_variable_callback(start))), "end_date", ONE_EXACTLY, expect_identifier_or_string(expect_date_str(assign_variable_callback(end))) )(node); - + alliances.push_back({ first, second, start, end }); return ret; }, diff --git a/src/openvic-simulation/history/DiplomaticHistory.hpp b/src/openvic-simulation/history/DiplomaticHistory.hpp index 85e2654..64529c2 100644 --- a/src/openvic-simulation/history/DiplomaticHistory.hpp +++ b/src/openvic-simulation/history/DiplomaticHistory.hpp @@ -46,7 +46,7 @@ namespace OpenVic { std::optional<Date> exited; war_participant_t(Country const* new_country, Date new_joined, std::optional<Date> new_exited); - + public: Country const* get_country() const; Date get_date_joined() const; @@ -105,7 +105,7 @@ namespace OpenVic { public: Country const* get_overlord() const; Country const* get_subject() const; - const type_t get_subject_type() const; + const type_t get_subject_type() const; }; struct DiplomaticHistoryManager { diff --git a/src/openvic-simulation/interface/GFX.cpp b/src/openvic-simulation/interface/GFX.cpp new file mode 100644 index 0000000..f4e2074 --- /dev/null +++ b/src/openvic-simulation/interface/GFX.cpp @@ -0,0 +1,99 @@ +#include "GFX.hpp" + +using namespace OpenVic; +using namespace OpenVic::GFX; +using namespace OpenVic::NodeTools; + +Font::Font(std::string_view new_identifier, colour_t new_colour, std::string_view new_fontname) + : HasIdentifierAndColour { new_identifier, new_colour, false, true }, fontname { new_fontname } {} + +node_callback_t Sprite::expect_sprite(callback_t<std::unique_ptr<Sprite>&&> callback) { + return expect_dictionary_keys( + "spriteType", ZERO_OR_MORE, _expect_instance<Sprite, TextureSprite>(callback), + "progressbartype", ZERO_OR_MORE, _expect_instance<Sprite, ProgressBar>(callback), + "PieChartType", ZERO_OR_MORE, _expect_instance<Sprite, PieChart>(callback), + "LineChartType", ZERO_OR_MORE, _expect_instance<Sprite, LineChart>(callback), + "textSpriteType", ZERO_OR_MORE, _expect_instance<Sprite, TextureSprite>(callback), + "maskedShieldType", ZERO_OR_MORE, _expect_instance<Sprite, MaskedFlag>(callback), + // TODO - add the rest of the sprite types + "corneredTileSpriteType", ZERO_OR_MORE, success_callback, + "tileSpriteType", ZERO_OR_MORE, success_callback, + "BarChartType", ZERO_OR_MORE, success_callback, + "scrollingSprite", ZERO_OR_MORE, success_callback + ); +} + +TextureSprite::TextureSprite() : texture_file {}, no_of_frames { NO_FRAMES } {} + +bool TextureSprite::_fill_key_map(key_map_t& key_map) { + bool ret = Sprite::_fill_key_map(key_map); + ret &= add_key_map_entries(key_map, + "texturefile", ZERO_OR_ONE, expect_string(assign_variable_callback_string(texture_file)), + "textureFile", ZERO_OR_ONE, expect_string(assign_variable_callback_string(texture_file)), + "noOfFrames", ZERO_OR_ONE, expect_uint(assign_variable_callback(no_of_frames)), + + "norefcount", ZERO_OR_ONE, success_callback, + "effectFile", ZERO_OR_ONE, success_callback, + "allwaystransparent", ZERO_OR_ONE, success_callback, + "transparencecheck", ZERO_OR_ONE, success_callback, + "loadType", ZERO_OR_ONE, success_callback, + "clicksound", ZERO_OR_ONE, success_callback + ); + return ret; +} + +ProgressBar::ProgressBar() +: back_colour {}, back_texture_file {}, + progress_colour {}, progress_texture_file {}, + size {} {} + +bool ProgressBar::_fill_key_map(key_map_t& key_map) { + bool ret = Sprite::_fill_key_map(key_map); + ret &= add_key_map_entries(key_map, + "color", ONE_EXACTLY, expect_colour(assign_variable_callback(back_colour)), + "colortwo", ONE_EXACTLY, expect_colour(assign_variable_callback(progress_colour)), + "textureFile1", ZERO_OR_ONE, expect_string(assign_variable_callback_string(back_texture_file)), + "textureFile2", ZERO_OR_ONE, expect_string(assign_variable_callback_string(progress_texture_file)), + "size", ONE_EXACTLY, expect_ivec2(assign_variable_callback(size)), + + "effectFile", ONE_EXACTLY, success_callback, + "allwaystransparent", ZERO_OR_ONE, success_callback, + "loadType", ZERO_OR_ONE, success_callback, + "horizontal", ZERO_OR_ONE, success_callback + ); + return ret; +} + +PieChart::PieChart() : size {} {} + +bool PieChart::_fill_key_map(key_map_t& key_map) { + bool ret = Sprite::_fill_key_map(key_map); + ret &= add_key_map_entries(key_map, "size", ONE_EXACTLY, expect_uint(assign_variable_callback(size))); + return ret; +} + +LineChart::LineChart() : size {}, linewidth {} {} + +bool LineChart::_fill_key_map(key_map_t& key_map) { + bool ret = Sprite::_fill_key_map(key_map); + ret &= add_key_map_entries(key_map, + "size", ONE_EXACTLY, expect_ivec2(assign_variable_callback(size)), + "linewidth", ONE_EXACTLY, expect_uint(assign_variable_callback(linewidth)), + "allwaystransparent", ZERO_OR_ONE, success_callback + ); + return ret; +} + +MaskedFlag::MaskedFlag() : texture_file {}, mask_file {} {} + +bool MaskedFlag::_fill_key_map(key_map_t& key_map) { + bool ret = Sprite::_fill_key_map(key_map); + ret &= add_key_map_entries(key_map, + "textureFile1", ONE_EXACTLY, expect_string(assign_variable_callback_string(texture_file)), + "textureFile2", ONE_EXACTLY, expect_string(assign_variable_callback_string(mask_file)), + "effectFile", ONE_EXACTLY, success_callback, + "allwaystransparent", ZERO_OR_ONE, success_callback, + "flipv", ZERO_OR_ONE, success_callback + ); + return ret; +} diff --git a/src/openvic-simulation/interface/GFX.hpp b/src/openvic-simulation/interface/GFX.hpp new file mode 100644 index 0000000..2422e24 --- /dev/null +++ b/src/openvic-simulation/interface/GFX.hpp @@ -0,0 +1,138 @@ +#pragma once + +#include "openvic-simulation/interface/LoadBase.hpp" + +namespace OpenVic { + class UIManager; +} + +namespace OpenVic::GFX { + + struct Font : HasIdentifierAndColour { + friend class OpenVic::UIManager; + + private: + const std::string PROPERTY(fontname); + + // TODO - colorcodes, effect + + Font(std::string_view new_identifier, colour_t new_colour, std::string_view new_fontname); + + public: + Font(Font&&) = default; + }; + + using frame_t = int32_t; /* Keep this as int32_t to simplify interfacing with Godot */ + static constexpr frame_t NO_FRAMES = 0; + + class Sprite : public Named<> { + protected: + Sprite() = default; + + public: + Sprite(Sprite&&) = default; + virtual ~Sprite() = default; + + OV_DETAIL_GET_BASE_TYPE(Sprite) + OV_DETAIL_GET_TYPE + + static NodeTools::node_callback_t expect_sprite(NodeTools::callback_t<std::unique_ptr<Sprite>&&> callback); + }; + + class TextureSprite final : public Sprite { + friend std::unique_ptr<TextureSprite> std::make_unique<TextureSprite>(); + + std::string PROPERTY(texture_file); + frame_t PROPERTY(no_of_frames); + + // TODO - norefcount, effectFile, allwaystransparent + + protected: + TextureSprite(); + + bool _fill_key_map(NodeTools::key_map_t& key_map) override; + + public: + TextureSprite(TextureSprite&&) = default; + virtual ~TextureSprite() = default; + + OV_DETAIL_GET_TYPE + }; + + class ProgressBar final : public Sprite { + friend std::unique_ptr<ProgressBar> std::make_unique<ProgressBar>(); + + colour_t PROPERTY(back_colour); + std::string PROPERTY(back_texture_file); + colour_t PROPERTY(progress_colour); + std::string PROPERTY(progress_texture_file); + ivec2_t PROPERTY(size); + + // TODO - effectFile + + protected: + ProgressBar(); + + bool _fill_key_map(NodeTools::key_map_t& key_map) override; + + public: + ProgressBar(ProgressBar&&) = default; + virtual ~ProgressBar() = default; + + OV_DETAIL_GET_TYPE + }; + + class PieChart final : public Sprite { + friend std::unique_ptr<PieChart> std::make_unique<PieChart>(); + + uint32_t PROPERTY(size); + + protected: + PieChart(); + + bool _fill_key_map(NodeTools::key_map_t& key_map) override; + + public: + PieChart(PieChart&&) = default; + virtual ~PieChart() = default; + + OV_DETAIL_GET_TYPE + }; + + class LineChart final : public Sprite { + friend std::unique_ptr<LineChart> std::make_unique<LineChart>(); + + ivec2_t PROPERTY(size); + uint32_t PROPERTY(linewidth); + + protected: + LineChart(); + + bool _fill_key_map(NodeTools::key_map_t& key_map) override; + + public: + LineChart(LineChart&&) = default; + virtual ~LineChart() = default; + + OV_DETAIL_GET_TYPE + }; + + + class MaskedFlag final : public Sprite { + friend std::unique_ptr<MaskedFlag> std::make_unique<MaskedFlag>(); + + std::string PROPERTY(texture_file) + std::string PROPERTY(mask_file); + + protected: + MaskedFlag(); + + bool _fill_key_map(NodeTools::key_map_t& key_map) override; + + public: + MaskedFlag(MaskedFlag&&) = default; + virtual ~MaskedFlag() = default; + + OV_DETAIL_GET_TYPE + }; +} diff --git a/src/openvic-simulation/interface/GUI.cpp b/src/openvic-simulation/interface/GUI.cpp new file mode 100644 index 0000000..aeec136 --- /dev/null +++ b/src/openvic-simulation/interface/GUI.cpp @@ -0,0 +1,182 @@ +#include "GUI.hpp" + +#include "openvic-simulation/interface/UI.hpp" + +using namespace OpenVic; +using namespace OpenVic::GUI; +using namespace OpenVic::NodeTools; + +Element::Element() : position {}, orientation { orientation_t::UPPER_LEFT } {} + +bool Element::_fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) { + bool ret = Named::_fill_key_map(key_map, ui_manager); + using enum orientation_t; + static const string_map_t<orientation_t> orientation_map = { + { "UPPER_LEFT", UPPER_LEFT }, { "LOWER_LEFT", LOWER_LEFT }, + { "LOWER_RIGHT", LOWER_RIGHT }, { "UPPER_RIGHT", UPPER_RIGHT }, + { "CENTER", CENTER } + }; + ret &= add_key_map_entries(key_map, + "position", ONE_EXACTLY, expect_ivec2(assign_variable_callback(position)), + "orientation", ZERO_OR_ONE, expect_string(expect_mapped_string(orientation_map, assign_variable_callback(orientation))), + "Orientation", ZERO_OR_ONE, expect_string(expect_mapped_string(orientation_map, assign_variable_callback(orientation))) + ); + return ret; +} + +bool Element::_fill_elements_key_map( + NodeTools::key_map_t& key_map, callback_t<std::unique_ptr<Element>&&> callback, UIManager const& ui_manager +) { + bool ret = true; + ret &= add_key_map_entries(key_map, + "iconType", ZERO_OR_MORE, _expect_instance<Element, Icon>(callback, ui_manager), + "guiButtonType", ZERO_OR_MORE, _expect_instance<Element, Button>(callback, ui_manager), + "checkboxType", ZERO_OR_MORE, _expect_instance<Element, Checkbox>(callback, ui_manager), + "textBoxType", ZERO_OR_MORE, _expect_instance<Element, Text>(callback, ui_manager), + "instantTextBoxType", ZERO_OR_MORE, _expect_instance<Element, Text>(callback, ui_manager), + "OverlappingElementsBoxType", ZERO_OR_MORE, _expect_instance<Element, OverlappingElementsBox>(callback, ui_manager), + "listboxType", ZERO_OR_MORE, _expect_instance<Element, ListBox>(callback, ui_manager), + "windowType", ZERO_OR_MORE, _expect_instance<Element, Window>(callback, ui_manager), + "positionType", ZERO_OR_MORE, success_callback // TODO - load this as a marker for placing sub-scenes + ); + return ret; +} + +Scene::Scene() : elements { "scene elements" } {} + +bool Scene::_fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) { + return Element::_fill_elements_key_map(key_map, [this](std::unique_ptr<Element>&& element) -> bool { + return elements.add_item(std::move(element)); + }, ui_manager); +} + +node_callback_t Scene::expect_scene( + std::string_view scene_name, callback_t<std::unique_ptr<Scene>&&> callback, UIManager const& ui_manager +) { + return _expect_instance<Scene, Scene>([scene_name, callback](std::unique_ptr<Scene>&& scene) -> bool { + scene->_set_name(scene_name); + return callback(std::move(scene)); + }, ui_manager); +} + +Window::Window() : elements { "window elements" }, size {}, moveable { false }, fullscreen { false } {} + +bool Window::_fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) { + bool ret = Element::_fill_elements_key_map(key_map, [this](std::unique_ptr<Element>&& element) -> bool { + return elements.add_item(std::move(element)); + }, ui_manager); + ret &= Element::_fill_key_map(key_map, ui_manager); + ret &= add_key_map_entries(key_map, + "backGround", ZERO_OR_ONE, success_callback, // TODO - load as potential panel texture (almost always empty) + "size", ONE_EXACTLY, expect_ivec2(assign_variable_callback(size)), + "moveable", ONE_EXACTLY, expect_int_bool(assign_variable_callback(moveable)), + "dontRender", ZERO_OR_ONE, success_callback, // always empty string? + "horizontalBorder", ZERO_OR_ONE, success_callback, + "verticalBorder", ZERO_OR_ONE, success_callback, + "fullScreen", ZERO_OR_ONE, expect_bool(assign_variable_callback(fullscreen)) + ); + return ret; +} + +Icon::Icon() : sprite { nullptr }, frame { GFX::NO_FRAMES } {} + +bool Icon::_fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) { + bool ret = Element::_fill_key_map(key_map, ui_manager); + ret &= add_key_map_entries(key_map, + "spriteType", ONE_EXACTLY, expect_string(ui_manager.expect_sprite_str(assign_variable_callback_pointer(sprite))), + "frame", ZERO_OR_ONE, expect_uint(assign_variable_callback(frame)) + ); + return ret; +} + +BaseButton::BaseButton() : sprite { nullptr } {} + +bool BaseButton::_fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) { + bool ret = Element::_fill_key_map(key_map, ui_manager); + // look up sprite registry for texture sprite with name... + ret &= add_key_map_entries(key_map, + "quadTextureSprite", ONE_EXACTLY, + expect_string(ui_manager.expect_sprite_str(assign_variable_callback_pointer(sprite)), true), + "shortcut", ZERO_OR_ONE, success_callback // TODO - load and use shortcuts (how to integrate with custom keybinds?) + ); + return ret; +} + +Button::Button() : text {}, font { nullptr} {} + +bool Button::_fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) { + bool ret = BaseButton::_fill_key_map(key_map, ui_manager); + ret &= add_key_map_entries(key_map, + "buttonText", ZERO_OR_ONE, expect_string(assign_variable_callback_string(text), true), + "buttonFont", ZERO_OR_ONE, expect_string(ui_manager.expect_font_str(assign_variable_callback_pointer(font))), + "clicksound", ZERO_OR_ONE, success_callback, + /* These are always empty in the base defines */ + "tooltip", ZERO_OR_ONE, success_callback, + "tooltipText", ZERO_OR_ONE, success_callback, + "delayedTooltipText", ZERO_OR_ONE, success_callback + ); + return ret; +} + +bool Checkbox::_fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) { + bool ret = BaseButton::_fill_key_map(key_map, ui_manager); + return ret; +} + +AlignedElement::AlignedElement() : format { format_t::left } {} + +bool AlignedElement::_fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) { + bool ret = Element::_fill_key_map(key_map, ui_manager); + using enum format_t; + static const string_map_t<format_t> format_map = { + { "left", left }, { "right", right }, { "centre", centre }, { "center", centre } + }; + ret &= add_key_map_entries(key_map, + "format", ZERO_OR_ONE, expect_identifier(expect_mapped_string(format_map, assign_variable_callback(format)) + )); + return ret; +} + +Text::Text() : text {}, font { nullptr } {} + +bool Text::_fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) { + bool ret = AlignedElement::_fill_key_map(key_map, ui_manager); + ret &= add_key_map_entries(key_map, + "text", ZERO_OR_ONE, expect_string(assign_variable_callback_string(text), true), + "font", ONE_EXACTLY, expect_string(ui_manager.expect_font_str(assign_variable_callback_pointer(font))), + "maxWidth", ONE_EXACTLY, expect_uint(assign_variable_callback(max_size.x)), + "maxHeight", ONE_EXACTLY, expect_uint(assign_variable_callback(max_size.y)), + + "borderSize", ZERO_OR_ONE, success_callback, + "fixedsize", ZERO_OR_ONE, success_callback, + + // Add warning about redundant key? + "textureFile", ZERO_OR_ONE, success_callback + ); + return ret; +} + +OverlappingElementsBox::OverlappingElementsBox() : size {} {} + +bool OverlappingElementsBox::_fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) { + bool ret = AlignedElement::_fill_key_map(key_map, ui_manager); + ret &= add_key_map_entries(key_map, + "size", ONE_EXACTLY, expect_ivec2(assign_variable_callback(size)), + "spacing", ONE_EXACTLY, success_callback + ); + return ret; +} + +ListBox::ListBox() : size {} {} + +bool ListBox::_fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) { + bool ret = Element::_fill_key_map(key_map, ui_manager); + ret &= add_key_map_entries(key_map, + "backGround", ZERO_OR_ONE, success_callback, + "size", ONE_EXACTLY, expect_ivec2(assign_variable_callback(size)), + "spacing", ZERO_OR_ONE, success_callback, + "scrollbartype", ZERO_OR_ONE, success_callback, // TODO - implement modable listbox scrollbars + "borderSize", ZERO_OR_ONE, success_callback + ); + return ret; +} diff --git a/src/openvic-simulation/interface/GUI.hpp b/src/openvic-simulation/interface/GUI.hpp new file mode 100644 index 0000000..1a76ca0 --- /dev/null +++ b/src/openvic-simulation/interface/GUI.hpp @@ -0,0 +1,235 @@ +#pragma once + +#include "openvic-simulation/interface/GFX.hpp" + +namespace OpenVic { + class UIManager; +} +namespace OpenVic::GUI { + class Scene; + + class Element : public Named<UIManager const&> { + friend class Scene; + + public: + enum class orientation_t { + UPPER_LEFT, LOWER_LEFT, LOWER_RIGHT, UPPER_RIGHT, CENTER + }; + + private: + ivec2_t PROPERTY(position); + orientation_t PROPERTY(orientation); + + protected: + Element(); + + bool _fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) override; + static bool _fill_elements_key_map( + NodeTools::key_map_t& key_map, NodeTools::callback_t<std::unique_ptr<Element>&&> callback, + UIManager const& ui_manager + ); + + public: + Element(Element&&) = default; + virtual ~Element() = default; + + OV_DETAIL_GET_BASE_TYPE(Element) + OV_DETAIL_GET_TYPE + }; + + class Scene : public Named<UIManager const&> { + friend std::unique_ptr<Scene> std::make_unique<Scene>(); + + NamedInstanceRegistry<Element, UIManager const&> elements; + + protected: + Scene(); + + bool _fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) override; + + public: + Scene(Scene&&) = default; + virtual ~Scene() = default; + + OV_DETAIL_GET_TYPE + + static NodeTools::node_callback_t expect_scene( + std::string_view scene_name, NodeTools::callback_t<std::unique_ptr<Scene>&&> callback, UIManager const& ui_manager + ); + + IDENTIFIER_REGISTRY_ACCESSORS(element) + }; + + class Window final : public Element { + friend std::unique_ptr<Window> std::make_unique<Window>(); + + NamedInstanceRegistry<Element, UIManager const&> elements; + + ivec2_t PROPERTY(size); + bool PROPERTY(moveable); + bool PROPERTY(fullscreen); + // TODO - background, dontRender, horizontalBorder, verticalBorder + + protected: + Window(); + + bool _fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) override; + + public: + Window(Window&&) = default; + virtual ~Window() = default; + + OV_DETAIL_GET_TYPE + + IDENTIFIER_REGISTRY_ACCESSORS(element) + }; + + class Icon final : public Element { + friend std::unique_ptr<Icon> std::make_unique<Icon>(); + + GFX::Sprite const* PROPERTY(sprite); + GFX::frame_t PROPERTY(frame); + + protected: + Icon(); + + bool _fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) override; + + public: + Icon(Icon&&) = default; + virtual ~Icon() = default; + + OV_DETAIL_GET_TYPE + }; + + class BaseButton : public Element { + GFX::Sprite const* PROPERTY(sprite); + // TODO - shortcut + + protected: + BaseButton(); + + bool _fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) override; + + public: + BaseButton(BaseButton&&) = default; + virtual ~BaseButton() = default; + + OV_DETAIL_GET_TYPE + }; + + class Button final : public BaseButton { + friend std::unique_ptr<Button> std::make_unique<Button>(); + + std::string PROPERTY(text); + GFX::Font const* PROPERTY(font); + + // TODO - clicksound + + protected: + Button() ; + + bool _fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) override; + + public: + Button(Button&&) = default; + virtual ~Button() = default; + + OV_DETAIL_GET_TYPE + }; + + class Checkbox final : public BaseButton { + friend std::unique_ptr<Checkbox> std::make_unique<Checkbox>(); + + protected: + Checkbox() = default; + + bool _fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) override; + + public: + Checkbox(Checkbox&&) = default; + virtual ~Checkbox() = default; + + OV_DETAIL_GET_TYPE + }; + + class AlignedElement : public Element { + public: + enum class format_t { + left, centre, right + }; + + private: + format_t PROPERTY(format); + + protected: + AlignedElement(); + + bool _fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) override; + + public: + AlignedElement(AlignedElement&&) = default; + virtual ~AlignedElement() = default; + + OV_DETAIL_GET_TYPE + }; + + class Text final : public AlignedElement { + friend std::unique_ptr<Text> std::make_unique<Text>(); + + std::string PROPERTY(text); + GFX::Font const* PROPERTY(font); + ivec2_t PROPERTY(max_size); // maxWidth, maxHeight + + // TODO - borderSize, fixedsize, textureFile + + protected: + Text(); + + bool _fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) override; + + public: + Text(Text&&) = default; + virtual ~Text() = default; + + OV_DETAIL_GET_TYPE + }; + + class OverlappingElementsBox final : public AlignedElement { + friend std::unique_ptr<OverlappingElementsBox> std::make_unique<OverlappingElementsBox>(); + + ivec2_t PROPERTY(size); + + // TODO - spacing + + protected: + OverlappingElementsBox(); + + bool _fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) override; + + public: + OverlappingElementsBox(OverlappingElementsBox&&) = default; + virtual ~OverlappingElementsBox() = default; + + OV_DETAIL_GET_TYPE + }; + + class ListBox final : public Element { + friend std::unique_ptr<ListBox> std::make_unique<ListBox>(); + + ivec2_t PROPERTY(size); + + // TODO - backGround, spacing, scrollbartype, borderSize + + protected: + ListBox(); + + bool _fill_key_map(NodeTools::key_map_t& key_map, UIManager const& ui_manager) override; + + public: + ListBox(ListBox&&) = default; + virtual ~ListBox() = default; + + OV_DETAIL_GET_TYPE + }; +} diff --git a/src/openvic-simulation/interface/LoadBase.hpp b/src/openvic-simulation/interface/LoadBase.hpp new file mode 100644 index 0000000..3ee7c83 --- /dev/null +++ b/src/openvic-simulation/interface/LoadBase.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include "openvic-simulation/types/IdentifierRegistry.hpp" +#include "openvic-simulation/utility/Getters.hpp" + +namespace OpenVic { + + template<typename... _Context> + class LoadBase { + protected: + LoadBase() = default; + + virtual bool _fill_key_map(NodeTools::key_map_t&, _Context...) = 0; + + public: + LoadBase(LoadBase&&) = default; + virtual ~LoadBase() = default; + + bool load(ast::NodeCPtr node, _Context... context) { + NodeTools::key_map_t key_map; + bool ret = _fill_key_map(key_map, context...); + ret &= NodeTools::expect_dictionary_key_map(std::move(key_map))(node); + return ret; + } + + template<std::derived_from<LoadBase<_Context...>> T, std::derived_from<T> U> + static NodeTools::node_callback_t _expect_instance( + NodeTools::callback_t<std::unique_ptr<T>&&> callback, _Context... context + ) { + return [callback, &context...](ast::NodeCPtr node) -> bool { + std::unique_ptr<T> instance { std::make_unique<U>() }; + bool ret = instance->load(node, context...); + ret &= callback(std::move(instance)); + return ret; + }; + } + + OV_DETAIL_GET_TYPE_BASE_CLASS(LoadBase) + }; + + template<typename... _Context> + class Named : public LoadBase<_Context...> { + std::string PROPERTY(name); + + protected: + Named() = default; + + virtual bool _fill_key_map(NodeTools::key_map_t& key_map, _Context...) override { + using namespace OpenVic::NodeTools; + return add_key_map_entries(key_map, "name", ONE_EXACTLY, expect_string(assign_variable_callback_string(name))); + } + + void _set_name(std::string_view new_name) { + if (!name.empty()) { + Logger::warning("Overriding scene name ", name, " with ", new_name); + } + name = new_name; + } + + public: + Named(Named&&) = default; + virtual ~Named() = default; + + OV_DETAIL_GET_TYPE + }; + + template<typename T, typename... _Context> + requires std::derived_from<T, Named<_Context...>> + struct _get_name { + constexpr std::string_view operator()(T const* item) const { + return item->get_name(); + } + }; + + template<typename T, typename... _Context> + requires std::derived_from<T, Named<_Context...>> + using NamedRegistry = ValueRegistry<T, _get_name<T, _Context...>>; + + template<typename T, typename... _Context> + requires std::derived_from<T, Named<_Context...>> + using NamedInstanceRegistry = InstanceRegistry<T, _get_name<T, _Context...>>; +} diff --git a/src/openvic-simulation/interface/UI.cpp b/src/openvic-simulation/interface/UI.cpp new file mode 100644 index 0000000..4653e5b --- /dev/null +++ b/src/openvic-simulation/interface/UI.cpp @@ -0,0 +1,92 @@ +#include "UI.hpp" + +using namespace OpenVic; +using namespace OpenVic::NodeTools; +using namespace OpenVic::GFX; +using namespace OpenVic::GUI; + +UIManager::UIManager() : sprites { "sprites" }, scenes { "scenes" }, fonts { "fonts" } {} + +bool UIManager::add_font(std::string_view identifier, colour_t colour, std::string_view fontname) { + if (identifier.empty()) { + Logger::error("Invalid font identifier - empty!"); + return false; + } + if (fontname.empty()) { + Logger::error("Invalid culture colour for ", identifier, ": ", colour_to_hex_string(colour)); + return false; + } + return fonts.add_item({ identifier, colour, fontname }, duplicate_warning_callback); +} + +bool UIManager::_load_font(ast::NodeCPtr node) { + std::string_view identifier, fontname; + colour_t colour = NULL_COLOUR; + bool ret = expect_dictionary_keys( + "name", ONE_EXACTLY, expect_string(assign_variable_callback(identifier)), + "fontname", ONE_EXACTLY, expect_string(assign_variable_callback(fontname)), + "color", ONE_EXACTLY, expect_colour_hex(assign_variable_callback(colour)), + "colorcodes", ZERO_OR_ONE, success_callback, + "effect", ZERO_OR_ONE, success_callback + )(node); + ret &= add_font(identifier, colour, fontname); + return ret; +} + +bool UIManager::load_gfx_file(ast::NodeCPtr root) { + return expect_dictionary_keys( + "spriteTypes", ZERO_OR_ONE, Sprite::expect_sprite( + [this](std::unique_ptr<Sprite>&& sprite) -> bool { + /* TODO - more checks on duplicates (the false here reduces them from + * errors to warnings). The repeats in vanilla are identical (simple + * texture sprites with the same name referring to the same asset), + * maybe check if duplicates are equal? (this would require some kind + * of duplicate handling callback for `add_item`) + * + * It's also possible that some of the vanilla duplicates are intentional, + * after all I'm assuming all GFX files are loaded into a global registry + * rather than each GUI file using only the contnts of a few specific, + * maybe even hardcoded GFX files. This can't, however, explain all of the + * vanilla duplicates, as GFX_frontend_tab_button is repeated in the same + * file, albeit in a section marked "OLD STUFF BELOW..". + * + * Vanilla Duplicates: + * - GFX_bp_open_manager and GFX_bp_toggle_visible + * - simple texture sprites appearing identically in menubar.gfx and battleplansinterface.gfx + * - GFX_frontend_tab_button + * - texture sprite appearing twice identically in the same file (temp_frontend.gfx), + * both below a comment saying "OLD STUFF BELOW.." + */ + return sprites.add_item(std::move(sprite), duplicate_warning_callback); + } + ), + "bitmapfonts", ZERO_OR_ONE, expect_dictionary( + [this](std::string_view key, ast::NodeCPtr node) -> bool { + if (key != "bitmapfont") { + Logger::error("Invalid bitmapfonts key: ", key); + return false; + } + return _load_font(node); + } + ), + "objectTypes", ZERO_OR_ONE, success_callback, + "lightTypes", ZERO_OR_ONE, success_callback, + "fonts", ZERO_OR_ONE, success_callback + )(root); +} + +bool UIManager::load_gui_file(std::string_view scene_name, ast::NodeCPtr root) { + if (!sprites_are_locked()) { + Logger::error("Cannot load GUI files until GFX files (i.e. Sprites) are locked!"); + return false; + } + return expect_dictionary_keys( + "guiTypes", ZERO_OR_ONE, Scene::expect_scene( + scene_name, + [this](std::unique_ptr<Scene>&& scene) -> bool { + return scenes.add_item(std::move(scene)); + }, + *this + ) + )(root); +} diff --git a/src/openvic-simulation/interface/UI.hpp b/src/openvic-simulation/interface/UI.hpp new file mode 100644 index 0000000..045766b --- /dev/null +++ b/src/openvic-simulation/interface/UI.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "openvic-simulation/interface/GUI.hpp" + +namespace OpenVic { + + class UIManager { + + NamedInstanceRegistry<GFX::Sprite> sprites; + NamedInstanceRegistry<GUI::Scene, UIManager const&> scenes; + IdentifierRegistry<GFX::Font> fonts; + + bool _load_font(ast::NodeCPtr node); + + public: + UIManager(); + + IDENTIFIER_REGISTRY_ACCESSORS(sprite) + IDENTIFIER_REGISTRY_ACCESSORS(scene) + IDENTIFIER_REGISTRY_ACCESSORS(font) + + bool add_font(std::string_view identifier, colour_t colour, std::string_view fontname); + + bool load_gfx_file(ast::NodeCPtr root); + bool load_gui_file(std::string_view scene_name, ast::NodeCPtr root); + }; +} diff --git a/src/openvic-simulation/map/TerrainType.cpp b/src/openvic-simulation/map/TerrainType.cpp index 796089e..f138cb2 100644 --- a/src/openvic-simulation/map/TerrainType.cpp +++ b/src/openvic-simulation/map/TerrainType.cpp @@ -43,7 +43,9 @@ bool TerrainTypeMapping::get_has_texture() const { TerrainTypeManager::TerrainTypeManager() : terrain_types { "terrain types" }, terrain_type_mappings { "terrain type mappings" } {} -bool TerrainTypeManager::add_terrain_type(std::string_view identifier, colour_t colour, ModifierValue&& values, bool is_water) { +bool TerrainTypeManager::add_terrain_type( + std::string_view identifier, colour_t colour, ModifierValue&& values, bool is_water +) { if (identifier.empty()) { Logger::error("Invalid terrain type identifier - empty!"); return false; diff --git a/src/openvic-simulation/military/Wargoal.cpp b/src/openvic-simulation/military/Wargoal.cpp index 8bc4446..b493f22 100644 --- a/src/openvic-simulation/military/Wargoal.cpp +++ b/src/openvic-simulation/military/Wargoal.cpp @@ -33,54 +33,6 @@ WargoalType::WargoalType( modifiers { std::move(new_modifiers) }, peace_options { new_peace_options } {} -std::string_view WargoalType::get_sprite() const { - return sprite; -} - -std::string_view WargoalType::get_war_name() const { - return war_name; -} - -const Timespan WargoalType::get_available_length() const { - return available_length; -} - -const Timespan WargoalType::get_truce_length() const { - return truce_length; -} - -const bool WargoalType::is_triggered_only() const { - return triggered_only; -} - -const bool WargoalType::is_civil_war() const { - return civil_war; -} - -const bool WargoalType::is_constructing() const { - return constructing; -} - -const bool WargoalType::is_crisis() const { - return crisis; -} - -const bool WargoalType::is_great_war() const { - return great_war; -} - -const bool WargoalType::is_mutual() const { - return mutual; -} - -WargoalType::peace_modifiers_t const& WargoalType::get_modifiers() const { - return modifiers; -} - -const peace_options_t WargoalType::get_peace_options() const { - return peace_options; -} - WargoalTypeManager::WargoalTypeManager() : wargoal_types { "wargoal types" } {} const std::vector<WargoalType const*>& WargoalTypeManager::get_peace_priority_list() const { @@ -106,7 +58,7 @@ bool WargoalTypeManager::add_wargoal_type( Logger::error("Invalid wargoal identifier - empty!"); return false; } - + if (sprite.empty()) { Logger::error("Invalid sprite for wargoal ", identifier, " - empty!"); return false; @@ -117,72 +69,47 @@ bool WargoalTypeManager::add_wargoal_type( return false; } - return wargoal_types.add_item({ identifier, sprite, war_name, available_length, truce_length, triggered_only, civil_war, constructing, crisis, great_war, mutual, std::move(modifiers), peace_options }); + return wargoal_types.add_item({ + identifier, sprite, war_name, available_length, truce_length, triggered_only, civil_war, constructing, crisis, + great_war, mutual, std::move(modifiers), peace_options + }); } bool WargoalTypeManager::load_wargoal_file(ast::NodeCPtr root) { bool ret = expect_dictionary( [this](std::string_view identifier, ast::NodeCPtr value) -> bool { if (identifier == "peace_order") return true; - + std::string_view sprite, war_name; Timespan available, truce; - bool triggered_only = false, civil_war = false, constructing = false, crisis = false, great_war = false, mutual = false; - peace_options_t peace_options; + bool triggered_only = false, civil_war = false, constructing = true, crisis = true, great_war = false, + mutual = false; + peace_options_t peace_options {}; WargoalType::peace_modifiers_t modifiers; bool ret = expect_dictionary_keys_and_default( [&modifiers, &identifier](std::string_view key, ast::NodeCPtr value) -> bool { - fixed_point_t modifier; - expect_fixed_point(assign_variable_callback(modifier))(value); - - if (key == "badboy_factor") { - modifiers[WargoalType::PEACE_MODIFIERS::BADBOY_FACTOR] += modifier; - return true; - } - if (key == "prestige_factor") { - modifiers[WargoalType::PEACE_MODIFIERS::PRESTIGE_FACTOR] += modifier; - return true; - } - if (key == "peace_cost_factor") { - modifiers[WargoalType::PEACE_MODIFIERS::PEACE_COST_FACTOR] += modifier; - return true; - } - if (key == "penalty_factor") { - modifiers[WargoalType::PEACE_MODIFIERS::PENALTY_FACTOR] += modifier; - return true; - } - if (key == "break_truce_prestige_factor") { - modifiers[WargoalType::PEACE_MODIFIERS::BREAK_TRUCE_PRESTIGE_FACTOR] += modifier; - return true; - } - if (key == "break_truce_infamy_factor") { - modifiers[WargoalType::PEACE_MODIFIERS::BREAK_TRUCE_INFAMY_FACTOR] += modifier; - return true; - } - if (key == "break_truce_militancy_factor") { - modifiers[WargoalType::PEACE_MODIFIERS::BREAK_TRUCE_MILITANCY_FACTOR] += modifier; - return true; - } - if (key == "good_relation_prestige_factor") { - modifiers[WargoalType::PEACE_MODIFIERS::GOOD_RELATION_PRESTIGE_FACTOR] += modifier; - return true; - } - if (key == "good_relation_infamy_factor") { - modifiers[WargoalType::PEACE_MODIFIERS::GOOD_RELATION_INFAMY_FACTOR] += modifier; - return true; - } - if (key == "good_relation_militancy_factor") { - modifiers[WargoalType::PEACE_MODIFIERS::GOOD_RELATION_MILITANCY_FACTOR] += modifier; - return true; - } - if (key == "tws_battle_factor") { - modifiers[WargoalType::PEACE_MODIFIERS::WAR_SCORE_BATTLE_FACTOR] += modifier; - return true; - } - if (key == "construction_speed") { - modifiers[WargoalType::PEACE_MODIFIERS::CONSTRUCTION_SPEED] += modifier; - return true; + using enum WargoalType::PEACE_MODIFIERS; + static const string_map_t<WargoalType::PEACE_MODIFIERS> peace_modifier_map { + { "badboy_factor", BADBOY_FACTOR }, + { "prestige_factor", PRESTIGE_FACTOR }, + { "peace_cost_factor", PEACE_COST_FACTOR }, + { "penalty_factor", PENALTY_FACTOR }, + { "break_truce_prestige_factor", BREAK_TRUCE_PRESTIGE_FACTOR }, + { "break_truce_infamy_factor", BREAK_TRUCE_INFAMY_FACTOR }, + { "break_truce_militancy_factor", BREAK_TRUCE_MILITANCY_FACTOR }, + { "good_relation_prestige_factor", GOOD_RELATION_PRESTIGE_FACTOR }, + { "good_relation_infamy_factor", GOOD_RELATION_INFAMY_FACTOR }, + { "good_relation_militancy_factor", GOOD_RELATION_MILITANCY_FACTOR }, + { "tws_battle_factor", WAR_SCORE_BATTLE_FACTOR }, + { "construction_speed", CONSTRUCTION_SPEED } + }; + const decltype(peace_modifier_map)::const_iterator it = peace_modifier_map.find(key); + if (it != peace_modifier_map.end()) { + return expect_fixed_point([&modifiers, peace_modifier = it->second](fixed_point_t val) -> bool { + modifiers[peace_modifier] = val; + return true; + })(value); } Logger::error("Modifier ", key, " in wargoal ", identifier, " is invalid."); @@ -284,24 +211,30 @@ bool WargoalTypeManager::load_wargoal_file(ast::NodeCPtr root) { "always", ZERO_OR_ONE, success_callback // usage unknown / quirk )(value); - add_wargoal_type(identifier, sprite, war_name, available, truce, triggered_only, civil_war, constructing, crisis, great_war, mutual, std::move(modifiers), peace_options); + add_wargoal_type( + identifier, sprite, war_name, available, truce, triggered_only, civil_war, constructing, crisis, great_war, + mutual, std::move(modifiers), peace_options + ); return ret; } )(root); /* load order in which CBs are prioritised by AI */ - ret &= expect_dictionary_keys_and_default( - key_value_success_callback, - "peace_order", ONE_EXACTLY, expect_list([this](ast::NodeCPtr value) -> bool { - WargoalType const* wargoal; - bool ret = expect_wargoal_type_identifier(assign_variable_callback_pointer(wargoal))(value); - if (ret) { - peace_priorities.push_back(wargoal); - } else { - Logger::warning("Attempted to add invalid wargoal type to AI peace order!"); - } - return true; - }) + ret &= expect_key( + "peace_order", + expect_list( + expect_wargoal_type_identifier( + [this](WargoalType const& wargoal) -> bool { + if (std::find(peace_priorities.begin(), peace_priorities.end(), &wargoal) == peace_priorities.end()) { + peace_priorities.push_back(&wargoal); + } else { + Logger::warning("Wargoal ", wargoal.get_identifier(), " is already in the peace priority list!"); + } + return true; + }, + true // warn instead of error + ) + ) )(root); lock_wargoal_types(); diff --git a/src/openvic-simulation/military/Wargoal.hpp b/src/openvic-simulation/military/Wargoal.hpp index dbb8d67..3700347 100644 --- a/src/openvic-simulation/military/Wargoal.hpp +++ b/src/openvic-simulation/military/Wargoal.hpp @@ -1,8 +1,9 @@ #pragma once -#include "openvic-simulation/types/IdentifierRegistry.hpp" -#include "openvic-simulation/types/EnumBitfield.hpp" #include "openvic-simulation/Modifier.hpp" +#include "openvic-simulation/types/EnumBitfield.hpp" +#include "openvic-simulation/types/IdentifierRegistry.hpp" +#include "openvic-simulation/utility/Getters.hpp" namespace OpenVic { struct WargoalTypeManager; @@ -46,22 +47,22 @@ namespace OpenVic { WAR_SCORE_BATTLE_FACTOR, CONSTRUCTION_SPEED }; - using peace_modifiers_t = std::map<PEACE_MODIFIERS, fixed_point_t>; - + using peace_modifiers_t = decimal_map_t<PEACE_MODIFIERS>; + private: - const std::string sprite; - const std::string war_name; - const Timespan available_length; - const Timespan truce_length; - const bool triggered_only; // only able to be added via effects (or within the code) - const bool civil_war; - const bool constructing; // can be added to existing wars or justified - const bool crisis; // able to be added to crises - const bool great_war; // automatically add to great wars - const bool mutual; // attacked and defender share wargoal - const peace_modifiers_t modifiers; - const peace_options_t peace_options; - + const std::string PROPERTY(sprite); + const std::string PROPERTY(war_name); + const Timespan PROPERTY(available_length); + const Timespan PROPERTY(truce_length); + const bool PROPERTY(triggered_only); // only able to be added via effects (or within the code) + const bool PROPERTY(civil_war); + const bool PROPERTY(constructing); // can be added to existing wars or justified + const bool PROPERTY(crisis); // able to be added to crises + const bool PROPERTY(great_war); // automatically add to great wars + const bool PROPERTY(mutual); // attacked and defender share wargoal + const peace_modifiers_t PROPERTY(modifiers); + const peace_options_t PROPERTY(peace_options); + // TODO: can_use, prerequisites, on_add, on_po_accepted WargoalType( @@ -82,25 +83,12 @@ namespace OpenVic { public: WargoalType(WargoalType&&) = default; - - std::string_view get_sprite() const; - std::string_view get_war_name() const; - const Timespan get_available_length() const; - const Timespan get_truce_length() const; - const bool is_triggered_only() const; - const bool is_civil_war() const; - const bool is_constructing() const; - const bool is_crisis() const; - const bool is_great_war() const; - const bool is_mutual() const; - peace_modifiers_t const& get_modifiers() const; - const peace_options_t get_peace_options() const; }; struct WargoalTypeManager { private: IdentifierRegistry<WargoalType> wargoal_types; - std::vector<WargoalType const*> peace_priorities; + std::vector<WargoalType const*> PROPERTY(peace_priorities); public: WargoalTypeManager(); diff --git a/src/openvic-simulation/types/IdentifierRegistry.hpp b/src/openvic-simulation/types/IdentifierRegistry.hpp index 1121956..662815e 100644 --- a/src/openvic-simulation/types/IdentifierRegistry.hpp +++ b/src/openvic-simulation/types/IdentifierRegistry.hpp @@ -214,24 +214,31 @@ namespace OpenVic { value_type CONST* get_item_by_index(size_t index) CONST { \ return index < items.size() ? &items[index] : nullptr; \ } \ - NodeTools::callback_t<std::string_view> expect_item_str(NodeTools::callback_t<value_type CONST&> callback) CONST { \ - return [this, callback](std::string_view identifier) -> bool { \ + NodeTools::callback_t<std::string_view> expect_item_str( \ + NodeTools::callback_t<value_type CONST&> callback, bool warn \ + ) CONST { \ + return [this, callback, warn](std::string_view identifier) -> bool { \ value_type CONST* item = get_item_by_identifier(identifier); \ if (item != nullptr) { \ return callback(*item); \ } \ - Logger::error("Invalid ", name, ": ", identifier); \ - return false; \ + if (!warn) { \ + Logger::error("Invalid ", name, ": ", identifier); \ + return false; \ + } else { \ + Logger::warning("Invalid ", name, ": ", identifier); \ + return true; \ + } \ }; \ } \ - NodeTools::node_callback_t expect_item_identifier(NodeTools::callback_t<value_type CONST&> callback) CONST { \ - return NodeTools::expect_identifier(expect_item_str(callback)); \ + NodeTools::node_callback_t expect_item_identifier(NodeTools::callback_t<value_type CONST&> callback, bool warn) CONST { \ + return NodeTools::expect_identifier(expect_item_str(callback, warn)); \ } \ NodeTools::node_callback_t expect_item_dictionary( \ NodeTools::callback_t<value_type CONST&, ast::NodeCPtr> callback \ ) CONST { \ return NodeTools::expect_dictionary([this, callback](std::string_view key, ast::NodeCPtr value) -> bool { \ - return expect_item_str(std::bind(callback, std::placeholders::_1, value))(key); \ + return expect_item_str(std::bind(callback, std::placeholders::_1, value), false)(key); \ }); \ } @@ -348,14 +355,14 @@ namespace OpenVic { return plural.get_item_identifiers(); \ } \ NodeTools::callback_t<std::string_view> expect_##singular##_str( \ - NodeTools::callback_t<decltype(plural)::value_type const&> callback \ + NodeTools::callback_t<decltype(plural)::value_type const&> callback, bool warn = false \ ) const { \ - return plural.expect_item_str(callback); \ + return plural.expect_item_str(callback, warn); \ } \ NodeTools::node_callback_t expect_##singular##_identifier( \ - NodeTools::callback_t<decltype(plural)::value_type const&> callback \ + NodeTools::callback_t<decltype(plural)::value_type const&> callback, bool warn = false \ ) const { \ - return plural.expect_item_identifier(callback); \ + return plural.expect_item_identifier(callback, warn); \ } \ NodeTools::node_callback_t expect_##singular##_dictionary( \ NodeTools::callback_t<decltype(plural)::value_type const&, ast::NodeCPtr> callback \ @@ -373,14 +380,14 @@ namespace OpenVic { return plural.get_item_by_identifier(identifier); \ } \ NodeTools::callback_t<std::string_view> expect_##singular##_str( \ - NodeTools::callback_t<decltype(plural)::value_type&> callback \ + NodeTools::callback_t<decltype(plural)::value_type&> callback, bool warn = false \ ) { \ - return plural.expect_item_str(callback); \ + return plural.expect_item_str(callback, warn); \ } \ NodeTools::node_callback_t expect_##singular##_identifier( \ - NodeTools::callback_t<decltype(plural)::value_type&> callback \ + NodeTools::callback_t<decltype(plural)::value_type&> callback, bool warn = false \ ) { \ - return plural.expect_item_identifier(callback); \ + return plural.expect_item_identifier(callback, warn); \ } \ NodeTools::node_callback_t expect_##singular##_dictionary( \ NodeTools::callback_t<decltype(plural)::value_type&, ast::NodeCPtr> callback \ diff --git a/src/openvic-simulation/utility/Getters.hpp b/src/openvic-simulation/utility/Getters.hpp index e34a970..53763ea 100644 --- a/src/openvic-simulation/utility/Getters.hpp +++ b/src/openvic-simulation/utility/Getters.hpp @@ -4,6 +4,38 @@ #include <string> #include <string_view> +#include <openvic-dataloader/detail/SelfType.hpp> +#include <openvic-dataloader/detail/TypeName.hpp> + +#define OV_DETAIL_GET_TYPE_BASE_CLASS(CLASS) \ + static constexpr std::string_view get_type_static() { return ::ovdl::detail::type_name<CLASS>(); } \ + constexpr virtual std::string_view get_type() const = 0; \ + static constexpr std::string_view get_base_type_static() { return ::ovdl::detail::type_name<CLASS>(); } \ + constexpr virtual std::string_view get_base_type() const { return get_base_type_static(); } \ + template<typename T> constexpr bool is_type() const { \ + return get_type().compare(::ovdl::detail::type_name<T>()) == 0; } \ + template<typename T> constexpr bool is_derived_from() const { \ + return is_type<T>() || get_base_type().compare(::ovdl::detail::type_name<T>()) == 0; } \ + template<typename T> constexpr T* cast_to() { \ + if (is_derived_from<T>() || is_type<CLASS>()) return (static_cast<T*>(this)); \ + return nullptr; } \ + template<typename T> constexpr const T* const cast_to() const { \ + if (is_derived_from<T>() || is_type<CLASS>()) return (static_cast<const T*>(this)); \ + return nullptr; } + +#define OV_DETAIL_GET_TYPE \ + struct _self_type_tag {}; \ + constexpr auto _self_type_helper()->decltype(::ovdl::detail::Writer<_self_type_tag, decltype(this)> {}); \ + using type = ::ovdl::detail::Read<_self_type_tag>; \ + static constexpr std::string_view get_type_static() { return ::ovdl::detail::type_name<type>(); } \ + constexpr std::string_view get_type() const override { \ + return ::ovdl::detail::type_name<std::decay_t<decltype(*this)>>(); } + +#define OV_DETAIL_GET_BASE_TYPE(CLASS) \ + static constexpr std::string_view get_base_type_static() { return ::ovdl::detail::type_name<CLASS>(); } \ + constexpr std::string_view get_base_type() const override { \ + return ::ovdl::detail::type_name<std::decay_t<decltype(*this)>>(); } + #define REF_GETTERS(var) \ constexpr decltype(var)& get_##var() { \ return var; \ |