diff options
19 files changed, 191 insertions, 123 deletions
diff --git a/docs/contribution/system-requirements.md b/docs/contribution/system-requirements.md index 9fd7719..9e1f16b 100644 --- a/docs/contribution/system-requirements.md +++ b/docs/contribution/system-requirements.md @@ -27,6 +27,7 @@ If you're installing Visual Studio 2015, ensure you choose custom and pick C++ a ### Linux Requirements * GCC 12.3+ + * For Ubuntu 22.04, you need to manually install g++-12 and set that version as active #### Debian/Ubuntu Linux Install ```sh @@ -43,7 +44,11 @@ apt-get install \ libpulse-dev \ libudev-dev \ libxi-dev \ - libxrandr-dev + libxrandr-dev \ + g++-12 + +update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12 +update-alternatives --set g++ /usr/bin/g++-12 ``` #### Arch Linux Requirements diff --git a/docs/simulation/calculations.yaml b/docs/simulation/calculations.yaml index e9de17a..96c045a 100644 --- a/docs/simulation/calculations.yaml +++ b/docs/simulation/calculations.yaml @@ -51,7 +51,44 @@ Economy: formula: MGQ * GOLD_TO_CASH_RATE MGQ: Money good quanity = total production of goods with money = yes GOLD_TO_CASH_RATE: defines.country.GOLD_TO_CASH_RATE + Overseas maintenance: + See: https://github.com/schombert/Project-Alice/blob/b7889a9c302feed851c039f1a98d73b75be3215a/docs/rules.md#overseas-penalty Production: + Resource gathering operation: + base workforce: defined in common/production_types.txt as workforce + rgo size: + formula: floor(1.5 * ceil(n_workers_in_state / (base workforce * (1 + terrain rgo size modifier))) + n_workers_in_state: Total size of POPs in state where POP type is in employees (common/production_types.txt). + note: Calculated when starting a new game. + Output: + formula: base output * rgo size * (1 + overseas penalty) * throughput from workers * (1 + throughput modifier) * (1 + output from workers) * (1 + output modifier) + base output: defined in common/production_types.txt + overseas penalty: + if province is 'overseas': overseas penalty (see economy > budget > overseas maintenance) + else: 0 + workforce: + formula: base workforce * rgo size * (1 + rgo size modifier) + base workforce: Defined in common/production_types.txt as workforce. + rgo size modifier: sum of rgo size modifiers + sum of farm size modifiers (if farm=yes) + sum of mine size modifiers (if mine=yes) + job portion of workforce: + formula: employees with job / workforce + employees with job: Number of POPs employed with the job. Job being an entry in employees in common/production_types.txt. + effect from job: + formula: + if effect_multiplier == 1: effect_multiplier * employees with job / workforce + else: effect_multiplier * min(employees with job / workforce, amount) + effect_multiplier: defined in common/production_types.txt as part of employees (default 1) + amount: defined in common/production_types.txt (default 1) + throughput from workers: sum of effect from job for each job with `effect = throughput` + output from workers: sum of effect from job for each job with `effect = output` + throughput modifier: sum of throughput modifiers (including from owner job) + output modifier: sum of output modifiers (including from owner job) + modifier from owner job: + formula: effect_multiplier * n_owners_in_state / n_pops_in_state + effect_multiplier: defined in common/production_types.txt as part of owner (default 1) + n_owners_in_state: total size of owner POPs in state + n_pops_in_state: total size of all POPs in state + note: included in throughput or output modifier Human resource management: Vacancies: max employees - employees count Maximum employees hired per day: @@ -68,6 +105,17 @@ Economy: absolute minimum: 50 (hardcoded value) relative minimum: floor(defines.economy.EMPLOYMENT_HIRE_LOWEST * employees count) Military: + Mobilisation: + Mobilised regiment limit: + formula: max(MIN_MOBILIZE_LIMIT, regular regiment count) * (1 + mobilization_impact) + MIN_MOBILIZE_LIMIT: defines.country.MIN_MOBILIZE_LIMIT + regular regiment count: number of deployed non-mobilised regiments + mobilization_impact: sum of mobilization_impact modifiers + Mobilisation throughput effect: + formula: -1 * mobilisation_size * mobilisation_economy_impact + -1: mobilisation reduces throughput for RGOs and factories + mobilisation_size: sum of mobilisation_size modifiers + mobilisation_economy_impact: sum of mobilisation_economy_impact modifiers Maximum regiments per soldier POP: if POP size < defines.military.POP_MIN_SIZE_FOR_REGIMENT: 0 else: @@ -109,7 +157,7 @@ Military: else: formula: x^(1+i) x: received supply - i: 0 based index of regiment in the army + i: 0-based index of regiment in the army trunc(... * 1000): Truncated to 1/1000th. min(..., 1000): Capped at max strength. Regiment reinforcements: @@ -165,10 +213,10 @@ POPs: RGO wages: Owners: if minimum worker wages > normal worker wage: - formula: (RGO income - total worker income) * owner POP size / n_owners * (1 - effective tax) + formula: (RGO income - total worker income) * owner POP size / n_owners_in_state * (1 - effective tax) else: - formula: RGO income * min(0.5, 2 * n_owners / n_workers) * owner POP size / n_owners * (1 - effective tax) - n_owners: total size of owner POPs in state + formula: RGO income * min(0.5, 2 * n_owners_in_state / n_workers) * owner POP size / n_owners_in_state * (1 - effective tax) + n_owners_in_state: total size of owner POPs in state n_workers: number of employed workers in RGO min(0.5: Hardcoded maximum of half the RGO income. 2 *: Hardcoded value. @@ -178,8 +226,8 @@ POPs: if minimum wage > normal wage: formula: min(minimum wage, RGO income * employed workers in POP / total non-slave employed workers) * (1 - effective tax) else: - formula: RGO income * max(0.5, 1 - 2 * n_owners / n_workers) * employed workers in POP / total non-slave employed workers * (1 - effective tax) - n_owners: total size of owner POPs in state + formula: RGO income * max(0.5, 1 - 2 * n_owners_in_state / n_workers) * employed workers in POP / total non-slave employed workers * (1 - effective tax) + n_owners_in_state: total size of owner POPs in state n_workers: number of employed workers in RGO max(0.5: Hardcoded minimum of half the RGO income. 2 *: Hardcoded value. diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation -Subproject 7fe8ba2b3bd3bafad374a691280bbf5102b5867 +Subproject 8defcd5daa1acd2c61aa1cd0a26478d472fed9b diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp index 69330a4..50616d9 100644 --- a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp +++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp @@ -11,6 +11,7 @@ namespace OpenVic { GDCLASS(GFXPieChartTexture, godot::ImageTexture) public: + using godot_pie_chart_data_t = godot::TypedArray<godot::Dictionary>; struct slice_t { godot::String name; godot::Color colour; @@ -29,56 +30,44 @@ namespace OpenVic { godot::Error _generate_pie_chart_image(); - protected: - static void _bind_methods(); - public: - GFXPieChartTexture(); - - // Position must be centred and normalised so that coords are in [-1, 1]. - slice_t const* get_slice(godot::Vector2 const& position) const; - - using godot_pie_chart_data_t = godot::TypedArray<godot::Dictionary>; - - /* Set slices given an Array of Dictionaries, each with the following key-value entries: - * - colour: Color - * - weight: float */ - godot::Error set_slices_array(godot_pie_chart_data_t const& new_slices); - /* Generate slice data from a distribution of objects satisfying HasGetIdentifierAndGetColour, sorted by their weight. * The resulting Array of Dictionaries can be used as an argument for set_slices_array. */ template<typename Container> static godot_pie_chart_data_t distribution_to_slices_array(Container const& dist) requires( ( - /* fixed_point_map_t<T const*>, T derived from HasIdentifierAndColour */ - utility::is_specialization_of_v<Container, tsl::ordered_map> && - HasGetIdentifierAndGetColour<std::remove_pointer_t<typename Container::key_type>> && - std::is_same_v<typename Container::mapped_type, fixed_point_t> - ) || ( - /* IndexedMap<T, fixed_point_t>, T derived from HasIdentifierAndColour */ - utility::is_specialization_of_v<Container, IndexedMap> && - HasGetIdentifierAndGetColour<typename Container::key_type> && - std::is_same_v<typename Container::value_t, fixed_point_t> + /* ordered_map<T const*, mapped_type>, T derived from HasIdentifierAndColour */ + utility::is_specialization_of_v<Container, tsl::ordered_map> + /* IndexedMap<T, mapped_type>, T derived from HasIdentifierAndColour */ + || utility::is_specialization_of_v<Container, IndexedMap> ) + && HasGetIdentifierAndGetColour<std::remove_pointer_t<typename Container::key_type>> + && std::convertible_to<typename Container::mapped_type, float> ) { using namespace godot; + using key_type = std::remove_pointer_t<typename Container::key_type>; - using entry_t = std::pair<key_type const*, fixed_point_t>; + using entry_t = std::pair<key_type const*, float>; + std::vector<entry_t> sorted_dist; + sorted_dist.reserve(dist.size()); if constexpr (utility::is_specialization_of_v<Container, tsl::ordered_map>) { - sorted_dist.reserve(dist.size()); - for (entry_t const& entry : dist) { - ERR_CONTINUE_MSG( - entry.first == nullptr, vformat("Null distribution key with value %f", entry.second.to_float()) - ); - sorted_dist.push_back(entry); + for (auto const& [key, non_float_value] : dist) { + const float value = static_cast<float>(non_float_value); + + ERR_CONTINUE_MSG(key == nullptr, vformat("Null distribution key with value %f", value)); + + if (value != 0.0f) { + sorted_dist.emplace_back(key, value); + } } } else { for (size_t index = 0; index < dist.size(); ++index) { - fixed_point_t const& value = dist[index]; - if (value != 0) { + const float value = static_cast<float>(dist[index]); + + if (value != 0.0f) { key_type const* key = &dist(index); sorted_dist.emplace_back(key, value); } @@ -93,16 +82,30 @@ namespace OpenVic { ERR_FAIL_COND_V(array.resize(sorted_dist.size()) != OK, {}); for (size_t idx = 0; idx < array.size(); ++idx) { - auto const& [key, val] = sorted_dist[idx]; + auto const& [key, value] = sorted_dist[idx]; Dictionary sub_dict; sub_dict[_slice_identifier_key()] = Utilities::std_to_godot_string(key->get_identifier()); sub_dict[_slice_colour_key()] = Utilities::to_godot_color(key->get_colour()); - sub_dict[_slice_weight_key()] = val.to_float(); + sub_dict[_slice_weight_key()] = value; array[idx] = std::move(sub_dict); } return array; } + protected: + static void _bind_methods(); + + public: + GFXPieChartTexture(); + + // Position must be centred and normalised so that coords are in [-1, 1]. + slice_t const* get_slice(godot::Vector2 const& position) const; + + /* Set slices given an Array of Dictionaries, each with the following key-value entries: + * - colour: Color + * - weight: float */ + godot::Error set_slices_array(godot_pie_chart_data_t const& new_slices); + /* Create a GFXPieChartTexture using the specified GFX::PieChart. Returns nullptr if gfx_pie_chart fails. */ static godot::Ref<GFXPieChartTexture> make_gfx_pie_chart_texture(GFX::PieChart const* gfx_pie_chart); diff --git a/extension/src/openvic-extension/singletons/LoadLocalisation.cpp b/extension/src/openvic-extension/singletons/LoadLocalisation.cpp index 55073d6..fbd618c 100644 --- a/extension/src/openvic-extension/singletons/LoadLocalisation.cpp +++ b/extension/src/openvic-extension/singletons/LoadLocalisation.cpp @@ -34,7 +34,7 @@ Error LoadLocalisation::_load_file(String const& file_path, Ref<Translation> con const Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::ModeFlags::READ); Error err = FileAccess::get_open_error(); ERR_FAIL_COND_V_MSG( - err != OK || file.is_null(), err == OK ? FAILED : err, vformat("Failed to open localisation file: %s", file_path) + err != OK || file.is_null(), err == OK ? FAILED : err, vformat("Failed to open localisation file: %s", file_path) ); int line_number = 0; while (!file->eof_reached()) { diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.cpp b/extension/src/openvic-extension/singletons/MenuSingleton.cpp index c4b704d..81582c2 100644 --- a/extension/src/openvic-extension/singletons/MenuSingleton.cpp +++ b/extension/src/openvic-extension/singletons/MenuSingleton.cpp @@ -2,8 +2,10 @@ #include <godot_cpp/variant/utility_functions.hpp> +#include <openvic-simulation/economy/GoodDefinition.hpp> #include <openvic-simulation/GameManager.hpp> -#include <openvic-simulation/misc/Modifier.hpp> +#include <openvic-simulation/modifier/Modifier.hpp> +#include <openvic-simulation/types/fixed_point/FixedPoint.hpp> #include "openvic-extension/classes/GFXPieChartTexture.hpp" #include "openvic-extension/classes/GUINode.hpp" @@ -434,6 +436,10 @@ Dictionary MenuSingleton::get_province_info_from_index(int32_t index) const { static const StringName province_info_controller_key = "controller"; static const StringName province_info_rgo_name_key = "rgo_name"; static const StringName province_info_rgo_icon_key = "rgo_icon"; + static const StringName province_info_rgo_total_employees_key = "rgo_total_employees"; + static const StringName province_info_rgo_employment_percentage_key = "rgo_employment_percentage"; + static const StringName province_info_rgo_output_quantity_yesterday_key = "rgo_output_quantity_yesterday"; + static const StringName province_info_rgo_revenue_yesterday_key = "rgo_revenue_yesterday"; static const StringName province_info_crime_name_key = "crime_name"; static const StringName province_info_crime_icon_key = "crime_icon"; static const StringName province_info_total_population_key = "total_population"; @@ -472,10 +478,21 @@ Dictionary MenuSingleton::get_province_info_from_index(int32_t index) const { ret[province_info_controller_key] = Utilities::std_to_godot_string(controller->get_identifier()); } - GoodDefinition const* rgo = province->get_rgo(); - if (rgo != nullptr) { - ret[province_info_rgo_name_key] = Utilities::std_to_godot_string(rgo->get_identifier()); - ret[province_info_rgo_icon_key] = static_cast<int32_t>(rgo->get_index()); + ResourceGatheringOperation const& rgo = province->get_rgo(); + ret[province_info_rgo_output_quantity_yesterday_key] = rgo.get_output_quantity_yesterday().to_float(); + ret[province_info_rgo_revenue_yesterday_key] = rgo.get_revenue_yesterday().to_float(); + ret[province_info_rgo_total_employees_key] = rgo.get_total_employees_count_cache(); + const Pop::pop_size_t max_employee_count = rgo.get_max_employee_count_cache(); + if (max_employee_count == 0) { + ret[province_info_rgo_employment_percentage_key] = 100.0f; + } else { + ret[province_info_rgo_employment_percentage_key] = (rgo.get_total_employees_count_cache() * fixed_point_t::_100() / max_employee_count).to_float_rounded(); + } + + GoodDefinition const* const rgo_good = province->get_rgo_good(); + if (rgo_good != nullptr) { + ret[province_info_rgo_name_key] = Utilities::std_to_godot_string(rgo_good->get_identifier()); + ret[province_info_rgo_icon_key] = static_cast<int32_t>(rgo_good->get_index()); } Crime const* crime = province->get_crime(); diff --git a/extension/src/openvic-extension/singletons/PopulationMenu.cpp b/extension/src/openvic-extension/singletons/PopulationMenu.cpp index 1bef050..700c57f 100644 --- a/extension/src/openvic-extension/singletons/PopulationMenu.cpp +++ b/extension/src/openvic-extension/singletons/PopulationMenu.cpp @@ -4,6 +4,7 @@ #include <openvic-simulation/DefinitionManager.hpp> #include <openvic-simulation/InstanceManager.hpp> +#include <openvic-simulation/types/fixed_point/FixedPoint.hpp> #include "openvic-extension/classes/GFXPieChartTexture.hpp" #include "openvic-extension/classes/GUINode.hpp" @@ -160,7 +161,7 @@ TypedArray<Dictionary> MenuSingleton::get_population_menu_province_list_rows(int return --count_counter > 0; } - return true; + return true; } } entry_visitor { *this, start, count }; @@ -366,7 +367,7 @@ Error MenuSingleton::_population_menu_update_pops() { if (province_entry != nullptr && province_entry->selected) { for (Pop const& pop : province_entry->province.get_pops()) { population_menu.pops.push_back(&pop); - population_menu_t::pop_filter_t& filter = population_menu.pop_filters[&pop.get_type()]; + population_menu_t::pop_filter_t& filter = population_menu.pop_filters[pop.get_type()]; filter.count += pop.get_size(); // TODO - set filter.promotion_demotion_change } @@ -387,7 +388,7 @@ Error MenuSingleton::_population_menu_update_filtered_pops() { population_menu.vote_distribution.clear(); for (Pop const* pop : population_menu.pops) { - if (population_menu.pop_filters[&pop->get_type()].selected) { + if (population_menu.pop_filters[pop->get_type()].selected) { population_menu.filtered_pops.push_back(pop); } } @@ -395,12 +396,12 @@ Error MenuSingleton::_population_menu_update_filtered_pops() { for (Pop const* pop : population_menu.filtered_pops) { const fixed_point_t pop_size = fixed_point_t::parse(pop->get_size()); - population_menu.workforce_distribution[&pop->get_type()] += pop->get_size(); - population_menu.religion_distribution[&pop->get_religion()] += pop->get_size(); - population_menu.ideology_distribution += pop->get_ideologies() * fixed_point_t::parse(pop->get_size()); - population_menu.culture_distribution[&pop->get_culture()] += pop->get_size(); - population_menu.issue_distribution += pop->get_issues() * fixed_point_t::parse(pop->get_size()); - population_menu.vote_distribution += pop->get_votes() * fixed_point_t::parse(pop->get_size()); + population_menu.workforce_distribution[pop->get_type()] += pop_size; + population_menu.religion_distribution[&pop->get_religion()] += pop_size; + population_menu.ideology_distribution += pop->get_ideologies() * pop_size; + population_menu.culture_distribution[&pop->get_culture()] += pop_size; + population_menu.issue_distribution += pop->get_issues() * pop_size; + population_menu.vote_distribution += pop->get_votes() * pop_size; } normalise_fixed_point_map(population_menu.workforce_distribution); @@ -678,7 +679,7 @@ TypedArray<Dictionary> MenuSingleton::get_population_menu_pop_rows(int32_t start Dictionary pop_dict; pop_dict[pop_size_key] = pop->get_size(); - pop_dict[pop_type_icon_key] = pop->get_type().get_sprite(); + pop_dict[pop_type_icon_key] = pop->get_type()->get_sprite(); pop_dict[pop_culture_key] = Utilities::std_to_godot_string(pop->get_culture().get_identifier()); pop_dict[pop_religion_icon_key] = pop->get_religion().get_icon(); if (pop->get_location() != nullptr) { diff --git a/extension/src/openvic-extension/singletons/SoundSingleton.cpp b/extension/src/openvic-extension/singletons/SoundSingleton.cpp index b9a2ee3..4ba62f2 100644 --- a/extension/src/openvic-extension/singletons/SoundSingleton.cpp +++ b/extension/src/openvic-extension/singletons/SoundSingleton.cpp @@ -28,32 +28,32 @@ void SoundSingleton::_bind_methods() { OV_BIND_METHOD(SoundSingleton::load_music); OV_BIND_METHOD(SoundSingleton::get_song, {"song_name"}); OV_BIND_METHOD(SoundSingleton::get_song_list); - + ADD_PROPERTY(PropertyInfo( Variant::ARRAY, - "song_list", PROPERTY_HINT_ARRAY_TYPE, - "AudioStreamMP3"), + "song_list", PROPERTY_HINT_ARRAY_TYPE, + "AudioStreamMP3"), "", "get_song_list"); OV_BIND_METHOD(SoundSingleton::load_sounds); OV_BIND_METHOD(SoundSingleton::get_sound_stream, {"sound_name"}); OV_BIND_METHOD(SoundSingleton::get_sound_base_volume, {"sound_name"}); OV_BIND_METHOD(SoundSingleton::get_sound_list); - + ADD_PROPERTY(PropertyInfo( Variant::ARRAY, - "sound_list", + "sound_list", PROPERTY_HINT_ARRAY_TYPE, "AudioStreamWAV"), "", "get_sound_list"); - + OV_BIND_METHOD(SoundSingleton::load_title_theme); OV_BIND_METHOD(SoundSingleton::get_title_theme); - + ADD_PROPERTY(PropertyInfo( Variant::STRING, "title_theme"), - "", "get_title_theme"); + "", "get_title_theme"); } @@ -71,15 +71,14 @@ SoundSingleton::~SoundSingleton() { _singleton = nullptr; } - -//Load a sound from the path +//Load a sound from the path Ref<AudioStreamMP3> SoundSingleton::_load_godot_mp3(String const& path) const { const Ref<FileAccess> file = FileAccess::open(path, FileAccess::ModeFlags::READ); - + Error err = FileAccess::get_open_error(); ERR_FAIL_COND_V_MSG( err != OK || file.is_null(), nullptr, - vformat("Failed to open mp3 file %s", path) //named %s, path, + vformat("Failed to open mp3 file %s", path) //named %s, path, ); const PackedByteArray data = file->get_buffer(file->get_length()); @@ -92,7 +91,7 @@ Ref<AudioStreamMP3> SoundSingleton::_load_godot_mp3(String const& path) const { } //slices a path down to after the base_folder, keeps the extension -//this is because the defines refer to audio files using this format, +//this is because the defines refer to audio files using this format, //so we might as well use this form as the key for the "name"->audiostream map String SoundSingleton::to_define_file_name(String const& path, std::string_view const& base_folder) const { String name = path.replace("\\","/"); @@ -132,7 +131,7 @@ bool SoundSingleton::load_title_theme(){ Dataloader::path_vector_t music_files = game_singleton->get_dataloader() .lookup_files_in_dir_recursive(music_directory, ".mp3"); - + if(music_files.size() < 1){ Logger::error("failed to load title theme: no files in music directory"); } @@ -159,7 +158,7 @@ bool SoundSingleton::load_title_theme(){ } if(!ret) Logger::error("Failed to load title theme!"); - + return ret; } @@ -167,13 +166,13 @@ bool SoundSingleton::load_music() { GameSingleton const* game_singleton = GameSingleton::get_singleton(); ERR_FAIL_NULL_V_MSG(game_singleton, false, vformat("Error retrieving GameSingleton")); - + static constexpr std::string_view music_directory = "music"; bool ret = true; Dataloader::path_vector_t music_files = game_singleton->get_dataloader() .lookup_files_in_dir_recursive(music_directory, ".mp3"); - + if(music_files.size() < 1){ Logger::error("failed to load music: no files in music directory"); ret = false; @@ -190,7 +189,6 @@ bool SoundSingleton::load_music() { continue; //don't try to append a null pointer to the list } song_list.append(name); - } return ret; @@ -209,7 +207,7 @@ Ref<AudioStreamWAV> SoundSingleton::get_sound(String const& path){ ERR_FAIL_NULL_V_MSG( sound, nullptr, - vformat("Failed to load sound file %s", path) //named %s, path, + vformat("Failed to load sound file %s", path) //named %s, path ); sfx.emplace(std::move(name), sound); @@ -256,14 +254,14 @@ bool SoundSingleton::load_sounds() { for(SoundEffect const& sound_inst : sound_manager.get_sound_effects()){ std::string folder_path = StringUtils::append_string_views(sound_directory, "/", sound_inst.get_file()); fs::path full_path = game_singleton->get_dataloader().lookup_file(folder_path, false); - + //UI_Cavalry_Selected.wav doesn't exist (paradox mistake, UI_Cavalry_Select.wav does), just keep going //the define its associated with also isn't used in game if(full_path.empty()){ Logger::warning("The sound define ",sound_inst.get_identifier()," points to an non-existing file ", folder_path); continue; } - + Ref<AudioStreamWAV> stream = get_sound(std_to_godot_string(full_path.string())); if(stream.is_null()){ Logger::error("failed to load sound ",sound_inst.get_identifier()," at path ",full_path); @@ -286,14 +284,13 @@ bool SoundSingleton::load_sounds() { Ref<AudioStreamWAV> SoundSingleton::_load_godot_wav(String const& path) const { const Ref<FileAccess> file = FileAccess::open(path, FileAccess::ModeFlags::READ); - + Error err = FileAccess::get_open_error(); ERR_FAIL_COND_V_MSG( err != OK || file.is_null(), nullptr, vformat("Failed to open wav file %s", path) ); - Ref<AudioStreamWAV> sound = Ref<AudioStreamWAV>(); sound.instantiate(); @@ -302,7 +299,6 @@ Ref<AudioStreamWAV> SoundSingleton::_load_godot_wav(String const& path) const { int riff_size = std::min(static_cast<uint64_t>(file->get_32()), file->get_length()); String form_type = read_riff_str(file); //WAVE - //ie. 16, 24, 32 bit audio int bits_per_sample = 0; @@ -328,13 +324,13 @@ Ref<AudioStreamWAV> SoundSingleton::_load_godot_wav(String const& path) const { int samplesPerSec = file->get_32(); int avgBytesPerSec = file->get_32(); int blockAlign = file->get_16(); - + bits_per_sample = file->get_16(); ERR_FAIL_COND_V_MSG( bits_per_sample == 24 || bits_per_sample == 32, nullptr, vformat("Unsupported wav file sample rate %s", bits_per_sample) ); - + if(size > 16){ int extensionSize = file->get_16(); } @@ -369,13 +365,12 @@ Ref<AudioStreamWAV> SoundSingleton::_load_godot_wav(String const& path) const { break; } } - + sound->set_mix_rate(samplesPerSec); - } else if(id=="data"){ PackedByteArray audio_data = file->get_buffer(size); - + if(bits_per_sample == 24 || bits_per_sample == 32){ //sound->set_data(to_16bit_wav_data(audio_data,bits_per_sample)); Logger::error("WAV file ",godot_to_std_string(path), " uses an unsupported sample rate ", bits_per_sample); diff --git a/extension/src/openvic-extension/singletons/SoundSingleton.hpp b/extension/src/openvic-extension/singletons/SoundSingleton.hpp index bfa03ea..67305a9 100644 --- a/extension/src/openvic-extension/singletons/SoundSingleton.hpp +++ b/extension/src/openvic-extension/singletons/SoundSingleton.hpp @@ -37,7 +37,7 @@ namespace OpenVic { std::optional<godot::Ref<godot::AudioStreamWAV>> audioStream; std::optional<fixed_point_t> volume; }; - using sfx_define_map_t = deque_ordered_map<godot::StringName,sound_asset_t>; + using sfx_define_map_t = deque_ordered_map<godot::StringName,sound_asset_t>; sfx_define_map_t sfx_define; static constexpr std::string_view title_theme_name = "thecoronation_titletheme.mp3"; @@ -47,7 +47,7 @@ namespace OpenVic { //property for gd scripts to access song names godot::Array PROPERTY(song_list); godot::String PROPERTY(title_theme); - + //property for gd scripts to access sound names godot::Array PROPERTY(sound_list); @@ -61,26 +61,25 @@ namespace OpenVic { godot::String to_define_file_name(godot::String const& path, std::string_view const& base_folder) const; godot::String read_riff_str(godot::Ref<godot::FileAccess> const& file, int size=4) const; - + private: /* Loads AudioStreams (.mp3 or .wav) at runtime using godot's functions*/ godot::Ref<godot::AudioStreamMP3> _load_godot_mp3(godot::String const& path) const; godot::Ref<godot::AudioStreamWAV> _load_godot_wav(godot::String const& path) const; - + public: //gets a song from the cache ('tracks' variable), or if not, then from the files using _load_godot_mp3 godot::Ref<godot::AudioStreamMP3> get_song(godot::String const& name); godot::Ref<godot::AudioStreamWAV> get_sound(godot::String const& path); - + //load the files into memory bool load_music(); bool load_sounds(); bool load_title_theme(); - + //for sound effects, get the stream and relative volume it should play at from the sfx map godot::Ref<godot::AudioStreamWAV> get_sound_stream(godot::String const& path); float get_sound_base_volume(godot::String const& path); }; - -}
\ No newline at end of file +} diff --git a/game/src/Game/Autoload/SoundManager.gd b/game/src/Game/Autoload/SoundManager.gd index 01562f8..520afb1 100644 --- a/game/src/Game/Autoload/SoundManager.gd +++ b/game/src/Game/Autoload/SoundManager.gd @@ -41,7 +41,7 @@ func play_effect_stream(sound : AudioStream, volume : float = 1.0) -> void: func play_effect(sound : String) -> void: play(sound, "SFX") - + func play_effect_compat(sfx : String, fallback : AudioStream=null) -> void: var sound:AudioStreamWAV = SoundSingleton.get_sound_stream(sfx) var volume:float = SoundSingleton.get_sound_base_volume(sfx) @@ -53,4 +53,3 @@ func play_effect_compat(sfx : String, fallback : AudioStream=null) -> void: play_effect_stream(fallback) else: push_warning("Failed to find sound %s" % sfx) - diff --git a/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd b/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd index 021c9f2..45e91d3 100644 --- a/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd +++ b/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd @@ -203,7 +203,7 @@ func _update_info() -> void: if _active: if _gold_label: - _gold_label.text = "%s¤" % GUINode.float_to_string_dp(_incVal - (_incVal % 7), 1) + _gold_label.text = "%s¤" % GUINode.float_to_string_dp(_incVal - (_incVal % 7), 1) if _total_inc_label: _total_inc_label.text = "%s¤" % GUINode.float_to_string_dp_dynamic(_incVal) diff --git a/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd b/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd index eb57387..b55d227 100644 --- a/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd +++ b/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd @@ -160,7 +160,7 @@ func _generate_province_list_row(index : int, type : MenuSingleton.ProvinceListE return OK func _setup_province_list() -> void: - if not _province_listbox: + if not _province_listbox: _province_listbox = get_gui_listbox_from_nodepath(^"./country_pop/pop_province_list") if not _province_listbox: diff --git a/game/src/Game/GameSession/ProvinceIndexSampler.gdshaderinc b/game/src/Game/GameSession/ProvinceIndexSampler.gdshaderinc index 1adcd95..acebdc6 100644 --- a/game/src/Game/GameSession/ProvinceIndexSampler.gdshaderinc +++ b/game/src/Game/GameSession/ProvinceIndexSampler.gdshaderinc @@ -13,7 +13,7 @@ uvec3 vec3_to_uvec3(vec3 v) { // (u, v) -> (province index bottom byte, province index top byte, terrain index byte) uvec3 read_uvec3(vec2 uv) { uv *= province_shape_subdivisions; - vec2 subdivision_coords = mod(floor(uv), province_shape_subdivisions); + vec2 subdivision_coords = mod(floor(uv), province_shape_subdivisions); float idx = subdivision_coords.x + subdivision_coords.y * province_shape_subdivisions.x; return vec3_to_uvec3(texture(province_shape_tex, vec3(uv, idx)).rgb); } diff --git a/game/src/Game/GameSession/ProvinceOverviewPanel.gd b/game/src/Game/GameSession/ProvinceOverviewPanel.gd index 66fd463..a9f0ae2 100644 --- a/game/src/Game/GameSession/ProvinceOverviewPanel.gd +++ b/game/src/Game/GameSession/ProvinceOverviewPanel.gd @@ -240,6 +240,10 @@ func _update_info() -> void: const _province_info_controller_key : StringName = &"controller" const _province_info_rgo_name_key : StringName = &"rgo_name" const _province_info_rgo_icon_key : StringName = &"rgo_icon" + const _province_info_rgo_total_employees_key : StringName = &"rgo_total_employees" + const _province_info_rgo_employment_percentage_key : StringName = &"rgo_employment_percentage" + const _province_info_rgo_output_quantity_yesterday_key : StringName = &"rgo_output_quantity_yesterday" + const _province_info_rgo_revenue_yesterday_key : StringName = &"rgo_revenue_yesterday" const _province_info_crime_name_key : StringName = &"crime_name" const _province_info_crime_icon_key : StringName = &"crime_icon" const _province_info_total_population_key : StringName = &"total_population" @@ -305,22 +309,19 @@ func _update_info() -> void: _rgo_icon.set_icon_index(_province_info.get(_province_info_rgo_icon_key, -1) + 2) if _rgo_produced_label: - # TODO - replace name with amount produced - _rgo_produced_label.text = _province_info.get(_province_info_rgo_name_key, _province_info_rgo_name_key + _missing_suffix) + _rgo_produced_label.text = GUINode.float_to_string_dp(_province_info.get(_province_info_rgo_output_quantity_yesterday_key, 0), 3) if _rgo_income_label: - # TODO - add £ sign and replace placeholder with actual value - _rgo_income_label.text = "%s¤" % GUINode.float_to_string_dp(12.34567, 3) + _rgo_income_label.text = "%s¤" % GUINode.float_to_string_dp(_province_info.get(_province_info_rgo_revenue_yesterday_key, 0), 3) if _rgo_employment_percentage_icon: - pass + _rgo_employment_percentage_icon.set_icon_index(int(_province_info.get(_province_info_rgo_employment_percentage_key, 0) / 10) + 1) if _rgo_employment_population_label: - # TODO - replace placeholder with actual value - _rgo_employment_population_label.text = GUINode.int_to_string_suffixed(_province_info.get(_province_info_total_population_key, 0) / 10) + _rgo_employment_population_label.text = GUINode.int_to_string_suffixed(_province_info.get(_province_info_rgo_total_employees_key, 0)) if _rgo_employment_percentage_label: - pass + _rgo_employment_percentage_label.text = "%d%%" % _province_info.get(_province_info_rgo_employment_percentage_key, 0) if _crime_name_label: _crime_name_label.text = _province_info.get(_province_info_crime_name_key, "") diff --git a/game/src/Game/GameSession/Topbar.gd b/game/src/Game/GameSession/Topbar.gd index 2f1fdbf..aecdced 100644 --- a/game/src/Game/GameSession/Topbar.gd +++ b/game/src/Game/GameSession/Topbar.gd @@ -570,7 +570,7 @@ func _update_speed_controls() -> void: var paused : bool = MenuSingleton.is_paused() var speed : int = MenuSingleton.get_speed() - # TODO - decide whether to disable these or not + # TODO - decide whether to disable these or not # (they don't appear to get disabled in the base game) #if _speed_up_button: # _speed_up_button.disabled = not MenuSingleton.can_increase_speed() diff --git a/game/src/Game/Model/XACLoader.gd b/game/src/Game/Model/XACLoader.gd index 6b12cf2..c16d1cd 100644 --- a/game/src/Game/Model/XACLoader.gd +++ b/game/src/Game/Model/XACLoader.gd @@ -197,13 +197,13 @@ static func _load_xac_model(source_file : String, is_unit : bool) -> Node3D: break skinning_chunk_ind += 1 if skinning_chunk_ind >= len(skinningChunks): - skinning_chunk_ind = 1 + skinning_chunk_ind = 1 applyVertexWeights = false var meshInstance : MeshInstance3D = MeshInstance3D.new() node.add_child(meshInstance) meshInstance.owner = node - + #stop the culling of units near the tops of screens meshInstance.extra_cull_margin = EXTRA_CULL_MARGIN diff --git a/game/src/Game/Model/XSMLoader.gd b/game/src/Game/Model/XSMLoader.gd index bc85f9b..e1706ae 100644 --- a/game/src/Game/Model/XSMLoader.gd +++ b/game/src/Game/Model/XSMLoader.gd @@ -122,8 +122,8 @@ class MetadataChunk: var unused : float var fMaxAcceptableError : float var fps : int # int32 - var exporterMajorVersion : int # byte - var exporterMinorVersion : int # byte + var exporterMajorVersion : int # byte + var exporterMinorVersion : int # byte var pad : int # 2x byte var sourceApp : String var origFileName : String diff --git a/game/src/Game/MusicConductor/MusicConductor.gd b/game/src/Game/MusicConductor/MusicConductor.gd index f7e3b2a..ade8fb4 100644 --- a/game/src/Game/MusicConductor/MusicConductor.gd +++ b/game/src/Game/MusicConductor/MusicConductor.gd @@ -34,7 +34,7 @@ func get_all_song_names() -> PackedStringArray: for si : SongInfo in _available_songs: songNames.append(si.song_name) return songNames - + func get_all_song_paths() -> PackedStringArray: var songPaths : PackedStringArray = [] for si : SongInfo in _available_songs: @@ -103,13 +103,13 @@ func setup_compat_song(file_name) -> void: var metadata = MusicMetadata.new() metadata.set_from_stream(stream) var title = metadata.title - + if title == "": #use the file name without the extension if there's no metadata title = file_name.split(".")[0] song.init_stream(file_name,title,stream) _available_songs.append(song) - + func add_compat_songs() -> void: for file_name : String in SoundSingleton.song_list: setup_compat_song(file_name) @@ -128,13 +128,13 @@ func add_ootb_music() -> void: func generate_playlist() -> void: var song_names = MusicConductor.get_all_song_paths() var possible_indices = range(len(song_names)-1) - + var title_index = song_names.find(SoundSingleton.title_theme) possible_indices.remove_at(title_index) - + var actual_playlist_len = min(preferred_playlist_len,len(possible_indices)) - - #if the playlist size is too large or small, make it the same size as what we + + #if the playlist size is too large or small, make it the same size as what we #need to support if len(playlist) != actual_playlist_len: playlist.resize(actual_playlist_len) @@ -143,14 +143,14 @@ func generate_playlist() -> void: #The song we just played can be in the playlist, just not the first one if last_played != -1: possible_indices.remove_at(last_played) - + #essentially shuffle-bag randomness, picking from a list of song indices for i in range(actual_playlist_len): var ind = randi_range(0,len(possible_indices)-1) #add back the last song we just played as an option if i==2: possible_indices.append(last_played) - + playlist[i] = possible_indices[ind] possible_indices.remove_at(ind) diff --git a/game/src/Game/MusicConductor/MusicPlayer.gd b/game/src/Game/MusicConductor/MusicPlayer.gd index 17285dc..0db44ca 100644 --- a/game/src/Game/MusicConductor/MusicPlayer.gd +++ b/game/src/Game/MusicConductor/MusicPlayer.gd @@ -27,7 +27,7 @@ func _process(_delta : float) -> void: _progress_slider.value = MusicConductor.get_current_song_progress_percentage() func _update_play_pause_button(_arg1 : Variant = null, _arg2 : Variant = null) -> void: - _play_pause_button.text = "◼" if MusicConductor.is_paused() else "▶" + _play_pause_button.text = "▶️" if MusicConductor.is_paused() else "❚❚" func _on_play_pause_button_pressed() -> void: MusicConductor.toggle_play_pause() |