diff options
Diffstat (limited to 'src/openvic-simulation/economy/production')
10 files changed, 1050 insertions, 0 deletions
diff --git a/src/openvic-simulation/economy/production/ArtisanalProducer.cpp b/src/openvic-simulation/economy/production/ArtisanalProducer.cpp new file mode 100644 index 0000000..d5cc3d3 --- /dev/null +++ b/src/openvic-simulation/economy/production/ArtisanalProducer.cpp @@ -0,0 +1,13 @@ +#include "ArtisanalProducer.hpp" + +using namespace OpenVic; + +ArtisanalProducer::ArtisanalProducer( + ProductionType const& new_production_type, + GoodDefinition::good_definition_map_t&& new_stockpile, + fixed_point_t new_current_production, + GoodDefinition::good_definition_map_t&& new_current_needs +) : production_type { new_production_type }, + stockpile { std::move(new_stockpile) }, + current_production { new_current_production }, + current_needs { std::move(new_current_needs) } {} diff --git a/src/openvic-simulation/economy/production/ArtisanalProducer.hpp b/src/openvic-simulation/economy/production/ArtisanalProducer.hpp new file mode 100644 index 0000000..65aa3fa --- /dev/null +++ b/src/openvic-simulation/economy/production/ArtisanalProducer.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "openvic-simulation/economy/GoodDefinition.hpp" +#include "openvic-simulation/economy/production/ProductionType.hpp" +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" +#include "openvic-simulation/utility/Getters.hpp" + +namespace OpenVic { + struct ArtisanalProducer { + private: + ProductionType const& PROPERTY(production_type); + GoodDefinition::good_definition_map_t PROPERTY(stockpile); + fixed_point_t PROPERTY(current_production); + GoodDefinition::good_definition_map_t PROPERTY(current_needs); + + public: + ArtisanalProducer( + ProductionType const& new_production_type, GoodDefinition::good_definition_map_t&& new_stockpile, + fixed_point_t new_current_production, GoodDefinition::good_definition_map_t&& new_current_needs + ); + }; +} diff --git a/src/openvic-simulation/economy/production/Employee.cpp b/src/openvic-simulation/economy/production/Employee.cpp new file mode 100644 index 0000000..569299a --- /dev/null +++ b/src/openvic-simulation/economy/production/Employee.cpp @@ -0,0 +1,8 @@ +#include "Employee.hpp" + +using namespace OpenVic; + +Employee::Employee(Pop& new_pop, const Pop::pop_size_t new_size) + : pop { new_pop }, + size { new_size } + {}
\ No newline at end of file diff --git a/src/openvic-simulation/economy/production/Employee.hpp b/src/openvic-simulation/economy/production/Employee.hpp new file mode 100644 index 0000000..8a09c31 --- /dev/null +++ b/src/openvic-simulation/economy/production/Employee.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "openvic-simulation/pop/Pop.hpp" + +namespace OpenVic { + struct Employee { + private: + Pop::pop_size_t PROPERTY_RW(size); + public: + Pop& pop; + Employee(Pop& new_pop, const Pop::pop_size_t new_size); + }; +}
\ No newline at end of file diff --git a/src/openvic-simulation/economy/production/FactoryProducer.cpp b/src/openvic-simulation/economy/production/FactoryProducer.cpp new file mode 100644 index 0000000..2ef9fa8 --- /dev/null +++ b/src/openvic-simulation/economy/production/FactoryProducer.cpp @@ -0,0 +1,58 @@ +#include "FactoryProducer.hpp" + +using namespace OpenVic; + +FactoryProducer::FactoryProducer( + ProductionType const& new_production_type, + fixed_point_t new_size_multiplier, + fixed_point_t new_revenue_yesterday, + fixed_point_t new_output_quantity_yesterday, + fixed_point_t new_unsold_quantity_yesterday, + ordered_map<Pop*, Pop::pop_size_t>&& new_employees, + GoodDefinition::good_definition_map_t&& new_stockpile, + fixed_point_t new_budget, + fixed_point_t new_balance_yesterday, + fixed_point_t new_received_investments_yesterday, + fixed_point_t new_market_spendings_yesterday, + fixed_point_t new_paychecks_yesterday, + uint32_t new_unprofitable_days, + uint32_t new_subsidised_days, + uint32_t new_days_without_input, + uint8_t new_hiring_priority, + uint8_t new_profit_history_current, + daily_profit_history_t&& new_daily_profit_history +) : production_type { new_production_type }, + size_multiplier { new_size_multiplier }, + revenue_yesterday { new_revenue_yesterday }, + output_quantity_yesterday { new_output_quantity_yesterday }, + unsold_quantity_yesterday { new_unsold_quantity_yesterday }, + employees { std::move(new_employees) }, + stockpile { std::move(new_stockpile) }, + budget { new_budget }, + balance_yesterday { new_balance_yesterday }, + received_investments_yesterday { new_received_investments_yesterday }, + market_spendings_yesterday { new_market_spendings_yesterday }, + paychecks_yesterday { new_paychecks_yesterday }, + unprofitable_days { new_unprofitable_days }, + subsidised_days { new_subsidised_days }, + days_without_input { new_days_without_input }, + hiring_priority { new_hiring_priority }, + profit_history_current { new_profit_history_current }, + daily_profit_history { std::move(new_daily_profit_history) } {} + +FactoryProducer::FactoryProducer(ProductionType const& new_production_type, fixed_point_t new_size_multiplier) + : FactoryProducer { new_production_type, new_size_multiplier, 0, 0, 0, {}, {}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {} } {} + +fixed_point_t FactoryProducer::get_profitability_yesterday() const { + return daily_profit_history[profit_history_current]; +} + +fixed_point_t FactoryProducer::get_average_profitability_last_seven_days() const { + fixed_point_t sum = 0; + + for (int i = 0; i <= profit_history_current; i++) { + sum += daily_profit_history[i]; + } + + return sum / (1 + profit_history_current); +} diff --git a/src/openvic-simulation/economy/production/FactoryProducer.hpp b/src/openvic-simulation/economy/production/FactoryProducer.hpp new file mode 100644 index 0000000..9e660ba --- /dev/null +++ b/src/openvic-simulation/economy/production/FactoryProducer.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include <cstdint> + +#include "openvic-simulation/economy/GoodDefinition.hpp" +#include "openvic-simulation/economy/production/ProductionType.hpp" +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" +#include "openvic-simulation/utility/Getters.hpp" + +namespace OpenVic { + struct FactoryProducer { + private: + static constexpr uint8_t DAYS_OF_HISTORY = 7; + using daily_profit_history_t = std::array<fixed_point_t, DAYS_OF_HISTORY>; + + uint8_t PROPERTY(profit_history_current); + daily_profit_history_t PROPERTY(daily_profit_history); + ProductionType const& PROPERTY(production_type); + fixed_point_t PROPERTY(revenue_yesterday); + fixed_point_t PROPERTY(output_quantity_yesterday); + fixed_point_t PROPERTY(unsold_quantity_yesterday); + fixed_point_t PROPERTY(size_multiplier); + ordered_map<Pop*, Pop::pop_size_t> PROPERTY(employees); + GoodDefinition::good_definition_map_t PROPERTY(stockpile); + fixed_point_t PROPERTY(budget); + fixed_point_t PROPERTY(balance_yesterday); + fixed_point_t PROPERTY(received_investments_yesterday); + fixed_point_t PROPERTY(market_spendings_yesterday); + fixed_point_t PROPERTY(paychecks_yesterday); + uint32_t PROPERTY(unprofitable_days); + uint32_t PROPERTY(subsidised_days); + uint32_t PROPERTY(days_without_input); + uint8_t PROPERTY_RW(hiring_priority); + + public: + FactoryProducer( + ProductionType const& new_production_type, fixed_point_t new_size_multiplier, fixed_point_t new_revenue_yesterday, + fixed_point_t new_output_quantity_yesterday, fixed_point_t new_unsold_quantity_yesterday, + ordered_map<Pop*, Pop::pop_size_t>&& new_employees, GoodDefinition::good_definition_map_t&& new_stockpile, + fixed_point_t new_budget, fixed_point_t new_balance_yesterday, fixed_point_t new_received_investments_yesterday, + fixed_point_t new_market_spendings_yesterday, fixed_point_t new_paychecks_yesterday, uint32_t new_unprofitable_days, + uint32_t new_subsidised_days, uint32_t new_days_without_input, uint8_t new_hiring_priority, + uint8_t new_profit_history_current, daily_profit_history_t&& new_daily_profit_history + ); + FactoryProducer(ProductionType const& new_production_type, fixed_point_t new_size_multiplier); + + fixed_point_t get_profitability_yesterday() const; + fixed_point_t get_average_profitability_last_seven_days() const; + }; +} diff --git a/src/openvic-simulation/economy/production/ProductionType.cpp b/src/openvic-simulation/economy/production/ProductionType.cpp new file mode 100644 index 0000000..033026d --- /dev/null +++ b/src/openvic-simulation/economy/production/ProductionType.cpp @@ -0,0 +1,349 @@ +#include "ProductionType.hpp" + +#include <openvic-dataloader/v2script/Parser.hpp> + +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +Job::Job( + PopType const* new_pop_type, + effect_t new_effect_type, + fixed_point_t new_effect_multiplier, + fixed_point_t new_amount +) : pop_type { new_pop_type }, + effect_type { new_effect_type }, + effect_multiplier { new_effect_multiplier }, + amount { new_amount } {} + +ProductionType::ProductionType( + const std::string_view new_identifier, + const std::optional<Job> new_owner, + std::vector<Job>&& new_jobs, + const template_type_t new_template_type, + const Pop::pop_size_t new_base_workforce_size, + GoodDefinition::good_definition_map_t&& new_input_goods, + GoodDefinition const& new_output_good, + const fixed_point_t new_base_output_quantity, + std::vector<bonus_t>&& new_bonuses, + GoodDefinition::good_definition_map_t&& new_maintenance_requirements, + const bool new_is_coastal, + const bool new_is_farm, + const bool new_is_mine +) : HasIdentifier { new_identifier }, + owner { new_owner }, + jobs { std::move(new_jobs) }, + template_type { new_template_type }, + base_workforce_size { new_base_workforce_size }, + input_goods { std::move(new_input_goods) }, + output_good { new_output_good }, + base_output_quantity { new_base_output_quantity }, + bonuses { std::move(new_bonuses) }, + maintenance_requirements { std::move(new_maintenance_requirements) }, + coastal { new_is_coastal }, + farm { new_is_farm }, + mine { new_is_mine } {} + +bool ProductionType::parse_scripts(DefinitionManager const& definition_manager) { + bool ret = true; + for (auto& [bonus_script, bonus_value] : bonuses) { + ret &= bonus_script.parse_script(false, definition_manager); + } + return ret; +} + +ProductionTypeManager::ProductionTypeManager() : + good_to_rgo_production_type { nullptr }, + rgo_owner_sprite { 0 } {} + +node_callback_t ProductionTypeManager::_expect_job( + GoodDefinitionManager const& good_definition_manager, PopManager const& pop_manager, callback_t<Job&&> callback +) { + return [this, &good_definition_manager, &pop_manager, callback](ast::NodeCPtr node) -> bool { + using enum Job::effect_t; + + std::string_view pop_type {}; + Job::effect_t effect_type { THROUGHPUT }; + fixed_point_t effect_multiplier = 1, desired_workforce_share = 1; + + static const string_map_t<Job::effect_t> effect_map = { + { "input", INPUT }, { "output", OUTPUT }, { "throughput", THROUGHPUT } + }; + + bool res = expect_dictionary_keys( + "poptype", ONE_EXACTLY, expect_identifier(assign_variable_callback(pop_type)), + "effect", ONE_EXACTLY, expect_identifier(expect_mapped_string(effect_map, assign_variable_callback(effect_type))), + "effect_multiplier", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(effect_multiplier)), + "amount", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(desired_workforce_share)) + )(node); + + PopType const* found_pop_type = pop_manager.get_pop_type_by_identifier(pop_type); + return res & callback({ found_pop_type, effect_type, effect_multiplier, desired_workforce_share }); + }; +} + +node_callback_t ProductionTypeManager::_expect_job_list( + GoodDefinitionManager const& good_definition_manager, PopManager const& pop_manager, + callback_t<std::vector<Job>&&> callback +) { + return [this, &good_definition_manager, &pop_manager, callback](ast::NodeCPtr node) -> bool { + std::vector<Job> jobs; + bool ret = expect_list(_expect_job(good_definition_manager, pop_manager, vector_callback(jobs)))(node); + ret &= callback(std::move(jobs)); + return ret; + }; +} + +bool ProductionTypeManager::add_production_type( + const std::string_view identifier, + std::optional<Job> owner, + std::vector<Job>&& jobs, + const ProductionType::template_type_t template_type, + const Pop::pop_size_t base_workforce_size, + GoodDefinition::good_definition_map_t&& input_goods, + GoodDefinition const* const output_good, + const fixed_point_t base_output_quantity, + std::vector<ProductionType::bonus_t>&& bonuses, + GoodDefinition::good_definition_map_t&& maintenance_requirements, + const bool is_coastal, + const bool is_farm, + const bool is_mine +) { + if (identifier.empty()) { + Logger::error("Invalid production type identifier - empty!"); + return false; + } + + if (base_workforce_size <= 0) { + Logger::error("Base workforce size ('workforce') for production type ", identifier, " was 0 or unset!"); + return false; + } + + if (base_output_quantity <= 0) { + Logger::error("Base output quantity ('value') for production type ", identifier, " was 0 or unset!"); + return false; + } + + if (output_good == nullptr) { + Logger::error("Output good for production type ", identifier, " was null!"); + return false; + } + + using enum ProductionType::template_type_t; + + if (template_type == ARTISAN) { + if (owner.has_value()) { + Logger::warning( + "Artisanal production type ", identifier, " should not have an owner - it is being ignored." + ); + owner.reset(); + } + + if (!jobs.empty()) { + Logger::warning( + "Artisanal production type ", identifier, " should not have employees - ", jobs.size(), " are being ignored." + ); + jobs.clear(); + } + } else { + if (!owner.has_value()) { + Logger::error("Production type ", identifier, " is missing an owner."); + return false; + } + + if (owner->get_pop_type() == nullptr) { + Logger::error("Production type ", identifier, " owner has an invalid pop type."); + return false; + } + + if (jobs.empty()) { + Logger::error("Production type ", identifier, " lacks jobs ('employees')."); + return false; + } + + for (size_t i = 0; i < jobs.size(); i++) { + if (jobs[i].get_pop_type() == nullptr) { + Logger::error("Production type ", identifier, " has invalid pop type in employees[", i, "]."); + return false; + } + } + } + + const bool ret = production_types.add_item({ + identifier, owner, std::move(jobs), template_type, base_workforce_size, std::move(input_goods), *output_good, + base_output_quantity, std::move(bonuses), std::move(maintenance_requirements), is_coastal, is_farm, is_mine + }); + + if (ret && (template_type == RGO)) { + ProductionType const& production_type = production_types.get_items().back(); + ProductionType const*& current_rgo_pt = good_to_rgo_production_type[*output_good]; + if (current_rgo_pt == nullptr || (is_farm && !current_rgo_pt->is_farm())) { + // first rgo pt or farms are preferred (over mines) in V2 + current_rgo_pt = &production_type; + } + //else ignore, we already have an rgo pt + } + + if (rgo_owner_sprite <= 0 && ret && template_type == RGO && owner.has_value() && owner->get_pop_type() != nullptr) { + /* Set rgo owner sprite to that of the first RGO owner we find. */ + rgo_owner_sprite = owner->get_pop_type()->get_sprite(); + } + + return ret; +} + +bool ProductionTypeManager::load_production_types_file( + GoodDefinitionManager const& good_definition_manager, PopManager const& pop_manager, ovdl::v2script::Parser const& parser +) { + using namespace std::string_view_literals; + auto template_symbol = parser.find_intern("template"sv); + if (!template_symbol) { + Logger::error("template could not be interned."); + } + + size_t expected_types = 0; + + /* Pass #1: find and store template identifiers */ + ordered_set<std::string_view> templates; + ordered_map<std::string_view, std::string_view> template_target_map; + bool ret = expect_dictionary( + [this, &expected_types, &templates, &template_target_map, &template_symbol](std::string_view key, ast::NodeCPtr value) -> bool { + expected_types++; + + std::string_view template_id = ""; + bool found_template = false; + const bool ret = + expect_key(template_symbol, expect_identifier(assign_variable_callback(template_id)), &found_template)(value); + if (found_template) { + if (ret) { + templates.emplace(template_id); + template_target_map.emplace(key, template_id); + } else { + Logger::error("Failed get template identifier for ", key); + return false; + } + } + return true; + } + )(parser.get_file_node()); + + /* Pass #2: create and populate the template map */ + ordered_map<std::string_view, ast::NodeCPtr> template_node_map; + ret &= expect_dictionary( + [this, &expected_types, &templates, &template_node_map](std::string_view key, ast::NodeCPtr value) -> bool { + if (templates.contains(key)) { + template_node_map.emplace(key, value); + expected_types--; + } + return true; + } + )(parser.get_file_node()); + + + /* Pass #3: actually load production types */ + good_to_rgo_production_type.set_keys(&good_definition_manager.get_good_definitions()); + + reserve_more_production_types(expected_types); + ret &= expect_dictionary( + [this, &good_definition_manager, &pop_manager, &template_target_map, &template_node_map]( + std::string_view key, ast::NodeCPtr node) -> bool { + using enum ProductionType::template_type_t; + + if (template_node_map.contains(key)) { + return true; + } + + std::optional<Job> owner; + std::vector<Job> jobs; + ProductionType::template_type_t template_type { FACTORY }; + GoodDefinition const* output_good = nullptr; + Pop::pop_size_t base_workforce_size = 0; + GoodDefinition::good_definition_map_t input_goods, maintenance_requirements; + fixed_point_t base_output_quantity = 0; + std::vector<ProductionType::bonus_t> bonuses; + bool is_coastal = false, is_farm = false, is_mine = false; + + bool ret = true; + + static const string_map_t<ProductionType::template_type_t> template_type_map = { + { "factory", FACTORY }, { "rgo", RGO }, { "artisan", ARTISAN } + }; + + auto parse_node = expect_dictionary_keys( + "template", ZERO_OR_ONE, success_callback, /* Already parsed using expect_key in Pass #1 above. */ + "bonus", ZERO_OR_MORE, [&bonuses](ast::NodeCPtr bonus_node) -> bool { + using enum scope_type_t; + + ConditionScript trigger { STATE, NO_SCOPE, NO_SCOPE }; + fixed_point_t bonus_value {}; + + const bool ret = expect_dictionary_keys( + "trigger", ONE_EXACTLY, trigger.expect_script(), + "value", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(bonus_value)) + )(bonus_node); + + bonuses.emplace_back(std::move(trigger), bonus_value); + + return ret; + }, + "owner", ZERO_OR_ONE, _expect_job(good_definition_manager, pop_manager, move_variable_callback(owner)), + "employees", ZERO_OR_ONE, _expect_job_list(good_definition_manager, pop_manager, move_variable_callback(jobs)), + "type", ZERO_OR_ONE, + expect_identifier(expect_mapped_string(template_type_map, assign_variable_callback(template_type))), + "workforce", ZERO_OR_ONE, expect_uint(assign_variable_callback(base_workforce_size)), + "input_goods", ZERO_OR_ONE, + good_definition_manager.expect_good_definition_decimal_map(move_variable_callback(input_goods)), + "output_goods", ZERO_OR_ONE, + good_definition_manager.expect_good_definition_identifier(assign_variable_callback_pointer(output_good)), + "value", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(base_output_quantity)), + "efficiency", ZERO_OR_ONE, good_definition_manager.expect_good_definition_decimal_map( + move_variable_callback(maintenance_requirements) + ), + "is_coastal", ZERO_OR_ONE, expect_bool(assign_variable_callback(is_coastal)), + "farm", ZERO_OR_ONE, expect_bool(assign_variable_callback(is_farm)), + "mine", ZERO_OR_ONE, expect_bool(assign_variable_callback(is_mine)) + ); + + /* Check if this ProductionType has a template, and if so parse it. */ + { + const typename decltype(template_target_map)::const_iterator target_it = template_target_map.find(key); + if (target_it != template_target_map.end()) { + const std::string_view template_id = target_it->second; + const typename decltype(template_node_map)::const_iterator node_it = template_node_map.find(template_id); + if (node_it != template_node_map.end()) { + ret &= parse_node(node_it->second); + } else { + Logger::error("Missing template ", template_id, " for production type ", key, "!"); + ret = false; + } + } + } + + /* Parse the ProductionType's own entries, over those of its template if necessary. */ + ret &= parse_node(node); + + ret &= add_production_type( + key, owner, std::move(jobs), template_type, base_workforce_size, std::move(input_goods), output_good, + base_output_quantity, std::move(bonuses), std::move(maintenance_requirements), is_coastal, is_farm, is_mine + ); + + return ret; + } + )(parser.get_file_node()); + + production_types.lock(); + + if (rgo_owner_sprite <= 0) { + Logger::error("No RGO owner pop type sprite found!"); + ret = false; + } + + return ret; +} + +bool ProductionTypeManager::parse_scripts(DefinitionManager const& definition_manager) { + bool ret = true; + for (ProductionType& production_type : production_types.get_items()) { + ret &= production_type.parse_scripts(definition_manager); + } + return ret; +} diff --git a/src/openvic-simulation/economy/production/ProductionType.hpp b/src/openvic-simulation/economy/production/ProductionType.hpp new file mode 100644 index 0000000..f9b1778 --- /dev/null +++ b/src/openvic-simulation/economy/production/ProductionType.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include <openvic-dataloader/v2script/Parser.hpp> + +#include "openvic-simulation/economy/GoodDefinition.hpp" +#include "openvic-simulation/pop/Pop.hpp" +#include "openvic-simulation/scripts/ConditionScript.hpp" +#include "openvic-simulation/types/IdentifierRegistry.hpp" +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" + +namespace OpenVic { + struct ProductionTypeManager; + + struct Job { + friend struct ProductionTypeManager; + + enum struct effect_t { INPUT, OUTPUT, THROUGHPUT }; + + private: + PopType const* PROPERTY(pop_type); + effect_t PROPERTY(effect_type); + fixed_point_t PROPERTY(effect_multiplier); + fixed_point_t PROPERTY(amount); + + Job( + PopType const* new_pop_type, + effect_t new_effect_type, + fixed_point_t new_effect_multiplier, + fixed_point_t new_amount + ); + + public: + Job() = default; + }; + + struct ProductionType : HasIdentifier { + friend struct ProductionTypeManager; + + enum struct template_type_t { FACTORY, RGO, ARTISAN }; + + using bonus_t = std::pair<ConditionScript, fixed_point_t>; + + private: + const std::optional<Job> PROPERTY(owner); + std::vector<Job> PROPERTY(jobs); + const template_type_t PROPERTY(template_type); + const Pop::pop_size_t PROPERTY(base_workforce_size); + + GoodDefinition::good_definition_map_t PROPERTY(input_goods); + GoodDefinition const& PROPERTY(output_good); + const fixed_point_t PROPERTY(base_output_quantity); + std::vector<bonus_t> PROPERTY(bonuses); + + GoodDefinition::good_definition_map_t PROPERTY(maintenance_requirements); + const bool PROPERTY_CUSTOM_PREFIX(coastal, is); + + const bool PROPERTY_CUSTOM_PREFIX(farm, is); + const bool PROPERTY_CUSTOM_PREFIX(mine, is); + + ProductionType( + const std::string_view new_identifier, + const std::optional<Job> new_owner, + std::vector<Job>&& new_jobs, + const template_type_t new_template_type, + const Pop::pop_size_t new_base_workforce_size, + GoodDefinition::good_definition_map_t&& new_input_goods, + GoodDefinition const& new_output_good, + const fixed_point_t new_base_output_quantity, + std::vector<bonus_t>&& new_bonuses, + GoodDefinition::good_definition_map_t&& new_maintenance_requirements, + const bool new_is_coastal, + const bool new_is_farm, + const bool new_is_mine + ); + + bool parse_scripts(DefinitionManager const& definition_manager); + + public: + ProductionType(ProductionType&&) = default; + }; + + struct ProductionTypeManager { + private: + IdentifierRegistry<ProductionType> IDENTIFIER_REGISTRY(production_type); + PopType::sprite_t PROPERTY(rgo_owner_sprite); + IndexedMap<GoodDefinition, ProductionType const*> PROPERTY(good_to_rgo_production_type); + + NodeTools::node_callback_t _expect_job( + GoodDefinitionManager const& good_definition_manager, PopManager const& pop_manager, + NodeTools::callback_t<Job&&> callback + ); + NodeTools::node_callback_t _expect_job_list( + GoodDefinitionManager const& good_definition_manager, PopManager const& pop_manager, + NodeTools::callback_t<std::vector<Job>&&> callback + ); + + public: + ProductionTypeManager(); + + bool add_production_type( + const std::string_view identifier, + std::optional<Job> owner, + std::vector<Job>&& jobs, + const ProductionType::template_type_t template_type, + const Pop::pop_size_t base_workforce_size, + GoodDefinition::good_definition_map_t&& input_goods, + GoodDefinition const* const output_good, + const fixed_point_t base_output_quantity, + std::vector<ProductionType::bonus_t>&& bonuses, + GoodDefinition::good_definition_map_t&& maintenance_requirements, + const bool is_coastal, + const bool is_farm, + const bool is_mine + ); + + bool load_production_types_file( + GoodDefinitionManager const& good_definition_manager, PopManager const& pop_manager, ovdl::v2script::Parser const& parser + ); + + bool parse_scripts(DefinitionManager const& definition_manager); + }; +} diff --git a/src/openvic-simulation/economy/production/ResourceGatheringOperation.cpp b/src/openvic-simulation/economy/production/ResourceGatheringOperation.cpp new file mode 100644 index 0000000..70cb64d --- /dev/null +++ b/src/openvic-simulation/economy/production/ResourceGatheringOperation.cpp @@ -0,0 +1,351 @@ +#include "ResourceGatheringOperation.hpp" + +#include <vector> + +#include "openvic-simulation/economy/production/Employee.hpp" +#include "openvic-simulation/economy/production/ProductionType.hpp" +#include "openvic-simulation/map/ProvinceInstance.hpp" +#include "openvic-simulation/map/State.hpp" +#include "openvic-simulation/modifier/ModifierEffectCache.hpp" +#include "openvic-simulation/pop/Pop.hpp" +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" +#include "openvic-simulation/utility/Logger.hpp" + +using namespace OpenVic; + +ResourceGatheringOperation::ResourceGatheringOperation( + ProductionType const* new_production_type_nullable, + fixed_point_t new_size_multiplier, + fixed_point_t new_revenue_yesterday, + fixed_point_t new_output_quantity_yesterday, + fixed_point_t new_unsold_quantity_yesterday, + std::vector<Employee>&& new_employees, + decltype(employee_count_per_type_cache)::keys_t const& pop_type_keys +) : production_type_nullable { new_production_type_nullable }, + revenue_yesterday { new_revenue_yesterday }, + output_quantity_yesterday { new_output_quantity_yesterday }, + unsold_quantity_yesterday { new_unsold_quantity_yesterday }, + size_multiplier { new_size_multiplier }, + employees { std::move(new_employees) }, + max_employee_count_cache { 0 }, + total_employees_count_cache { 0 }, + total_paid_employees_count_cache { 0 }, + total_owner_income_cache { }, + total_employee_income_cache { }, + employee_count_per_type_cache { &pop_type_keys } +{ } + +ResourceGatheringOperation::ResourceGatheringOperation(decltype(employee_count_per_type_cache)::keys_t const& pop_type_keys) : ResourceGatheringOperation { + nullptr, fixed_point_t::_0(), + fixed_point_t::_0(), fixed_point_t::_0(), + fixed_point_t::_0(), {}, pop_type_keys +} {} + +void ResourceGatheringOperation::initialise_for_new_game(ProvinceInstance& location, ModifierEffectCache const& modifier_effect_cache) { + if (production_type_nullable == nullptr) { + output_quantity_yesterday = 0; + revenue_yesterday = 0; + return; + } + + ProductionType const& production_type = *production_type_nullable; + const fixed_point_t size_modifier = calculate_size_modifier(location, modifier_effect_cache); + const Pop::pop_size_t total_worker_count_in_province = update_size_and_return_total_worker_count(location, modifier_effect_cache, size_modifier); + hire(location, total_worker_count_in_province); + Pop::pop_size_t total_owner_count_in_state_cache = 0; + std::vector<Pop*> owner_pops_cache {}; + output_quantity_yesterday = produce(location, owner_pops_cache, total_owner_count_in_state_cache, modifier_effect_cache, size_modifier); + revenue_yesterday = output_quantity_yesterday * production_type.get_output_good().get_base_price(); //TODO sell on market + pay_employees(location, revenue_yesterday, total_worker_count_in_province, owner_pops_cache, total_owner_count_in_state_cache); +} + +Pop::pop_size_t ResourceGatheringOperation::update_size_and_return_total_worker_count( + ProvinceInstance& location, + ModifierEffectCache const& modifier_effect_cache, + const fixed_point_t size_modifier +) { + if (production_type_nullable == nullptr) { + size_multiplier = fixed_point_t::_0(); + max_employee_count_cache = fixed_point_t::_0(); + return fixed_point_t::_0(); + } + + Pop::pop_size_t total_worker_count_in_province = 0; //not counting equivalents + ProductionType const& production_type = *production_type_nullable; + std::vector<Job> const& jobs = production_type.get_jobs(); + //can't use pop_type_distribution as it is not filled correctly yet (possibly due to equivalent pop type conversion) + for (Pop const& pop : location.get_pops()){ + PopType const* pop_type = pop.get_type(); + for(Job const& job : jobs) { + if (job.get_pop_type() == pop_type) { + total_worker_count_in_province += pop.get_size(); + break; + } + } + } + + fixed_point_t base_size_modifier = fixed_point_t::_1(); + if (production_type.is_farm()) { + base_size_modifier += location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_farm_rgo_size_local()); + } + if (production_type.is_mine()) { + base_size_modifier += location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_mine_rgo_size_local()); + } + + const fixed_point_t base_workforce_size = production_type.get_base_workforce_size(); + if (base_size_modifier == fixed_point_t::_0()) { + size_multiplier = 0; + } else { + size_multiplier = ((total_worker_count_in_province / (base_size_modifier * base_workforce_size)).ceil() * fixed_point_t::_1_50()).floor(); + } + max_employee_count_cache = (size_modifier * size_multiplier * base_workforce_size).floor(); + return total_worker_count_in_province; +} + +fixed_point_t ResourceGatheringOperation::calculate_size_modifier(ProvinceInstance const& location, ModifierEffectCache const& modifier_effect_cache) const { + if (production_type_nullable == nullptr) { + return fixed_point_t::_1(); + } + + ProductionType const& production_type = *production_type_nullable; + + fixed_point_t size_modifier = fixed_point_t::_1(); + if (production_type.is_farm()) { + size_modifier += location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_farm_rgo_size_global()) + + location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_farm_rgo_size_local()); + } + if (production_type.is_mine()) { + size_modifier += location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_mine_rgo_size_global()) + + location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_mine_rgo_size_local()); + } + auto const& good_effects = modifier_effect_cache.get_good_effects()[production_type.get_output_good()]; + size_modifier += location.get_modifier_effect_value_nullcheck(good_effects.get_rgo_size()); + return size_modifier > fixed_point_t::_0() ? size_modifier : fixed_point_t::_0(); +} + +void ResourceGatheringOperation::hire(ProvinceInstance& location, Pop::pop_size_t available_worker_count) { + total_employees_count_cache = 0; + total_paid_employees_count_cache=0; + if (production_type_nullable == nullptr) { + employees.clear(); + employee_count_per_type_cache.fill(fixed_point_t::_0()); + return; + } + + ProductionType const& production_type = *production_type_nullable; + if (max_employee_count_cache <= 0) { return; } + if (available_worker_count <= 0) { return; } + + fixed_point_t proportion_to_hire; + if (max_employee_count_cache >= available_worker_count) { + //hire everyone + proportion_to_hire = fixed_point_t::_1(); + } else { + //hire all pops proportionally + const fixed_point_t max_worker_count_real = max_employee_count_cache, available_worker_count_real = available_worker_count; + proportion_to_hire = max_worker_count_real / available_worker_count_real; + } + + std::vector<Job> const& jobs = production_type.get_jobs(); + for (Pop& pop : location.get_mutable_pops()){ + PopType const& pop_type = *pop.get_type(); + for(Job const& job : jobs) { + if (job.get_pop_type() == &pop_type) { + const Pop::pop_size_t pop_size_to_hire = static_cast<Pop::pop_size_t>((proportion_to_hire * pop.get_size()).floor()); + employee_count_per_type_cache[pop_type] += pop_size_to_hire; + employees.emplace_back(pop, pop_size_to_hire); + total_employees_count_cache += pop_size_to_hire; + if (!pop_type.get_is_slave()) { + total_paid_employees_count_cache += pop_size_to_hire; + } + break; + } + } + } +} + +fixed_point_t ResourceGatheringOperation::produce( + ProvinceInstance& location, + std::vector<Pop*>& owner_pops_cache, + Pop::pop_size_t& total_owner_count_in_state_cache, + ModifierEffectCache const& modifier_effect_cache, + const fixed_point_t size_modifier +) { + if (size_modifier == fixed_point_t::_0()){ + return fixed_point_t::_0(); + } + + total_owner_count_in_state_cache = 0; + owner_pops_cache = {}; + if (production_type_nullable == nullptr || max_employee_count_cache <= 0) { + return fixed_point_t::_0(); + } + + ProductionType const& production_type = *production_type_nullable; + fixed_point_t throughput_multiplier = fixed_point_t::_1(); + fixed_point_t output_multilpier = fixed_point_t::_1(); + + std::optional<Job> const& owner = production_type.get_owner(); + if (owner.has_value()) { + Job const& owner_job = owner.value(); + PopType const* owner_job_pop_type_nullable = owner_job.get_pop_type(); + if (owner_job_pop_type_nullable == nullptr) { + Logger::error("Owner job for ", production_type.get_identifier(), " has nullptr as pop_type."); + return fixed_point_t::_0(); + } + PopType const& owner_pop_type = *owner_job_pop_type_nullable; + State const* state_nullable = location.get_state(); + if (state_nullable == nullptr) { + Logger::error("Province ", location.get_identifier(), " has no state."); + return fixed_point_t::_0(); + } + State const& state = *state_nullable; + Pop::pop_size_t state_population = 0; //state.get_total_population() is not filled yet + std::vector<ProvinceInstance*> const& provinces_in_state = state.get_provinces(); + for (ProvinceInstance* const province_nullable : provinces_in_state) { + if (province_nullable == nullptr) { + Logger::error("State ", state.get_identifier(), " has nullptr in provinces."); + return fixed_point_t::_0(); + } + ProvinceInstance& province = *province_nullable; + for (Pop& pop : province.get_mutable_pops()){ + state_population += pop.get_size(); + if (&owner_pop_type == pop.get_type()) { + owner_pops_cache.push_back(&pop); + total_owner_count_in_state_cache += pop.get_size(); + } + } + } + + if (total_owner_count_in_state_cache > 0) { + switch (owner_job.get_effect_type()) { + case Job::effect_t::OUTPUT: + output_multilpier += owner_job.get_effect_multiplier() * total_owner_count_in_state_cache / state_population; + break; + case Job::effect_t::THROUGHPUT: + throughput_multiplier += owner_job.get_effect_multiplier() * total_owner_count_in_state_cache / state_population; + break; + default: + Logger::error("Invalid job effect in RGO ",production_type.get_identifier()); + break; + } + } + } + + throughput_multiplier += location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_rgo_throughput()) + +location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_local_rgo_throughput()); + output_multilpier += location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_rgo_output()) + +location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_local_rgo_output()); + + if (production_type.is_farm()) { + throughput_multiplier += location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_farm_rgo_throughput_global()); + output_multilpier += location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_farm_rgo_output_global()) + + location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_farm_rgo_output_local()); + } + if (production_type.is_mine()) { + throughput_multiplier += location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_mine_rgo_throughput_global()); + output_multilpier += location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_mine_rgo_output_global()) + + location.get_modifier_effect_value_nullcheck(modifier_effect_cache.get_mine_rgo_output_local()); + } + auto const& good_effects = modifier_effect_cache.get_good_effects()[production_type.get_output_good()]; + throughput_multiplier += location.get_modifier_effect_value_nullcheck(good_effects.get_rgo_goods_throughput()); + output_multilpier += location.get_modifier_effect_value_nullcheck(good_effects.get_rgo_goods_output()); + + fixed_point_t throughput_from_workers = fixed_point_t::_0(); + fixed_point_t output_from_workers = fixed_point_t::_1(); + for (PopType const& pop_type : *employee_count_per_type_cache.get_keys()) { + const Pop::pop_size_t employees_of_type = employee_count_per_type_cache[pop_type]; + + for(Job const& job : production_type.get_jobs()) { + if (job.get_pop_type() != &pop_type) { + continue; + } + + fixed_point_t const effect_multiplier = job.get_effect_multiplier(); + fixed_point_t relative_to_workforce = fixed_point_t::parse(employees_of_type) / fixed_point_t::parse(max_employee_count_cache); + fixed_point_t const amount = job.get_amount(); + if (effect_multiplier != fixed_point_t::_1() && relative_to_workforce > amount) { + relative_to_workforce = amount; + } + switch (job.get_effect_type()) { + case Job::effect_t::OUTPUT: + output_from_workers += effect_multiplier * relative_to_workforce; + break; + case Job::effect_t::THROUGHPUT: + throughput_from_workers += effect_multiplier * relative_to_workforce; + break; + default: + Logger::error("Invalid job effect in RGO ",production_type.get_identifier()); + break; + } + } + } + + //if province is overseas multiply by (1 + overseas penalty) + + return production_type.get_base_output_quantity() + * size_modifier * size_multiplier + * throughput_multiplier * throughput_from_workers + * output_multilpier * output_from_workers; +} + +void ResourceGatheringOperation::pay_employees( + ProvinceInstance& location, + const fixed_point_t revenue, + const Pop::pop_size_t total_worker_count_in_province, + std::vector<Pop*>& owner_pops_cache, + const Pop::pop_size_t total_owner_count_in_state_cache +) { + total_owner_income_cache = 0; + total_employee_income_cache = 0; + if (production_type_nullable == nullptr || revenue <= 0 || total_worker_count_in_province <= 0) { + if (revenue < 0) { Logger::error("Negative revenue for province ", location.get_identifier()); } + if (total_worker_count_in_province < 0) { Logger::error("Negative total worker count for province ", location.get_identifier()); } + return; + } + + ProductionType const& production_type = *production_type_nullable; + + fixed_point_t revenue_left = revenue; + if (total_owner_count_in_state_cache > 0) { + Job const& owner_job = production_type.get_owner().value(); + PopType const* owner_job_pop_type_nullable = owner_job.get_pop_type(); + + fixed_point_t owner_share = (fixed_point_t::_2() * total_owner_count_in_state_cache / total_worker_count_in_province); + constexpr fixed_point_t upper_limit = fixed_point_t::_0_50(); + if (owner_share > upper_limit) { + owner_share = upper_limit; + } + + for(Pop* owner_pop_nullable : owner_pops_cache) { + Pop& owner_pop = *owner_pop_nullable; + const fixed_point_t income_for_this_pop = revenue_left * owner_share * owner_pop.get_size() / total_owner_count_in_state_cache; + owner_pop.add_rgo_owner_income(income_for_this_pop); + total_owner_income_cache += income_for_this_pop; + } + revenue_left *= (fixed_point_t::_1() - owner_share); + } + + if (total_paid_employees_count_cache > 0) { + for (Employee& employee : employees) { + Pop& employee_pop = employee.pop; + PopType const* employee_pop_type_nullable = employee_pop.get_type(); + if (employee_pop_type_nullable == nullptr) { + Logger::error("employee has nullptr pop_type."); + return; + } + PopType const& employee_pop_type = *employee_pop_type_nullable; + if (employee_pop_type.get_is_slave()) { + continue; + } + + const Pop::pop_size_t employee_size = employee.get_size(); + const fixed_point_t income_for_this_pop = revenue_left * employee_size / total_paid_employees_count_cache; + employee_pop.add_rgo_worker_income(income_for_this_pop); + total_employee_income_cache += income_for_this_pop; + } + } else { + //scenario slaves only + //Money is removed from system in Victoria 2. + } +}
\ No newline at end of file diff --git a/src/openvic-simulation/economy/production/ResourceGatheringOperation.hpp b/src/openvic-simulation/economy/production/ResourceGatheringOperation.hpp new file mode 100644 index 0000000..a15e87d --- /dev/null +++ b/src/openvic-simulation/economy/production/ResourceGatheringOperation.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include "openvic-simulation/economy/production/Employee.hpp" +#include "openvic-simulation/economy/production/ProductionType.hpp" +#include "openvic-simulation/modifier/ModifierEffectCache.hpp" +#include "openvic-simulation/pop/Pop.hpp" +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" +#include "openvic-simulation/utility/Getters.hpp" + +namespace OpenVic { + struct ResourceGatheringOperation { + private: + ProductionType const* PROPERTY_RW(production_type_nullable); + fixed_point_t PROPERTY(revenue_yesterday); + fixed_point_t PROPERTY(output_quantity_yesterday); + fixed_point_t PROPERTY(unsold_quantity_yesterday); + fixed_point_t PROPERTY_RW(size_multiplier); + std::vector<Employee> PROPERTY(employees); + Pop::pop_size_t PROPERTY(max_employee_count_cache); + Pop::pop_size_t PROPERTY(total_employees_count_cache); + Pop::pop_size_t PROPERTY(total_paid_employees_count_cache); + fixed_point_t PROPERTY(total_owner_income_cache); + fixed_point_t PROPERTY(total_employee_income_cache); + IndexedMap<PopType, Pop::pop_size_t> PROPERTY(employee_count_per_type_cache); + + Pop::pop_size_t update_size_and_return_total_worker_count( + ProvinceInstance& location, + ModifierEffectCache const& modifier_effect_cache, + const fixed_point_t size_modifier + ); + fixed_point_t calculate_size_modifier(ProvinceInstance const& location, ModifierEffectCache const& modifier_effect_cache) const; + void hire(ProvinceInstance& location, const Pop::pop_size_t available_worker_count); + fixed_point_t produce( + ProvinceInstance& location, + std::vector<Pop*>& owner_pops_cache, + Pop::pop_size_t& total_owner_count_in_state_cache, + ModifierEffectCache const& modifier_effect_cache, + const fixed_point_t size_modifier + ); + void pay_employees( + ProvinceInstance& location, + const fixed_point_t revenue, + const Pop::pop_size_t total_worker_count_in_province, + std::vector<Pop*>& owner_pops_cache, + const Pop::pop_size_t total_owner_count_in_state_cache + ); + + public: + ResourceGatheringOperation( + ProductionType const* new_production_type_nullable, + fixed_point_t new_size_multiplier, + fixed_point_t new_revenue_yesterday, + fixed_point_t new_output_quantity_yesterday, + fixed_point_t new_unsold_quantity_yesterday, + std::vector<Employee>&& new_employees, + decltype(employee_count_per_type_cache)::keys_t const& pop_type_keys + ); + ResourceGatheringOperation(decltype(employee_count_per_type_cache)::keys_t const& pop_type_keys); + constexpr bool is_valid() const { + return production_type_nullable != nullptr; + } + void initialise_for_new_game(ProvinceInstance& location, ModifierEffectCache const& modifier_effect_cache); + }; +} |