diff --git a/EU4ToVic3/Data_Files/configurables/economy/ownership.txt b/EU4ToVic3/Data_Files/configurables/economy/ownership.txt index 96967a305..cc82ac7e1 100644 --- a/EU4ToVic3/Data_Files/configurables/economy/ownership.txt +++ b/EU4ToVic3/Data_Files/configurables/economy/ownership.txt @@ -1,8 +1,8 @@ # Decide how ownership will be split among Vic 3 buildings. # Keywords are 'local', 'national', 'national_service', 'building_manor_house', 'building_financial_district' -# colonial = 0.2 ### This shifts 20% of the aristocracy too be owned by an overlord if applicable; default is 0% +# colonial = 0.2 ### This shifts 20% of the aristocracy to be owned by an overlord if applicable; default is 0% # financial = 0.1 ### This shifts 10% of the capital to the nation's capital; default is 0% -# recognized = yes ### This prevents capitalists from owning these buildings if a nation is unrecognized +# recognized = yes ### This prevents an ownership building type from owning these buildings if a nation is unrecognized agriculture = { ownership = { diff --git a/EU4ToVic3/Data_Files/configurables/economy/production_method_rules.txt b/EU4ToVic3/Data_Files/configurables/economy/production_method_rules.txt index 5dc74a3c2..196afcee1 100644 --- a/EU4ToVic3/Data_Files/configurables/economy/production_method_rules.txt +++ b/EU4ToVic3/Data_Files/configurables/economy/production_method_rules.txt @@ -1,83 +1,90 @@ # Linked buildings will walk towards the PMs specified until the PM is reached. or the next step is invalid due to tech. # The percent is the percent of building levels that will try to move off of default to adopt the defined PM. It is assumed to be 1(100%) unless specified. +# Ownership PMs should be marked with law_bound = yes # building_government_administration # SPECIAL CASE government administration links will be ignored even if defined. + ##### Development building_port = { - pm = { name = pm_basic_port } + pm = { name = pm_basic_port } } building_barracks = { - pm = { name = pm_general_training } + pm = { name = pm_general_training } } building_naval_base = { - pm = { name = pm_power_of_the_purse } + pm = { name = pm_power_of_the_purse } } ##### Urban +building_urban_center = { + pm = { name = pm_market_squares } + pm = { name = pm_gas_streetlights } + pm = { name = pm_free_urban_clergy law_bound = yes } +} building_tooling_workshops = { - pm = { name = pm_pig_iron } + pm = { name = pm_pig_iron } } building_glassworks = { - pm = { name = pm_leaded_glass } + pm = { name = pm_leaded_glass } } building_paper_mills = { - pm = { name = pm_sulfite_pulping percent = 0.4 } + pm = { name = pm_sulfite_pulping percent = 0.4 } } building_furniture_manufacturies = { - pm = { name = pm_lathe } - pm = { name = pm_luxury_furniture percent = 0.5 } + pm = { name = pm_lathe } + pm = { name = pm_luxury_furniture percent = 0.5 } } building_textile_mills = { - pm = { name = pm_dye_workshops percent = 0.05 } - pm = { name = pm_craftsman_sewing percent = 0.1 } + pm = { name = pm_dye_workshops percent = 0.05 } + pm = { name = pm_craftsman_sewing percent = 0.1 } } building_shipyards = { - pm = { name = pm_complex_shipbuilding } + pm = { name = pm_complex_shipbuilding } } building_military_shipyards = { - pm = { name = pm_military_shipbuilding_wooden } + pm = { name = pm_military_shipbuilding_wooden } } building_arms_industry = { - pm = { name = pm_rifles } + pm = { name = pm_rifles } } building_artillery_foundries = { - pm = { name = pm_cannons } + pm = { name = pm_cannons } } building_food_industry = { - pm = { name = pm_pot_stills } + pm = { name = pm_pot_stills } } building_chemical_plants = { - pm = { name = pm_nitrogen_fixation } + pm = { name = pm_nitrogen_fixation } } ##### Rural building_logging_camp = { - pm = { name = pm_saw_mills } - pm = { name = pm_hardwood percent = 0.4 } + pm = { name = pm_saw_mills } + pm = { name = pm_hardwood percent = 0.4 } } ### Farms building_rye_farm = { - pm = { name = pm_potatoes percent = 0.15 } + pm = { name = pm_potatoes percent = 0.15 } } building_wheat_farm = { - pm = { name = pm_citrus_orchards percent = 0.2 } + pm = { name = pm_citrus_orchards percent = 0.2 } } building_millet_farm = { - pm = { name = pm_soil_enriching_farming percent = 0.2 } + pm = { name = pm_soil_enriching_farming percent = 0.2 } } building_maize_farm = { - pm = { name = pm_citrus_orchards percent = 0.2 } + pm = { name = pm_citrus_orchards percent = 0.2 } } ### Mines building_coal_mine = { - pm = { name = pm_atmospheric_engine_pump_building_coal_mine } + pm = { name = pm_atmospheric_engine_pump_building_coal_mine } } building_iron_mine = { - pm = { name = pm_atmospheric_engine_pump_building_iron_mine } + pm = { name = pm_atmospheric_engine_pump_building_iron_mine } } building_gold_mine = { - pm = { name = pm_atmospheric_engine_pump_building_gold_mine } + pm = { name = pm_atmospheric_engine_pump_building_gold_mine } } building_sulfur_mine = { - pm = { name = pm_atmospheric_engine_pump_building_sulfur_mine } + pm = { name = pm_atmospheric_engine_pump_building_sulfur_mine } } diff --git a/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/PMRule.h b/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/PMRule.h index 9c067ab16..1677f10b0 100644 --- a/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/PMRule.h +++ b/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/PMRule.h @@ -8,6 +8,7 @@ struct PMRule { std::string pm; double percent = 1.0; + bool lawBound = false; bool operator==(const PMRule&) const = default; }; diff --git a/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/ProductionMethodEntry.cpp b/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/ProductionMethodEntry.cpp index 2ca0d9bc0..c0549e495 100644 --- a/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/ProductionMethodEntry.cpp +++ b/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/ProductionMethodEntry.cpp @@ -17,5 +17,8 @@ void mappers::ProductionMethodEntry::registerKeys() registerKeyword("percent", [this](std::istream& theStream) { rule.percent = commonItems::getDouble(theStream); }); + registerKeyword("law_bound", [this](std::istream& theStream) { + rule.lawBound = commonItems::getString(theStream) == "yes"; + }); registerRegex(commonItems::catchallRegex, commonItems::ignoreItem); } diff --git a/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/ProductionMethodMapper.cpp b/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/ProductionMethodMapper.cpp index 094b08e41..fdb7b42f7 100644 --- a/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/ProductionMethodMapper.cpp +++ b/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/ProductionMethodMapper.cpp @@ -153,6 +153,9 @@ std::pair mappers::ProductionMethodMapper::pickPM(cons const std::map& PMGroups) { // NOTE(Gawquon): This works for most PMs, but is not guaranteed to work for ownership PMs. + // Added a workaround for ownership PMs in Econ 2.0, but a unifying theory of PMs would be nice. + // Workaround will break for ownership PMs which require tech, should be none at the start. + // This is just a basic version that will support every use-case we currently care about. for (const auto& PMGroup: PMGroups | std::views::values) { @@ -191,6 +194,110 @@ std::pair mappers::ProductionMethodMapper::pickPM(cons return {"", ""}; } +int mappers::ProductionMethodMapper::walkPMsTechbound(const std::vector& groupPMs, + const V3::Country& country, + const std::string& targetName, + const std::map& PMs) +{ + // Validate every PM in group + for (const auto& PM: groupPMs) + { + if (!PMs.contains(PM)) + { + Log(LogLevel::Error) << "Unknown PM: " << PM << "."; + return 0; + } + } + + // Walk the group, we're looking for the most advanced PM allowed by tech, up to our target PM. + int pick = 0; + for (const auto& PM: groupPMs) + { + if (!country.hasAnyOfTech(PMs.at(PM).getUnlockingTechs())) + return std::max(pick - 1, 0); + if (PM == targetName) + return pick; + ++pick; + } + return std::max(pick - 1, 0); +} + +// No explicit target, finds the first PM allowed by law. +int mappers::ProductionMethodMapper::walkPMsLawbound(const std::vector& groupPMs, + const V3::Country& country, + const std::map& PMs) +{ + int pick = 0; + + for (const auto& PM: groupPMs) + { + const auto& thePM = PMs.at(PM); + const bool hasUnlockingLaws = country.hasAnyOfLawUnlocking(thePM.getUnlockingLaws()); + const bool hasBlockingLaws = country.hasAnyOfLawBlocking(thePM.getBlockingLaws()); + + if (hasUnlockingLaws && !hasBlockingLaws) + return std::max(pick, 0); + ++pick; + } + return 0; // Nothing is legal, just go with default PM. +} + +std::map> mappers::ProductionMethodMapper::estimatePMs(const V3::Country& country, + const std::map& PMs, + const std::map& PMGroups, + const std::map& buildings) const +{ + // PMs are not necessarily unique to a building, but PMGroups are. + // Additionally, no PM will appear twice in the same building. + std::map> expectedPMs; // PMGroup -> (expected PM index, %) + + // Configuration based PMs, checked against available tech. + for (const auto& [buildingName, building]: buildings) + { + if (const auto& rulesIter = buildingToRules.find(buildingName); rulesIter != buildingToRules.end()) + { + const auto& rules = rulesIter->second; + for (const auto& PMGroup: building.getPMGroups()) + { + bool flag = false; + for (int ruleIndex = 0; ruleIndex < rules.size() && !flag; ruleIndex++) + { + const auto& groupPMs = PMGroups.at(PMGroup).getPMs(); + for (const auto& PM: groupPMs) + { + if (const auto& rule = rules[ruleIndex]; rule.pm == PM) + { + if (rule.lawBound) + expectedPMs.emplace(PMGroup, std::make_tuple(walkPMsLawbound(groupPMs, country, PMs), rule.percent)); + else + expectedPMs.emplace(PMGroup, std::make_tuple(walkPMsTechbound(groupPMs, country, PM, PMs), rule.percent)); + + flag = true; + break; + } + } + } + if (!flag) + expectedPMs.emplace(PMGroup, std::tuple{0, 1.0}); + } + } + } + + // Law based PMs (Subsistence & Clergy) + for (const auto& [buildingName, building]: buildings) + { + if (buildingName.find("subsistence") == std::string::npos) + continue; + + for (const auto& PMGroup: building.getPMGroups()) + { + const auto& groupPMs = PMGroups.at(PMGroup).getPMs(); + expectedPMs.emplace(PMGroup, std::make_tuple(walkPMsLawbound(groupPMs, country, PMs), 1)); + } + } + return expectedPMs; +} + ////////////////////////////////////////// Subset-sum fxns std::vector> mappers::ProductionMethodMapper::subSetSum(const std::vector>& subSet, int targetVal) { diff --git a/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/ProductionMethodMapper.h b/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/ProductionMethodMapper.h index 7ae82d5d4..6b6755c62 100644 --- a/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/ProductionMethodMapper.h +++ b/EU4ToVic3/Source/Mappers/BuildingMapper/ProductionMethodMapper/ProductionMethodMapper.h @@ -12,6 +12,9 @@ class Country; } // namespace V3 namespace mappers { +using PmIndex = int; +using PmFraction = double; +using PmGroup = std::string; class ProductionMethodMapper: commonItems::parser { public: @@ -23,6 +26,12 @@ class ProductionMethodMapper: commonItems::parser [[nodiscard]] const auto& getRules() const { return buildingToRules; } + // demandEstimates helper + [[nodiscard]] std::map> estimatePMs(const V3::Country& country, + const std::map& PMs, + const std::map& PMGroups, + const std::map& buildings) const; + private: void registerKeys(); @@ -36,6 +45,13 @@ class ProductionMethodMapper: commonItems::parser const std::set& buildingPMGroups, const std::map& PMs, const std::map& PMGroups); + [[nodiscard]] static int walkPMsTechbound(const std::vector& groupPMs, + const V3::Country& country, + const std::string& targetName, + const std::map& PMs); + [[nodiscard]] static int walkPMsLawbound(const std::vector& groupPMs, + const V3::Country& country, + const std::map& PMs); // Subset-sum static std::vector> subSetSum(const std::vector>& subSet, int targetVal); diff --git a/EU4ToVic3/Source/V3World/ClayManager/State/State.cpp b/EU4ToVic3/Source/V3World/ClayManager/State/State.cpp index ab49eb6be..11abb8a31 100644 --- a/EU4ToVic3/Source/V3World/ClayManager/State/State.cpp +++ b/EU4ToVic3/Source/V3World/ClayManager/State/State.cpp @@ -18,6 +18,9 @@ void V3::State::loadState(std::istream& theStream) void V3::State::registerKeys() { + registerKeyword("subsistence_building", [this](std::istream& theStream) { + subsistenceBuilding = commonItems::getString(theStream); + }); registerKeyword("provinces", [this](std::istream& theStream) { for (const auto& provinceName: commonItems::getStrings(theStream)) { diff --git a/EU4ToVic3/Source/V3World/ClayManager/State/State.h b/EU4ToVic3/Source/V3World/ClayManager/State/State.h index 384874737..491713469 100644 --- a/EU4ToVic3/Source/V3World/ClayManager/State/State.h +++ b/EU4ToVic3/Source/V3World/ClayManager/State/State.h @@ -40,6 +40,7 @@ class State: commonItems::parser [[nodiscard]] const auto& getTraits() const { return traits; } [[nodiscard]] const auto& getCappedResources() const { return cappedResources; } [[nodiscard]] const auto& getArableResources() const { return arableResources; } + [[nodiscard]] const auto& getSubsistenceBuilding() const { return subsistenceBuilding; } [[nodiscard]] ProvinceMap getUnassignedProvinces() const; [[nodiscard]] bool hasUnassignedProvinces() const; @@ -65,6 +66,7 @@ class State: commonItems::parser std::vector traits; // state_trait_natural_harbors std::map cappedResources; // RGO and arable land potential std::vector arableResources; // Which buildings can be built on arable land + std::string subsistenceBuilding; // building_subsistence_fishing_villages std::set homelands; }; } // namespace V3 diff --git a/EU4ToVic3/Source/V3World/ClayManager/State/StateModifier.cpp b/EU4ToVic3/Source/V3World/ClayManager/State/StateModifier.cpp index 0d8562e55..0841e88a5 100644 --- a/EU4ToVic3/Source/V3World/ClayManager/State/StateModifier.cpp +++ b/EU4ToVic3/Source/V3World/ClayManager/State/StateModifier.cpp @@ -73,19 +73,24 @@ double V3::StateModifier::getAllBonuses(const std::map& mod return std::accumulate(modifiers.begin(), modifiers.end(), 0.0); } -std::optional V3::StateModifier::getBuildingGroupModifier(const std::string& buildingGroup, const BuildingGroups& bgs) const +double V3::StateModifier::getBuildingGroupModifier(const std::string& buildingGroup, const BuildingGroups& bgs) const { - std::optional currentGroup = buildingGroup; - do + double modifierTotal = 0; + for (const auto& [groupName, modifier]: buildingGroupModifiers) { - if (const auto& mod = buildingGroupModifiers.find(currentGroup.value()); mod != buildingGroupModifiers.end()) - { - return mod->second; - } - currentGroup = bgs.tryGetParentName(currentGroup); - } while (currentGroup); + std::optional currentGroup = buildingGroup; - return std::nullopt; + do + { + if (groupName == currentGroup.value()) + { + modifierTotal += modifier; + break; + } + currentGroup = bgs.tryGetParentName(currentGroup); + } while (currentGroup); + } + return modifierTotal; } std::optional V3::StateModifier::getBuildingModifier(const std::string& building) const @@ -104,4 +109,13 @@ std::optional V3::StateModifier::getGoodsModifier(const std::string& goo return possibleModifier->second; } return std::nullopt; -} \ No newline at end of file +} + +double V3::StateModifier::calcBuildingModifiers(const Building& building, const BuildingGroups& buildingGroups) const +{ + const auto& modifierIter = buildingModifiers.find(building.getName()); + double modifierTotal = modifierIter == buildingModifiers.end() ? 0 : modifierIter->second; + modifierTotal += getBuildingGroupModifier(building.getBuildingGroup(), buildingGroups); + + return modifierTotal; +} diff --git a/EU4ToVic3/Source/V3World/ClayManager/State/StateModifier.h b/EU4ToVic3/Source/V3World/ClayManager/State/StateModifier.h index ffd62c2f4..80a86dbdd 100644 --- a/EU4ToVic3/Source/V3World/ClayManager/State/StateModifier.h +++ b/EU4ToVic3/Source/V3World/ClayManager/State/StateModifier.h @@ -1,5 +1,6 @@ #ifndef STATE_MODIFIER_H #define STATE_MODIFIER_H +#include "EconomyManager/Building/Building.h" #include "EconomyManager/Building/BuildingGroups.h" #include "Parser.h" @@ -20,9 +21,10 @@ class StateModifier: commonItems::parser [[nodiscard]] const auto& getBuildingModifiersMap() const { return buildingModifiers; } [[nodiscard]] const auto& getGoodsModifiersMap() const { return goodsModifiers; } [[nodiscard]] static double getAllBonuses(const std::map& modifierMap); // Sum of all modifiers off a single type - [[nodiscard]] std::optional getBuildingGroupModifier(const std::string& buildingGroup, const BuildingGroups& bgs) const; + [[nodiscard]] double getBuildingGroupModifier(const std::string& buildingGroup, const BuildingGroups& bgs) const; [[nodiscard]] std::optional getBuildingModifier(const std::string& building) const; [[nodiscard]] std::optional getGoodsModifier(const std::string& good) const; + [[nodiscard]] double calcBuildingModifiers(const Building& building, const BuildingGroups& buildingGroups) const; private: void registerKeys(); diff --git a/EU4ToVic3/Source/V3World/ClayManager/State/SubState.cpp b/EU4ToVic3/Source/V3World/ClayManager/State/SubState.cpp index 0c5fe823e..8a72eb585 100644 --- a/EU4ToVic3/Source/V3World/ClayManager/State/SubState.cpp +++ b/EU4ToVic3/Source/V3World/ClayManager/State/SubState.cpp @@ -68,6 +68,7 @@ double V3::SubState::getTerrainFrequency(const std::string& theTerrain) const double V3::SubState::getOverPopulation() const { + // TODO(Gawquon) Have this check type of subsistence, they give different amount of jobs now. Also capacity is land * jobs / working_adult_ratio. const double pops = subStatePops.getPopCount(); const double capacity = getResource("bg_agriculture") * 5000.0; // One arable land supports roughly 5k people. if (capacity < 5000) @@ -689,6 +690,11 @@ void V3::SubState::generatePops(int totalAmount, const int slaveAmount) } } +double V3::SubState::getJob(const std::string& job) const +{ + return estimatedJobs.contains(job) ? estimatedJobs.at(job) : 0; +} + std::optional V3::SubState::getPrimaryCulture() const { if (subStatePops.getPopCount() == 0) diff --git a/EU4ToVic3/Source/V3World/ClayManager/State/SubState.h b/EU4ToVic3/Source/V3World/ClayManager/State/SubState.h index 8ee6408ac..43ee2f81b 100644 --- a/EU4ToVic3/Source/V3World/ClayManager/State/SubState.h +++ b/EU4ToVic3/Source/V3World/ClayManager/State/SubState.h @@ -61,6 +61,8 @@ class SubState void setSubStatePops(const SubStatePops& thePops) { subStatePops = thePops; } void addPop(const Pop& pop) { subStatePops.addPop(pop); } void addPops(const std::vector& pops) { subStatePops.addPops(pops); } + void setJob(const std::string& job, const double amount) { estimatedJobs[job] = amount; } + void addJob(const std::string& job, const double amount) { estimatedJobs[job] += amount; } void setVanillaPopCount(const int popCount) { vanillaPopCount = popCount; } void setIndustryWeight(const double theIndustryWeight) { industryWeight = theIndustryWeight; } @@ -68,6 +70,7 @@ class SubState void setOriginalCPBudget(const int theCPBudget) { originalCPBudget = theCPBudget; } void spendCPBudget(const int theCPExpense) { CPBudget -= theCPExpense; } void addBuilding(const std::shared_ptr& building) { buildings.push_back(building); } + void addUrbanCenters(const double theUrbanCenters) { urbanCenters += theUrbanCenters; } void setVanillaBuildingElements(const std::vector& elements) { vanillaBuildingElements = elements; } void calculateInfrastructure(const StateModifiers& theStateModifiers, const std::map& techMap, int popPerInfrastructure); @@ -116,12 +119,15 @@ class SubState [[nodiscard]] const auto& getTerrainFrequencies() { return terrainFrequency; } [[nodiscard]] const auto& getDemographics() const { return demographics; } [[nodiscard]] const auto& getSubStatePops() const { return subStatePops; } + [[nodiscard]] double getJob(const std::string& job) const; + [[nodiscard]] const auto& getEstimatedJobs() const { return estimatedJobs; } [[nodiscard]] const auto& getVanillaPopCount() const { return vanillaPopCount; } [[nodiscard]] std::optional getPrimaryCulture() const; [[nodiscard]] const auto& getIndustryWeight() const { return industryWeight; } [[nodiscard]] const auto& getCPBudget() const { return CPBudget; } [[nodiscard]] const auto& getBuildings() const { return buildings; } + [[nodiscard]] const auto& getUrbanCenters() const { return urbanCenters; } [[nodiscard]] const auto& getVanillaBuildingElements() const { return vanillaBuildingElements; } [[nodiscard]] double calcBuildingWeight(const Building& building, const BuildingGroups& buildingGroups, @@ -211,12 +217,14 @@ class SubState std::map terrainFrequency; // Normalized vector (math-wise) of terrain in substate as % std::vector demographics; SubStatePops subStatePops; - int vanillaPopCount = 0; // What pop of substate would be without adjustments + int vanillaPopCount = 0; // What pop of substate would be without adjustments + std::map estimatedJobs; // An estimated count of all pop's jobs, including dependents. double industryWeight = 0; // Share of owner's industry a substate should get, not normalized int CPBudget = 0; // Construction Points for a substate to spend on its development int originalCPBudget = 0; // Used in Building Weight calculations std::vector> buildings; // buildings available to build in the subState + double urbanCenters = 0; // Accumulated levels of Urban Centers std::vector vanillaBuildingElements; // vanilla buildings for this substate, ready for direct dump. std::set claims; diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Building/Building.h b/EU4ToVic3/Source/V3World/EconomyManager/Building/Building.h index dc7e2a941..c4c045620 100644 --- a/EU4ToVic3/Source/V3World/EconomyManager/Building/Building.h +++ b/EU4ToVic3/Source/V3World/EconomyManager/Building/Building.h @@ -15,8 +15,8 @@ class Building: commonItems::parser void setWeight(const double theWeight) { weight = theWeight; } void addPM(const std::string& pm) { PMs.emplace(pm); } void setPMGroups(const std::set& pmgs) { PMGroups = pmgs; } - void addInvestor(const int theLevel, const std::string& type, const std::string& theState, const std::string& theNation); - void addShareholderLevels(const int theLevels, const std::string& type); + void addInvestor(int theLevel, const std::string& type, const std::string& theState, const std::string& theNation); + void addShareholderLevels(int theLevels, const std::string& type); [[nodiscard]] const auto& getName() const { return name; } [[nodiscard]] const auto& getBuildingGroup() const { return buildingGroup; } diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroup.cpp b/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroup.cpp index 186c38358..9ca83b822 100644 --- a/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroup.cpp +++ b/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroup.cpp @@ -14,6 +14,9 @@ void V3::BuildingGroup::registerKeys() registerKeyword("parent_group", [this](std::istream& theStream) { parent = commonItems::getString(theStream); }); + registerKeyword("is_subsistence", [this](std::istream& theStream) { + subsistence = (commonItems::getString(theStream) == "yes"); + }); registerKeyword("capped_by_resources", [this](std::istream& theStream) { resourceCapped = (commonItems::getString(theStream) == "yes"); }); @@ -30,6 +33,9 @@ void V3::BuildingGroup::registerKeys() registerKeyword("category", [this](std::istream& theStream) { category = commonItems::getString(theStream); }); + registerKeyword("urbanization", [this](std::istream& theStream) { + urbanization = commonItems::getInt(theStream); + }); registerKeyword("infrastructure_usage_per_level", [this](std::istream& theStream) { infrastructureCost = commonItems::getDouble(theStream); }); diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroup.h b/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroup.h index 7d2ba7902..a44157a7b 100644 --- a/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroup.h +++ b/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroup.h @@ -19,6 +19,8 @@ class BuildingGroup: commonItems::parser [[nodiscard]] const auto& getInfrastructureCost() const { return infrastructureCost; } [[nodiscard]] const auto& possibleIsResourceCapped() const { return resourceCapped; } [[nodiscard]] const auto& usesArableLand() const { return arableCapped; } + [[nodiscard]] const auto& isSubsistence() const { return subsistence; } + [[nodiscard]] const auto& getUrbanization() const { return urbanization; } private: void registerKeys(); @@ -28,6 +30,8 @@ class BuildingGroup: commonItems::parser std::optional infrastructureCost; std::optional resourceCapped; std::optional arableCapped; + bool subsistence; + int urbanization = 0; }; } // namespace V3 diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroups.cpp b/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroups.cpp index 033b85f31..13f0b287c 100644 --- a/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroups.cpp +++ b/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroups.cpp @@ -98,7 +98,6 @@ bool V3::BuildingGroups::usesArableLand(const std::optional& theGro { break; } - const auto& cat = possibleGroup->second->getCategory(); if (possibleGroup->second->usesArableLand()) { return true; @@ -167,3 +166,28 @@ std::optional V3::BuildingGroups::tryGetIsCapped(const std::optionalsecond->getUrbanization(); urbanization > 0) + { + return urbanization; + } + const auto& parentName = possibleGroup->second->getParentName(); + if (!parentName) + { + break; + } + name = parentName.value(); + } + + return 0; +} diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroups.h b/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroups.h index d155367d6..51e2b0e35 100644 --- a/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroups.h +++ b/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingGroups.h @@ -19,6 +19,7 @@ class BuildingGroups [[nodiscard]] std::optional tryGetParentName(const std::optional& theGroupName) const; [[nodiscard]] std::optional tryGetInfraCost(const std::optional& theGroupName) const; [[nodiscard]] std::optional tryGetIsCapped(const std::optional& theGroupName) const; + [[nodiscard]] int getUrbanization(const std::string& theGroupName) const; [[nodiscard]] bool usesArableLand(const std::optional& theGroupName) const; private: diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingResources.cpp b/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingResources.cpp new file mode 100644 index 000000000..b5d620115 --- /dev/null +++ b/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingResources.cpp @@ -0,0 +1,54 @@ +#include "BuildingResources.h" + +void V3::BuildingResources::evaluateResources(const std::set& usedPMGroups, + const std::map>& estimatedPMs, + const std::map& PMs, + const std::map& PMGroups) +{ + for (const auto& PMGroupName: usedPMGroups) + { + int index = 0; + double fraction = 1.0; + + // If a rule exists + if (const auto& estimateIter = estimatedPMs.find(PMGroupName); estimateIter != estimatedPMs.end()) + { + const auto& [i, p] = estimateIter->second; + index = i; + fraction = p; + } + + // PMs and PMGroups previously validated by the PM estimation process + const auto& basePM = PMs.at(PMGroups.at(PMGroupName).getPMs()[0]); + const auto& advancedPM = PMs.at(PMGroups.at(PMGroupName).getPMs()[index]); + + if (fraction < 1) + { + for (const auto& [job, amount]: basePM.getEmployment()) + { + jobs[job] += amount * (1 - fraction); + } + for (const auto& [input, amount]: basePM.getInputs()) + { + inputs[input] += amount * (1 - fraction); + } + for (const auto& [output, amount]: basePM.getOutputs()) + { + outputs[output] += amount * (1 - fraction); + } + } + + for (const auto& [job, amount]: advancedPM.getEmployment()) + { + jobs[job] += amount * fraction; + } + for (const auto& [input, amount]: advancedPM.getInputs()) + { + inputs[input] += amount * fraction; + } + for (const auto& [output, amount]: advancedPM.getOutputs()) + { + outputs[output] += amount * fraction; + } + } +} \ No newline at end of file diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingResources.h b/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingResources.h new file mode 100644 index 000000000..73c6c43d9 --- /dev/null +++ b/EU4ToVic3/Source/V3World/EconomyManager/Building/BuildingResources.h @@ -0,0 +1,33 @@ +#ifndef V3_BUILDING_RESOURCES_H +#define V3_BUILDING_RESOURCES_H +#include "ProductionMethods/ProductionMethod.h" +#include "ProductionMethods/ProductionMethodGroup.h" + +namespace V3 +{ +class BuildingResources +{ + public: + BuildingResources() = default; + + void addJobs(const std::string& job, const double amount) { jobs[job] += std::max(0.0, amount); } + void addInputs(const std::string& input, const double amount) { inputs[input] += std::max(0.0, amount); } + void addOutputs(const std::string& output, const double amount) { outputs[output] += std::max(0.0, amount); } + void evaluateResources(const std::set& usedPMGroups, + const std::map>& estimatedPMs, + const std::map& PMs, + const std::map& PMGroups); + + [[nodiscard]] const auto& getJobs() const { return jobs; } + [[nodiscard]] const auto& getInputs() const { return inputs; } + [[nodiscard]] const auto& getOutputs() const { return outputs; } + + + private: + std::map jobs; + std::map inputs; + std::map outputs; +}; +} // namespace V3 + +#endif // V3_BUILDING_RESOURCES_H \ No newline at end of file diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Building/ProductionMethods/ProductionMethod.cpp b/EU4ToVic3/Source/V3World/EconomyManager/Building/ProductionMethods/ProductionMethod.cpp index 161b77833..0e653301a 100644 --- a/EU4ToVic3/Source/V3World/EconomyManager/Building/ProductionMethods/ProductionMethod.cpp +++ b/EU4ToVic3/Source/V3World/EconomyManager/Building/ProductionMethods/ProductionMethod.cpp @@ -15,10 +15,10 @@ void V3::ProductionMethod::registerKeys() scalingParser.registerKeyword("country_bureaucracy_add", [this](std::istream& theStream) { bureaucracy = commonItems::getInt(theStream); }); - scalingParser.registerRegex("goods_input\\w+_add", [this](const std::string& goodsType, std::istream& theStream) { + scalingParser.registerRegex("goods_input_\\w+_add", [this](const std::string& goodsType, std::istream& theStream) { inputs.emplace(getType(goodsType), commonItems::getDouble(theStream)); }); - scalingParser.registerRegex("goods_output\\w+_add", [this](const std::string& goodsType, std::istream& theStream) { + scalingParser.registerRegex("goods_output_\\w+_add", [this](const std::string& goodsType, std::istream& theStream) { outputs.emplace(getType(goodsType), commonItems::getDouble(theStream)); }); scalingParser.registerRegex("building_employment_\\w+_add", [this](const std::string& employmentType, std::istream& theStream) { @@ -55,14 +55,24 @@ void V3::ProductionMethod::registerKeys() registerRegex(commonItems::catchallRegex, commonItems::ignoreItem); } +// Returns the \\w+ part of goods_output_\\w+_add and similar regexs std::string V3::ProductionMethod::getType(const std::string& typeString) { std::stringstream typeStream(typeString); - std::string type; - - for (int i = 0; i < 3; i++) + std::string type, segment; + int i = 0; + while (segment != "add") { - std::getline(typeStream, type, '_'); + ++i; + if (i == 4) + { + type = segment; + } + else if (i > 4) + { + type = type + "_" + segment; + } + std::getline(typeStream, segment, '_'); } return type; diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Demand/Market.cpp b/EU4ToVic3/Source/V3World/EconomyManager/Demand/Market.cpp index 9c0fc2086..a12e48da1 100644 --- a/EU4ToVic3/Source/V3World/EconomyManager/Demand/Market.cpp +++ b/EU4ToVic3/Source/V3World/EconomyManager/Demand/Market.cpp @@ -1,5 +1,7 @@ #include "Market.h" +#include +#include #include #include @@ -13,6 +15,16 @@ V3::Market::Market(const std::vector& possibleGoods) } } +void V3::Market::loadGoods(const std::map& goodsList) +{ + for (const auto& good: goodsList | std::views::keys) + { + sellOrders[good] = 0; + buyOrdersBuildings[good] = 0; + buyOrdersPops[good] = 0; + } +} + std::map V3::Market::getMarketBalance() const { std::map marketBalance; @@ -107,7 +119,8 @@ std::set V3::Market::getObsessions(const std::string& culture, cons { if (!cultureErrors.contains(culture)) { - Log(LogLevel::Warning) << "Culture: " << culture << " has no definition. Assuming no obsessions or taboos."; + const auto& theCulture = culture.empty() ? "None" : culture; + Log(LogLevel::Warning) << "Culture: " << theCulture << " has no definition. Assuming no obsessions or taboos."; } return {}; } @@ -127,7 +140,8 @@ std::set V3::Market::getTaboos(const std::string& culture, { if (!religionErrors.contains(religion)) { - Log(LogLevel::Warning) << "Religion: " << religion << " has no definition. Assuming no taboos."; + const auto& theReligion = religion.empty() ? "None" : religion; + Log(LogLevel::Warning) << "Religion: " << theReligion << " has no definition. Assuming no taboos."; } return {}; } @@ -172,7 +186,7 @@ std::map V3::Market::estimateCulturalPrevalence(const std:: { auto culturalFactors = initCulturalFactors(); - for (const auto& [culture, percent]: cultureData) + for (const auto& [culture, fraction]: cultureData) { const auto& taboos = getTaboos(culture, cultures, religions); const auto& obsessions = getObsessions(culture, cultures); @@ -183,7 +197,7 @@ std::map V3::Market::estimateCulturalPrevalence(const std:: { continue; } - culturalFactors.at(taboo) -= percent; + culturalFactors.at(taboo) -= fraction; } for (const auto& obsession: obsessions) { @@ -191,7 +205,7 @@ std::map V3::Market::estimateCulturalPrevalence(const std:: { continue; } - culturalFactors.at(obsession) += percent; + culturalFactors.at(obsession) += fraction; } } @@ -216,7 +230,7 @@ double V3::Market::calcPopFactor(const double size, const std::map& lawsMap) { double workingRatio = popType.getDependentRatio().value_or(defines.getWorkingAdultRatioBase()); - workingRatio += calcAddedWorkingPopPercent(laws, lawsMap); // Propertied Woman + workingRatio += calcAddedWorkingPopFraction(laws, lawsMap); // Propertied Woman return (size * workingRatio + size * (1 - workingRatio) * defines.getDependentConsumptionRatio()) * popType.getConsumptionRate() / 10000; } @@ -267,8 +281,8 @@ double V3::Market::calcPurchaseWeight(double marketShare, const GoodsFulfillment const double culturalFactor = calcCulturalFactor(culturalPrevalence); if (culturalFactor > 1) { - const double percent = culturalFactor - 1; - weight = std::max(weight, percent + (1 - percent) * weight); // (x * 1) + (1 - x)y + const double fraction = culturalFactor - 1; + weight = std::max(weight, fraction + (1 - fraction) * weight); // (x * 1) + (1 - x)y } return marketShare * weight * culturalFactor; @@ -288,7 +302,7 @@ double V3::Market::calcCulturalNeedFactor(const std::vector& goods, return culturalNeedFactor + 1; } -double V3::Market::calcAddedWorkingPopPercent(const std::set& laws, const std::map& lawsMap) +double V3::Market::calcAddedWorkingPopFraction(const std::set& laws, const std::map& lawsMap) { return std::accumulate(laws.begin(), laws.end(), 0.0, [lawsMap](double sum, const std::string& law) { return sum + lawsMap.at(law).workingAdultRatioAdd; @@ -301,7 +315,6 @@ bool V3::Market::validateGood(const std::string& good) const { if (!goodsErrors.contains(good)) { - Log(LogLevel::Warning) << "Good: " << good << " not recognized in market. Converter will act like it doesn't exist."; goodsErrors.emplace(good); } @@ -329,8 +342,7 @@ void V3::Market::calcPopOrders(const int popSize, const auto& culturalPrevalence = estimateCulturalPrevalence(cultureData, cultures, religions); const auto& goodsMap = demand.getGoodsMap(); - // Assuming enough land for each pop (for now). - for (const auto& [job, jobPercent]: jobData) + for (const auto& [job, jobFraction]: jobData) { if (!popTypeMap.contains(job)) { @@ -343,7 +355,7 @@ void V3::Market::calcPopOrders(const int popSize, } const auto& popType = popTypeMap.at(job); - const double popFactor = calcPopFactor(popSize * jobPercent, popType, defines, laws, lawsMap); + const double popFactor = calcPopFactor(popSize * jobFraction, popType, defines, laws, lawsMap); const int wealth = estimateWealth(popType.getStrata()); if (!demand.getWealthConsumptionMap().contains(wealth)) @@ -385,4 +397,56 @@ void V3::Market::calcPopOrders(const int popSize, } } } -} \ No newline at end of file +} + +void V3::Market::clearMarket() +{ + for (auto& value: sellOrders | std::views::values) + { + value = 0; + } + for (auto& value: buyOrdersPops | std::views::values) + { + value = 0; + } + for (auto& value: buyOrdersBuildings | std::views::values) + { + value = 0; + } +} + +// Debugging function +std::stringstream V3::Market::marketAsTable() const +{ + std::stringstream out; + int goodLength = 0; + int amountLength = 0; + const auto& balance = getMarketBalance(); + for (const auto& [good, amount]: balance) + { + goodLength = std::max(goodLength, static_cast(good.length())); + amountLength = std::max(amountLength, static_cast(std::to_string(amount).length())); + } + + out << std::setprecision(3); + out << std::endl; + out << std::left << std::setw(goodLength + 2) << "Good" << std::setw(amountLength + 2) << "Percent" << std::endl; + out << std::setfill('-') << std::setw(goodLength + 2) << "" << std::setw(amountLength + 2) << "" << std::endl; + out << std::setfill(' '); + + std::vector> balanceVector = {balance.begin(), balance.end()}; + + std::ranges::sort(balanceVector, [=](const std::pair& lhs, const std::pair& rhs) { + return lhs.second < rhs.second; + }); + + for (const auto& pair: balanceVector) + { + if (std::abs(pair.second) < 1) + continue; + + const double pricePercent = -0.75 * std::max(-1.0, std::min(1.0, pair.second / 100)); + out << std::left << std::setw(goodLength + 2) << pair.first << std::setw(amountLength + 2) << pricePercent * 100 << "%" << std::endl; + } + return out; +} diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Demand/Market.h b/EU4ToVic3/Source/V3World/EconomyManager/Demand/Market.h index f69b74831..f4713d7ca 100644 --- a/EU4ToVic3/Source/V3World/EconomyManager/Demand/Market.h +++ b/EU4ToVic3/Source/V3World/EconomyManager/Demand/Market.h @@ -15,6 +15,7 @@ class Market public: Market() = default; explicit Market(const std::vector& possibleGoods); + void loadGoods(const std::map& goodsList); [[nodiscard]] std::map getMarketBalance() const; [[nodiscard]] std::map getMarketShare(const std::vector& goods) const; @@ -32,6 +33,10 @@ class Market const std::map& religions, const std::set& laws, const std::map& lawsMap); + void clearMarket(); + std::stringstream marketAsTable() const; + + static double calcAddedWorkingPopFraction(const std::set& laws, const std::map& lawsMap); private: static int estimateWealth(const std::string& strata); @@ -51,7 +56,6 @@ class Market const std::map& goodsMap); static double calcPurchaseWeight(double marketShare, const GoodsFulfillment& fulfillment, double culturalPrevalence); static double calcCulturalNeedFactor(const std::vector& goods, const std::map& culturalPrevalence); - static double calcAddedWorkingPopPercent(const std::set& laws, const std::map& lawsMap); [[nodiscard]] bool validateGood(const std::string& good) const; [[nodiscard]] std::vector enumerateGoods(const std::map& map, const std::map& goodsMap) const; diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Demand/MarketJobs.cpp b/EU4ToVic3/Source/V3World/EconomyManager/Demand/MarketJobs.cpp new file mode 100644 index 000000000..a8db5d688 --- /dev/null +++ b/EU4ToVic3/Source/V3World/EconomyManager/Demand/MarketJobs.cpp @@ -0,0 +1,191 @@ +#include "MarketJobs.h" + +#include "ClayManager/State/SubState.h" + +#include +#include + + +V3::MarketJobs::MarketJobs(const std::vector>& manorHouseRoster): manorHouseRoster(manorHouseRoster) +{ +} + +// Returns levels of displaced subsistence building. +double V3::MarketJobs::createJobs(const std::map& rgoUnitEmployment, + const std::map& subsistenceUnitEmployment, + const int levels, + const double defaultRatio, + const double womenJobRate, + const std::map& estimatedOwnerships, + const std::map>& ownershipEmployments, + const std::map& popTypes, + const std::shared_ptr& subState) +{ + auto unitEmployment = rgoUnitEmployment; + for (const auto& [ownershipBuildingName, fraction]: estimatedOwnerships) + { + if (!ownershipEmployments.contains(ownershipBuildingName)) + { + if (ownershipEmploymentsErrorFlag.find(ownershipBuildingName) == ownershipEmploymentsErrorFlag.end()) + { + Log(LogLevel::Error) << ownershipBuildingName << ": has no known employment data, economy generation will be broken."; + ownershipEmploymentsErrorFlag.emplace(ownershipBuildingName); + } + continue; + } + + for (const auto& [job, amount]: ownershipEmployments.at(ownershipBuildingName)) // Account for the owner of the buildings. + { + unitEmployment[job] += amount * fraction; + } + } + for (const auto& [job, amount]: unitEmployment) // Track Dependents + { + unitEmployment[job] = getWorkersPlusDependents(amount, popTypes.at(job), defaultRatio, womenJobRate); + } + + double totalPlaced = 0; + for (const auto& [job, amount]: unitEmployment) + { + subState->addJob(job, amount * levels); + totalPlaced += amount * levels; + } + return hireFromWorseJobs(totalPlaced, defaultRatio, womenJobRate, popTypes, subsistenceUnitEmployment, subState); +} + +// post: The given subState's job estimate is initialized with the 0 buildings version of local employment. +// Returns number of subsistence building levels filled. +double V3::MarketJobs::createSubsistence(const std::map& subsistenceUnitEmployment, + const double defaultRatio, + const double womenJobRate, + const int arableLand, + const std::map& popTypes, + const std::shared_ptr& subState) +{ + std::map unitEmployment; + + for (const auto& [job, amount]: subsistenceUnitEmployment) + { + unitEmployment[job] += amount; + } + for (const auto& [job, amount]: manorHouseRoster) // Peasants create manor house jobs. + { + unitEmployment[job] += amount; + } + for (const auto& [job, amount]: unitEmployment) // Track Dependents + { + unitEmployment[job] = getWorkersPlusDependents(amount, popTypes.at(job), defaultRatio, womenJobRate); + } + + double unitEmploymentPop = std::accumulate(unitEmployment.begin(), unitEmployment.end(), 0.0, [](double sum, const auto& pair) { + return sum + pair.second; + }); + unitEmploymentPop = std::max(unitEmploymentPop, 1.0); + const double unemployedPerUnit = subState->getSubStatePops().getPopCount() / unitEmploymentPop; + const double levels = std::min(static_cast(arableLand), unemployedPerUnit); + + for (const auto& [job, amount]: unitEmployment) + { + subState->addJob(job, amount * levels); + subState->addJob("unemployed", amount * (unemployedPerUnit - levels)); + } + + return levels; +} + + +double V3::MarketJobs::calculateWorkerDependencyRatio(const PopType& popType, const double defaultRatio, const double womenJobRate) +{ + return popType.getDependentRatio().value_or(defaultRatio) + womenJobRate; +} + +double V3::MarketJobs::getWorkersPlusDependents(const double amountOfJobs, const PopType& popType, const double defaultRatio, const double womenJobRate) +{ + return amountOfJobs / calculateWorkerDependencyRatio(popType, defaultRatio, womenJobRate); +} + + +double V3::MarketJobs::hireFromWorseJobs(double amount, + const double defaultRatio, + const double womenJobRate, + const std::map& popTypes, + const std::map& subsistenceUnitEmployment, + const std::shared_ptr& subState) +{ + amount = hireFromUnemployed(amount, subState); + return hireFromSubsistence(amount, subsistenceUnitEmployment, defaultRatio, womenJobRate, popTypes, subState); +} + +double V3::MarketJobs::hireFromUnemployed(const double amount, const std::shared_ptr& subState) +{ + const double unemployed = subState->getJob("unemployed"); + if (unemployed > amount) + { + subState->addJob("unemployed", -amount); + return 0; + } + subState->setJob("unemployed", 0); + return amount - unemployed; +} + + +double V3::MarketJobs::hireFromSubsistence(const double amount, + const std::map& subsistenceUnitEmployment, + const double defaultRatio, + const double womenJobRate, + const std::map& popTypes, + const std::shared_ptr& subState) +{ + // Use peasants as a proxy for subsistence worker presence. + if (subState->getJob("peasants") == 0) + { + // Log(LogLevel::Warning) << "No subsistence workers available."; + return 0; + } + + // Accounts for Homesteading, 2.5% of jobs are Farmers per subsistence level. + std::map subsistenceCounts; + double unitSubsistencePop = 0; // Amount of workers + dependents in a subsistence level + for (const auto& [job, workers]: subsistenceUnitEmployment) + { + subsistenceCounts[job] = getWorkersPlusDependents(workers, popTypes.at(job), defaultRatio, womenJobRate); + unitSubsistencePop += subsistenceCounts[job]; + } + + if (!subsistenceCounts.contains("peasants") || subsistenceCounts.at("peasants") <= 0) + { + if (!peasantErrorFlag) + { + Log(LogLevel::Error) << "Supposed subsistence building contains no peasants in its production methods. Job predictions will be unreliable."; + peasantErrorFlag = true; + } + return 0; + } + + const double lostSubsistenceLevels = std::min(amount / unitSubsistencePop, subState->getJob("peasants") / subsistenceCounts.at("peasants")); + for (const auto& [job, people]: subsistenceCounts) + { + subState->addJob(job, -people * lostSubsistenceLevels); + } + // When subsistence levels are removed, Manor Houses downsize + downsizeManorHouses(lostSubsistenceLevels, defaultRatio, womenJobRate, popTypes, subState); + + // if (const double remainder = amount - lostSubsistenceLevels * unitSubsistencePop; remainder > 0) + //{ + // Log(LogLevel::Warning) << "Could not find available workers for " << remainder << " jobs."; // Should never happen. + // } + + return lostSubsistenceLevels; +} + +void V3::MarketJobs::downsizeManorHouses(const double lostSubsistenceLevels, + const double defaultRatio, + const double womenJobRate, + const std::map& popTypes, + const std::shared_ptr& subState) +{ + for (const auto& [job, amount]: manorHouseRoster) + { + subState->addJob(job, -getWorkersPlusDependents(amount, popTypes.at(job), defaultRatio, womenJobRate) * lostSubsistenceLevels); + } +} \ No newline at end of file diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Demand/MarketJobs.h b/EU4ToVic3/Source/V3World/EconomyManager/Demand/MarketJobs.h new file mode 100644 index 000000000..88aec3ada --- /dev/null +++ b/EU4ToVic3/Source/V3World/EconomyManager/Demand/MarketJobs.h @@ -0,0 +1,67 @@ +#ifndef V3_MARKET_JOBS_H +#define V3_MARKET_JOBS_H + +#include "PoliticalManager/Country/Country.h" +#include "PopManager/Pops/PopType.h" + +namespace V3 +{ +class MarketJobs +{ + public: + MarketJobs() = default; + explicit MarketJobs(const std::vector>& manorHouseRoster); + + double createJobs(const std::map& rgoUnitEmployment, + const std::map& subsistenceUnitEmployment, + int levels, + double defaultRatio, + double womenJobRate, + const std::map& estimatedOwnerships, + const std::map>& ownershipEmployments, + const std::map& popTypes, + const std::shared_ptr& subState); + double createSubsistence(const std::map& subsistenceUnitEmployment, + double defaultRatio, + double womenJobRate, + int arableLand, + const std::map& popTypes, + const std::shared_ptr& subState); + + private: + // For a given popType, what % of that popType have jobs? + static double calculateWorkerDependencyRatio(const PopType& popType, double defaultRatio, double womenJobRate); + // How many people do x number of jobs support? + static double getWorkersPlusDependents(double amountOfJobs, const PopType& popType, double defaultRatio, double womenJobRate); + + // Returns the amount of jobs with no unemployed available. + static double hireFromUnemployed(double amount, const std::shared_ptr& subState); + // Returns the level of subsistence buildings downsized. + double hireFromWorseJobs(double amount, + double defaultRatio, + double womenJobRate, + const std::map& popTypes, + const std::map& subsistenceUnitEmployment, + const std::shared_ptr& subState); + // Returns the level of subsistence buildings downsized. + double hireFromSubsistence(double amount, + const std::map& subsistenceUnitEmployment, + double defaultRatio, + double womenJobRate, + const std::map& popTypes, + const std::shared_ptr& subState); + // Removes employment from Manor Houses based on # of peasants who got real jobs. + void downsizeManorHouses(double lostSubsistenceLevels, + double defaultRatio, + double womenJobRate, + const std::map& popTypes, + const std::shared_ptr& subState); + + std::vector> manorHouseRoster; + + inline static bool peasantErrorFlag = false; + inline static std::set ownershipEmploymentsErrorFlag = {}; +}; +} // namespace V3 + +#endif // V3_MARKET_JOBS_H \ No newline at end of file diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Demand/MarketTracker.cpp b/EU4ToVic3/Source/V3World/EconomyManager/Demand/MarketTracker.cpp new file mode 100644 index 000000000..ed0bcc882 --- /dev/null +++ b/EU4ToVic3/Source/V3World/EconomyManager/Demand/MarketTracker.cpp @@ -0,0 +1,288 @@ +#include "MarketTracker.h" +#include "ClayManager/State/State.h" +#include "ClayManager/State/SubState.h" +#include "EconomyManager/Building/BuildingGroup.h" +#include +#include + +V3::MarketTracker::MarketTracker(const std::map& possibleGoods, + const std::set& manorHousePMGroups, + const std::map& PMGroups, + const std::map& PMs) +{ + market.loadGoods(possibleGoods); + std::map manorHouseEmployment; + for (const auto& pmg: manorHousePMGroups) + { + for (const auto& [job, amount]: PMs.at(PMGroups.at(pmg).getPMs()[0]).getEmployment()) + { + manorHouseEmployment[job] = amount; + } + } + std::vector> manorHouseRoster; + for (const auto& pair: manorHouseEmployment) + { + manorHouseRoster.push_back(pair); + } + marketJobs = MarketJobs(manorHouseRoster); +} + +void V3::MarketTracker::resetMarket() +{ + market.clearMarket(); + marketCulture.clear(); +} + +void V3::MarketTracker::loadPeasants(const Country& country, + const double defaultRatio, + const std::map& lawsMap, + const std::map>& estimatedPMs, + const std::map& PMs, + const std::map& PMGroups, + const std::map& popTypes, + const std::map& buildings, + const V3::BuildingGroups& buildingGroups, + const std::map& stateTraits) +{ + const double womenJobRate = Market::calcAddedWorkingPopFraction(country.getProcessedData().laws, lawsMap); + + // For each state check peasant PM + for (const auto& subState: country.getSubStates()) + { + const auto& traits = subState->getHomeState()->getTraits(); + BuildingResources subsistenceResources; + const Building& subsistenceBuilding = buildings.at(subState->getHomeState()->getSubsistenceBuilding()); + subsistenceResources.evaluateResources(subsistenceBuilding.getPMGroups(), estimatedPMs, PMs, PMGroups); + + const double subsistenceModifier = calcThroughputStateModifier(traits, subsistenceBuilding, buildingGroups, stateTraits); + const double levels = + marketJobs.createSubsistence(subsistenceResources.getJobs(), defaultRatio, womenJobRate, subState->getResource("bg_agriculture"), popTypes, subState); + updateMarketGoods(levels, levels, 0, subsistenceModifier, subsistenceResources, traits, stateTraits); + } +} + +void V3::MarketTracker::updatePopNeeds(const V3::Country& country, + const Vic3DefinesLoader& defines, + const DemandLoader& demandDefines, + const std::set& laws, + const std::map& popTypes, + const std::map& cultures, + const std::map& religions, + const std::map& lawsMap) +{ + market.calcPopOrders(country.getPopCount(), country.getJobBreakdown(), marketCulture, defines, demandDefines, popTypes, cultures, religions, laws, lawsMap); +} + +void V3::MarketTracker::integrateBuilding(const Building& building, + const int p, + const double defaultRatio, + const std::map>& estimatedPMs, + const std::map& PMGroups, + const std::map& PMs, + const BuildingGroups& buildingGroups, + const std::map>& estOwnershipFractions, + const std::map& lawsMap, + const std::map& techMap, + const std::map& stateTraits, + const std::map& popTypes, + const std::map& buildings, + const std::shared_ptr& subState) +{ + const auto& traits = subState->getHomeState()->getTraits(); + BuildingResources buildingResources; + buildingResources.evaluateResources(building.getPMGroups(), estimatedPMs, PMs, PMGroups); + + // Collect any/all building or inherited building_group throughput modifiers + const double throughputMod = calcThroughputStateModifier(traits, building, buildingGroups, stateTraits); + + // Evaluate the economy of scale cap + // There is an economy of scale penalty for nationalized buildings. For now we're ignoring it. + // There are scenarios other than subsistence building that do not use economy of scale, but none that are currently relevant. + const int eosCap = calcEconomyOfScaleCap(*subState->getOwner(), building, buildingGroups, techMap); + + //// Update the market. + updateMarketGoods(building.getLevel(), p, eosCap, throughputMod, buildingResources, traits, stateTraits); + + // Ownership fraction filtering. + std::map estOwnershipFraction; + if (estOwnershipFractions.contains(building.getName())) + { + estOwnershipFraction = estOwnershipFractions.at(building.getName()); + } + + // Set up Subsistence + BuildingResources subsistenceResources; + const Building& subsistenceBuilding = buildings.at(subState->getHomeState()->getSubsistenceBuilding()); + subsistenceResources.evaluateResources(subsistenceBuilding.getPMGroups(), estimatedPMs, PMs, PMGroups); + const double addedWorkingPopFraction = Market::calcAddedWorkingPopFraction(subState->getOwner()->getProcessedData().laws, lawsMap); + + // Track Jobs changed. + double lostSubsistence = marketJobs.createJobs(buildingResources.getJobs(), + subsistenceResources.getJobs(), + p, + defaultRatio, + addedWorkingPopFraction, + estOwnershipFraction, + {{ + "building_financial_district", + {{"capitalists", 50}, {"shopkeepers", 100}, {"clerks", 100}}, + }, + {"building_manor_house", {{"aristocrats", 50}, {"laborers", 100}}}}, // TODO(Gawquon): Load in + popTypes, + subState); + + // Track outputs and Jobs from new Urban Centers. + if (const auto& urbanization = buildingGroups.getUrbanization(building.getBuildingGroup()); urbanization > 0) + { + const double urbanFrac = urbanization / 100.0; + subState->addUrbanCenters(urbanFrac * p); + + BuildingResources urbanResources; + if (!buildings.contains("building_urban_center")) + { + Log(LogLevel::Error) << "No building definition of Urban Center."; + } + const Building& urbanBuilding = buildings.at("building_urban_center"); + const double urbanThroughputMod = calcThroughputStateModifier(traits, urbanBuilding, buildingGroups, stateTraits); + urbanResources.evaluateResources(urbanBuilding.getPMGroups(), estimatedPMs, PMs, PMGroups); + + const double urbanLevel = subState->getUrbanCenters() + urbanFrac * p; + updateMarketGoods(urbanLevel, urbanFrac * p, eosCap, urbanThroughputMod, urbanResources, traits, stateTraits); + + lostSubsistence += marketJobs.createJobs(urbanResources.getJobs(), + subsistenceResources.getJobs(), + p * urbanFrac, + defaultRatio, + addedWorkingPopFraction, + {}, + {}, + popTypes, + subState); + } + + // Track Subsistence outputs. + const double subsistenceModifier = calcThroughputStateModifier(traits, subsistenceBuilding, buildingGroups, stateTraits); + updateMarketGoods(-lostSubsistence, -lostSubsistence, 0, subsistenceModifier, subsistenceResources, traits, stateTraits); +} + +void V3::MarketTracker::logDebugMarket(const Country& country) const +{ + Log(LogLevel::Debug) << "\n" + << country.getName("english") << "'s Market:\n" + << market.marketAsTable().str() << "\nJobs Estimate:" << breakdownAsTable(country.getJobBreakdown(), country.getPopCount(), false).str() + << "\nCulture Split:" << breakdownAsTable(marketCulture, 0).str(); +} + +// Debugging function +std::stringstream V3::MarketTracker::breakdownAsTable(const std::map& breakdown, const int popCount, const bool asPercent) +{ + std::stringstream out; + int nameLength = 0; + int percentLength = 0; + for (const auto& [good, amt]: breakdown) + { + nameLength = std::max(nameLength, static_cast(good.length())); + percentLength = std::max(percentLength, static_cast(std::to_string(amt).length())); + } + + out << std::setprecision(3); + out << std::endl; + out << std::setfill('-') << std::setw(nameLength + 2) << "" << std::setw(percentLength + 2) << "" << std::endl; + out << std::setfill(' '); + + const double mult = asPercent ? 1 : popCount; + + for (const auto& pair: breakdown) + { + out << std::left << std::setw(nameLength + 2) << pair.first << std::setw(percentLength + 2) << pair.second * mult << std::endl; + } + return out; +} + +void V3::MarketTracker::updateMarketGoods(const double level, + const double p, + const int eosCap, + const double throughputMod, + const BuildingResources& buildingResources, + const std::vector& traits, + const std::map& stateTraits) +{ + for (const auto& [good, amount]: buildingResources.getInputs()) + { + double effectiveLevelsAdded; + if (level <= eosCap) + { + effectiveLevelsAdded = p * ((2 * level - p) / 100.0 + 1 + throughputMod); + } + else if (level - p >= eosCap) // We're already past the economy of scale cap. + { + effectiveLevelsAdded = p * (eosCap / 100.0 + 1 + throughputMod); + } + else // The new level of buildings just jumped over the economy of scale cap. + { + effectiveLevelsAdded = p * (throughputMod + 1) + (eosCap * level + 2 * level * p - pow(level, 2) - pow(p, 2)) / 100.0; + } + market.buyForBuilding(good, effectiveLevelsAdded * amount); + } + + for (const auto& [good, amount]: buildingResources.getOutputs()) + { + // Collect any/all good specific output modifiers + double outputMod = 0; + for (const auto& trait: traits) + { + if (const auto& traitIter = stateTraits.find(trait); traitIter != stateTraits.end()) + { + outputMod += traitIter->second.getGoodsModifier(good).value_or(0); + continue; + } + Log(LogLevel::Warning) << "Trait: " << trait << "has no definition."; + } + + double effectiveLevelsAdded; + if (level <= eosCap) + { + effectiveLevelsAdded = p * ((2 * level - p) / 100.0 + 1 + throughputMod + outputMod); + } + else if (level - p >= eosCap) // We're already past the economy of scale cap. + { + effectiveLevelsAdded = p * (eosCap / 100.0 + 1 + throughputMod + outputMod); + } + else // The new level of buildings just jumped over the economy of scale cap. + { + effectiveLevelsAdded = p * (throughputMod + outputMod + 1) + (eosCap * level + 2 * level * p - pow(level, 2) - pow(p, 2)) / 100.0; + } + market.sell(good, effectiveLevelsAdded * amount); + } +} + +double V3::MarketTracker::calcThroughputStateModifier(const std::vector& traits, + const Building& building, + const BuildingGroups& buildingGroups, + const std::map& stateTraits) +{ + double throughputMod = 0; + for (const auto& trait: traits) + { + if (const auto& traitIter = stateTraits.find(trait); traitIter != stateTraits.end()) + { + throughputMod += traitIter->second.calcBuildingModifiers(building, buildingGroups); + continue; + } + Log(LogLevel::Warning) << "Trait: " << trait << "has no definition."; + } + + return throughputMod; +} + +int V3::MarketTracker::calcEconomyOfScaleCap(const Country& country, + const Building& building, + const BuildingGroups& buildingGroups, + const std::map& techMap) +{ + if (buildingGroups.getBuildingGroupMap().at(building.getBuildingGroup())->isSubsistence()) + { + return 0; + } + return country.getThroughputMax(techMap); +} diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Demand/MarketTracker.h b/EU4ToVic3/Source/V3World/EconomyManager/Demand/MarketTracker.h new file mode 100644 index 000000000..ddcc822b7 --- /dev/null +++ b/EU4ToVic3/Source/V3World/EconomyManager/Demand/MarketTracker.h @@ -0,0 +1,90 @@ +#ifndef V3_MARKET_TRACKER_H +#define V3_MARKET_TRACKER_H +#include "ClayManager/State/StateModifier.h" +#include "EconomyManager/Building/Building.h" +#include "EconomyManager/Building/BuildingGroups.h" +#include "EconomyManager/Building/BuildingResources.h" +#include "EconomyManager/Building/ProductionMethods/ProductionMethod.h" +#include "EconomyManager/Building/ProductionMethods/ProductionMethodGroup.h" +#include "Market.h" +#include "MarketJobs.h" +#include "PoliticalManager/Country/Country.h" + + +namespace V3 +{ +class MarketTracker +{ + public: + MarketTracker() = default; + explicit MarketTracker(const std::map& possibleGoods, + const std::set& manorHousePMGroups, + const std::map& PMGroups, + const std::map& PMs); + void resetMarket(); + void loadPeasants(const Country& country, + double defaultRatio, + const std::map& lawsMap, + const std::map>& estimatedPMs, + const std::map& PMs, + const std::map& PMGroups, + const std::map& popTypes, + const std::map& buildings, + const BuildingGroups& buildingGroups, + const std::map& stateTraits); + void loadCultures(const std::map& cultureData) { marketCulture = cultureData; } + + void updatePopNeeds(const Country& country, + const Vic3DefinesLoader& defines, + const DemandLoader& demandDefines, + const std::set& laws, + const std::map& popTypes, + const std::map& cultures, + const std::map& religions, + const std::map& lawsMap); + void integrateBuilding(const Building& building, + int p, + double defaultRatio, + const std::map>& estimatedPMs, + const std::map& PMGroups, + const std::map& PMs, + const BuildingGroups& buildingGroups, + const std::map>& estOwnershipFractions, + const std::map& lawsMap, + const std::map& techMap, + const std::map& stateTraits, + const std::map& popTypes, + const std::map& buildings, + const std::shared_ptr& subState); + + void logDebugMarket(const Country& country) const; + + private: + static std::stringstream breakdownAsTable(const std::map& breakdown, int popCount, bool asPercent = true); + static double calcThroughputStateModifier(const std::vector& traits, + const Building& building, + const BuildingGroups& buildingGroups, + const std::map& stateTraits); + static int calcEconomyOfScaleCap(const Country& country, + const Building& building, + const BuildingGroups& buildingGroups, + const std::map& techMap); + + void updateMarketGoods(double level, + const double p, + int eosCap, + double throughputMod, + const BuildingResources& buildingResources, + const std::vector& traits, + const std::map& stateTraits); + + + Market market; + MarketJobs marketJobs; + std::map marketCulture; + + static inline std::set subsistenceErrors = {}; +}; +} // namespace V3 + +#endif // V3_MARKET_TRACKER_H \ No newline at end of file diff --git a/EU4ToVic3/Source/V3World/EconomyManager/EconomyManager.cpp b/EU4ToVic3/Source/V3World/EconomyManager/EconomyManager.cpp index 6fa051140..4e757cc12 100644 --- a/EU4ToVic3/Source/V3World/EconomyManager/EconomyManager.cpp +++ b/EU4ToVic3/Source/V3World/EconomyManager/EconomyManager.cpp @@ -15,6 +15,7 @@ #include "Loaders/StateModifierLoader/StateModifierLoader.h" #include "Loaders/TerrainLoader/TerrainModifierLoader.h" #include "Log.h" +#include "Packet.h" #include "PoliticalManager/Country/Country.h" #include "PoliticalManager/PoliticalManager.h" #include @@ -22,7 +23,6 @@ #include #include #include - void V3::EconomyManager::loadCentralizedStates(const std::map>& countries) { Log(LogLevel::Info) << "-> Loading Centralized Countries for Economy Application."; @@ -61,9 +61,10 @@ void V3::EconomyManager::loadMappersAndConfigs(const commonItems::ModFilesystem& loadPopTypes(modFS); } -void V3::EconomyManager::establishBureaucracy(const PoliticalManager& politicalManager, const Vic3DefinesLoader& defines) const +void V3::EconomyManager::establishBureaucracy(const std::shared_ptr& country, + const std::map& lawsMap, + const Vic3DefinesLoader& defines) const { - Log(LogLevel::Info) << "-> Establishing Bureaucracy."; if (!buildings.contains("building_government_administration")) { Log(LogLevel::Error) << "No building definition found for: building_government_administration."; @@ -72,54 +73,75 @@ void V3::EconomyManager::establishBureaucracy(const PoliticalManager& politicalM const auto& govAdmin = buildings.at("building_government_administration"); - for (const auto& country: centralizedCountries) + // Check tech requirement for government administrations. + if (!country->hasAnyOfTech(govAdmin.getUnlockingTechs())) { - // Check tech requirement for government administrations. - if (!country->hasAnyOfTech(govAdmin.getUnlockingTechs())) - { - continue; - } - - // Give 10% extra for trade routes - cap at +400 - const double usage = country->calculateBureaucracyUsage(politicalManager.getLawsMap(), defines); - const double generationTarget = std::min(usage * 1.1, usage + 500) - 100; + return; + } - // Use the PM with the most generation available - int PMGeneration = 35; - const auto& PMName = pickBureaucracyPM(*country); - if (PMs.contains(PMName)) - { - PMGeneration = PMs.at(PMName).getBureaucracy(); - } + // Give 10% extra for trade routes - cap at +400 + const double usage = country->calculateBureaucracyUsage(lawsMap, defines); + const double generationTarget = std::min(usage * 1.1, usage + 500) - 100; - country->distributeGovAdmins(generationTarget, PMGeneration, techMap.getTechs(), buildings.at("building_government_administration")); + // Use the PM with the most generation available + int PMGeneration = 35; + const auto& PMName = pickBureaucracyPM(*country); + if (PMs.contains(PMName)) + { + PMGeneration = PMs.at(PMName).getBureaucracy(); } - Log(LogLevel::Info) << "<> Bureaucracy Established."; + + country->distributeGovAdmins(generationTarget, PMGeneration, techMap.getTechs(), buildings.at("building_government_administration")); } -void V3::EconomyManager::hardcodePorts() const +void V3::EconomyManager::hardcodePorts(const std::shared_ptr& country) const { - Log(LogLevel::Info) << "-> Hardcoding Ports."; - auto counter = 0; - for (const auto& country: centralizedCountries) + for (const auto& subState: country->getSubStates()) { - for (const auto& subState: country->getSubStates()) + if (!subState->getVanillaBuildingElements().empty()) + continue; // don't affect states imported from vanilla. + if (!subState->isCoastal()) + continue; + + auto port = std::make_shared(buildings.at("building_port")); + port->setLevel(1); + + subState->addBuilding(port); + subState->getOwner()->addTech("navigation"); + } +} + +void V3::EconomyManager::integrateHardcodedBuildings(const std::shared_ptr& country, + double defaultRatio, + const std::map>& estimatedPMs, + const std::map& lawsMap, + const std::map& popTypes, + MarketTracker& market) const +{ + for (const auto& subState: country->getSubStates()) + { + for (const auto& building: subState->getBuildings()) { - if (!subState->getVanillaBuildingElements().empty()) - continue; // don't affect states imported from vanilla. - if (!subState->isCoastal()) + if (building->getLevel() <= 0) continue; - auto port = std::make_shared(buildings.at("building_port")); - port->setLevel(1); - - subState->addBuilding(port); - ++counter; - subState->getOwner()->addTech("navigation"); + market.integrateBuilding(*building, + building->getLevel(), + defaultRatio, + estimatedPMs, + PMGroups, + PMs, + buildingGroups, + {}, // All currently hardcoded buildings do not use ownership. + lawsMap, + techMap.getTechs(), + stateTraits, + popTypes, + buildings, + subState); } } - Log(LogLevel::Info) << "<> Hardcoded " << counter << " ports."; } void V3::EconomyManager::assignCountryCPBudgets(const Configuration::ECONOMY economyType, @@ -207,7 +229,10 @@ void V3::EconomyManager::balanceNationalBudgets() const Log(LogLevel::Info) << "<> Industry Sectors Primed."; } -void V3::EconomyManager::buildBuildings(const std::map& lawsMap) const +void V3::EconomyManager::buildBuildings(const std::map& lawsMap, + const std::map& cultures, + const std::map& religions, + const Vic3DefinesLoader& defines) const { Log(LogLevel::Info) << "-> Building buildings."; auto counter = 0; @@ -223,20 +248,54 @@ void V3::EconomyManager::buildBuildings(const std::map& lawsMa // 3c. packet size is based on the mean amount of CP states have left to build and is configurable // 4. If a substate ends up with less CP than the cost for any possible valid building, they relinquish it to the next sector/substate + MarketTracker market(demand.getGoodsMap(), buildings.at("building_manor_house").getPMGroups(), PMGroups, PMs); + for (const auto& country: centralizedCountries) { + // Make estimates from country data. const auto& sectors = country->getProcessedData().industrySectors; auto subStatesByBudget = prepareSubStatesByBudget(country, lawsMap); + const auto& estimatedPMs = PMMapper.estimatePMs(*country, PMs, PMGroups, buildings); + const auto& estimatedOwnershipFracs = estimateInvestorBuildings(*country); + + // Initialize the market in a no-buildings state. + market.resetMarket(); + market.loadPeasants(*country, + defines.getWorkingAdultRatioBase(), + lawsMap, + estimatedPMs, + PMs, + PMGroups, + popTypeLoader.getPopTypes(), + buildings, + buildingGroups, + stateTraits); + market.loadCultures(country->getCultureBreakdown()); - // Until every substate is unable to build anything + // Initialize hardcoded buildings needed for balance. + establishBureaucracy(country, lawsMap, defines); + hardcodePorts(country); + integrateHardcodedBuildings(country, defines.getWorkingAdultRatioBase(), estimatedPMs, lawsMap, popTypeLoader.getPopTypes(), market); + + // Until no substate can build. while (!subStatesByBudget.empty()) { - // Enter negotiation - // Pick the substate with the most budget - negotiateBuilding(subStatesByBudget[0], sectors, lawsMap, subStatesByBudget); + // Update the pop's demand. + market.updatePopNeeds(*country, defines, demand, country->getProcessedData().laws, popTypeLoader.getPopTypes(), cultures, religions, lawsMap); + + // Enter negotiation. + // Pick the substate with the most budget. + negotiateBuilding(subStatesByBudget[0], + sectors, + lawsMap, + subStatesByBudget, + estimatedPMs, + estimatedOwnershipFracs, + defines.getWorkingAdultRatioBase(), + market); ++counter; - // A Building has now been built, process for next round + // A Building has now been built, process for next round. std::sort(subStatesByBudget.begin(), subStatesByBudget.end(), SubState::greaterBudget); removeSubStateIfFinished(subStatesByBudget, subStatesByBudget.end() - 1, lawsMap); } @@ -505,26 +564,11 @@ void V3::EconomyManager::investCapital(const std::mapgetName()); - std::map investorWeights; - - double totalWeight = 0; - for (const auto& [type, investorData]: ownershipMap) - { - if (investorData.recognized && country->getProcessedData().type != "recognized") - { - continue; - } + const auto& investorFractions = calcInvestorFractions(ownershipMap, *country); - investorWeights[type] = investorData.weight; - totalWeight += investorData.weight; - } - for (const auto& type: investorWeights | std::views::keys) - { - investorWeights[type] /= totalWeight; - } // Now apportion the buildings out among the different investor types - const auto& investorApportionment = apportionInvestors(building->getLevel(), investorWeights, investorIOUs); + const auto& investorApportionment = apportionInvestors(building->getLevel(), investorFractions, investorIOUs); // For each investor class with assigned buildings, split owners between local/foreign/capital as directed for (const auto& [type, levels]: investorApportionment) @@ -659,7 +703,11 @@ std::vector> V3::EconomyManager::prepareSubStatesB void V3::EconomyManager::negotiateBuilding(const std::shared_ptr& subState, const std::map>& sectors, const std::map& lawsMap, - const std::vector>& subStates) const + const std::vector>& subStates, + const std::map>& estimatedPMs, + const std::map>& estimatedOwnershipFracs, + const double defaultRatio, + MarketTracker& market) const { // Whether or not the negotiation succeeds, a building MUST be built. @@ -693,7 +741,7 @@ void V3::EconomyManager::negotiateBuilding(const std::shared_ptr& subS } // So we're a valid building in a valid sector and there is budget for us. Great! - buildBuilding(building, subState, sectors.at(sector.value()), lawsMap, subStates); + buildBuilding(building, subState, sectors.at(sector.value()), lawsMap, subStates, estimatedPMs, estimatedOwnershipFracs, defaultRatio, market); talksFail = false; break; } @@ -702,7 +750,15 @@ void V3::EconomyManager::negotiateBuilding(const std::shared_ptr& subS { // Negotiation failed // State picks its favorite building, takes from biggest sector - buildBuilding(subState->getBuildings()[0], subState, getSectorWithMostBudget(sectors), lawsMap, subStates); + buildBuilding(subState->getBuildings()[0], + subState, + getSectorWithMostBudget(sectors), + lawsMap, + subStates, + estimatedPMs, + estimatedOwnershipFracs, + defaultRatio, + market); } } @@ -719,13 +775,19 @@ void V3::EconomyManager::buildBuilding(const std::shared_ptr& building const std::shared_ptr& subState, const std::shared_ptr& sector, const std::map& lawsMap, - const std::vector>& subStates) const + const std::vector>& subStates, + const std::map>& estimatedPMs, + const std::map>& estimatedOwnershipFracs, + const double defaultRatio, + MarketTracker& market) const { // SUBSTATE MUST SPEND ITS CP OR WE GET INFINITE LOOPS // Spend sector CP if possible // Pick a packet size! - const int p = determinePacketSize(building, sector, subState, lawsMap, subStates); + const int p = + Packet(*building, sector->getCPBudget(), econDefines.getPacketFactor(), *subState, subStates, lawsMap, techMap.getTechs(), stateTraits, buildingGroups) + .getSize(); int cost = building->getConstructionCost() * p; subState->spendCPBudget(cost); @@ -738,6 +800,22 @@ void V3::EconomyManager::buildBuilding(const std::shared_ptr& building { subState->setResource("bg_agriculture", subState->getResource("bg_agriculture") - p); } + + // Track Demand + market.integrateBuilding(*building, + p, + defaultRatio, + estimatedPMs, + PMGroups, + PMs, + buildingGroups, + estimatedOwnershipFracs, + lawsMap, + techMap.getTechs(), + stateTraits, + popTypeLoader.getPopTypes(), + buildings, + subState); } void V3::EconomyManager::removeSubStateIfFinished(std::vector>& subStates, @@ -755,51 +833,6 @@ void V3::EconomyManager::removeSubStateIfFinished(std::vector& building, - const std::shared_ptr& sector, - const std::shared_ptr& subState, - const std::map& lawsMap, - const std::vector>& subStates) const -{ - // Packet size is the minimum of (Sector CP budget/cost, SubState CP budget/cost, SubState capacity, and our clustering metric) - const int sectorPacket = sector->getCPBudget() / building->getConstructionCost(); - const int subStatePacket = subState->getCPBudget() / building->getConstructionCost(); - const int capacityPacket = subState->getBuildingCapacity(*building, buildingGroups, lawsMap, techMap.getTechs(), stateTraits) - building->getLevel(); - const int clusterPacket = getClusterPacket(building->getConstructionCost(), subStates); - - const int packet = std::max(std::min({sectorPacket, subStatePacket, capacityPacket, clusterPacket}), 1); - - return packet; -} - -int V3::EconomyManager::getClusterPacket(const int baseCost, const std::vector>& subStates) const -{ - const int CPAll = std::accumulate(subStates.begin(), subStates.end(), 0, [](const int sum, const std::shared_ptr& subState) { - return sum + subState->getCPBudget(); - }); - const double CPMean = static_cast(CPAll) / static_cast(subStates.size()); - - - const int maxCP = subStates[0]->getCPBudget(); - const int minCP = std::max(subStates.back()->getCPBudget(), baseCost); - - // Default, when factor is 0 - int packet = static_cast(CPMean / baseCost); - const double factor = econDefines.getPacketFactor(); - if (factor < 0) - { - // Trends toward only building 1 building at a time - packet = static_cast(std::floor(CPMean * (1.0 + factor) + minCP * -factor) / baseCost); - } - if (factor > 0) - { - // Trends toward building as many buildings as the substate can get away with at a time - packet = static_cast(std::floor(CPMean * (1 - factor) + maxCP * factor) / baseCost); - } - - return packet; -} - std::map V3::EconomyManager::apportionInvestors(const int levels, const std::map& investorWeights, std::map& investorIOUs) const @@ -864,6 +897,65 @@ std::map V3::EconomyManager::apportionInvestors(const int leve return investorLevels; } +std::map> V3::EconomyManager::estimateInvestorBuildings(const Country& country) const +{ + std::map> buildingInvestorEstimates; + + for (const auto& building: ownershipLoader.getBuildingSectorMap() | std::views::keys) + { + double totalWeight = 0; + std::map investorWeights; + const auto& ownershipMap = ownershipLoader.getOwnershipsFromBuilding(building); + + for (const auto& [type, data]: ownershipMap) + { + if (data.recognized && country.getProcessedData().type != "recognized") + { + continue; + } + + totalWeight += data.weight; + if (const auto& findIter = type.find("building"); findIter != std::string::npos) + investorWeights[type] = data.weight; + } + for (const auto& type: investorWeights | std::views::keys) + { + investorWeights[type] /= totalWeight; + } + for (const auto& [type, weight]: investorWeights) + { + const auto& data = ownershipMap.at(type); + buildingInvestorEstimates[building][type] = weight - weight * (data.colonialFrac + data.financialCenterFrac); + } + } + return buildingInvestorEstimates; +} + +std::map V3::EconomyManager::calcInvestorFractions(const std::map& ownershipMap, const Country& country) +{ + std::map investorWeights; + + double totalWeight = 0; + for (const auto& [type, investorData]: ownershipMap) + { + if (investorData.recognized && country.getProcessedData().type != "recognized") + { + continue; + } + + investorWeights[type] = investorData.weight; + totalWeight += investorData.weight; + } + + // Turn weights into fractions + for (const auto& type: investorWeights | std::views::keys) + { + investorWeights[type] /= totalWeight; + } + + return investorWeights; +} + void V3::EconomyManager::loadTerrainModifierMatrices(const std::string& filePath) { Log(LogLevel::Info) << "-> Loading Terrain Modifier Matrices."; diff --git a/EU4ToVic3/Source/V3World/EconomyManager/EconomyManager.h b/EU4ToVic3/Source/V3World/EconomyManager/EconomyManager.h index fc217ada2..f88073814 100644 --- a/EU4ToVic3/Source/V3World/EconomyManager/EconomyManager.h +++ b/EU4ToVic3/Source/V3World/EconomyManager/EconomyManager.h @@ -6,6 +6,8 @@ #include "BuildingMapper/ProductionMethodMapper/ProductionMethodMapper.h" #include "ClayManager/State/StateModifier.h" #include "Configuration.h" +#include "CultureMapper/CultureDefinitionLoader/CultureDef.h" +#include "Demand/MarketTracker.h" #include "EconomyManager/Building/ProductionMethods/ProductionMethod.h" #include "EconomyManager/Building/ProductionMethods/ProductionMethodGroup.h" #include "Loaders/BuildingLoader/OwnershipLoader/OwnershipLoader.h" @@ -16,6 +18,7 @@ #include "Loaders/PopLoader/PopTypeLoader.h" #include "Loaders/TechLoader/TechLoader.h" #include "PoliticalManager/PoliticalManager.h" +#include "ReligionMapper/ReligionDefinitionLoader/ReligionDef.h" namespace V3 { @@ -29,18 +32,19 @@ class Sector; * Primarily handles buildings * 1. Load in centralized countries. * 2. Read in Mappers & Configs. - * 3. Prepare estimates + * 3. Prepare country specific estimates. Ownership fractions, PMs used, PM fractions, etc. * 4. Bureaucracy! Have to handle it separate for game balance. Hand out generation that ~ matches need. + * 4b. Ports! Easy for markets to end up cut off from your own market without giving each eligible state 1 port. * 5. For each centralized country get a CP budget based on fronter option. * 6. For each substate in a centralized country get a CP budget based on fronter option and terrain/state modifiers. * 7. Figure out the "national budget" of each country using the sector blueprints in NationalBudgetLoader. * 7b. NationalBudget is a list of sectors like 30% Farming, 25% Light Industry, etc. * 7c. Each Sector has a list of buildings that fall under it. - * 8. Each Substate scores buildings based on EU4 buildings, terrain, state modifiers, and other factors. + * 8. Each Substate scores buildings based on market demand, EU4 buildings, terrain, state modifiers, and other factors. * 8b. Only buildings that are valid (resource/tech-wise) are scored. * 9. A negotiation between the states and their country about what to build. * 9b. The state w/ the most CP asks to build it's highest scoring building. - * 9c. The country says that building is in sector A and as a country we have X CP to spend in that sector. + * 9c. The country says, that building is in sector A and as a country we have X CP to spend in that sector. * 9d. The state then builds as many buildings of that kind as it can, limited by capacity, packet size and sector CP. * 9e. A bunch of small details that make this flow until all CP is assigned. Repeat for each country. * @@ -56,9 +60,6 @@ class EconomyManager void loadCentralizedStates(const std::map>& countries); void loadMappersAndConfigs(const commonItems::ModFilesystem& modFS, const std::string& filePath = ""); - void establishBureaucracy(const PoliticalManager& politicalManager, const Vic3DefinesLoader& defines) const; - void hardcodePorts() const; - void assignCountryCPBudgets(Configuration::ECONOMY economyType, Configuration::STARTDATE startDate, const DatingData& dateData, @@ -66,7 +67,10 @@ class EconomyManager bool vn = false) const; void assignSubStateCPBudgets(Configuration::ECONOMY economyType) const; void balanceNationalBudgets() const; - void buildBuildings(const std::map& lawsMap) const; + void buildBuildings(const std::map& lawsMap, + const std::map& cultures, + const std::map& religions, + const Vic3DefinesLoader& defines) const; void investCapital(const std::map>& countries) const; void setPMs() const; @@ -75,6 +79,7 @@ class EconomyManager private: static double calculatePopDistanceFactor(int countryPopulation, double geoMeanPopulation); static double calculateDateFactor(Configuration::STARTDATE startDate, const DatingData& dateData); + static std::map calcInvestorFractions(const std::map& buildingOwnershipMap, const Country& country); // Budget fxns set weight for all countries, accumulates the total weight, and returns a modifier to the globalCP pool (if any). [[nodiscard]] std::pair countryBudgetCalcs(Configuration::ECONOMY economyType) const; // Return total weight, any special factors @@ -89,6 +94,14 @@ class EconomyManager [[nodiscard]] double calculateStateTraitMultiplier(const std::shared_ptr& subState) const; [[nodiscard]] double getDensityFactor(double perCapitaDev) const; + void establishBureaucracy(const std::shared_ptr& country, const std::map& lawsMap, const Vic3DefinesLoader& defines) const; + void hardcodePorts(const std::shared_ptr& country) const; + void integrateHardcodedBuildings(const std::shared_ptr& country, + double defaultRatio, + const std::map>& estimatedPMs, + const std::map& lawsMap, + const std::map& popTypes, + MarketTracker& market) const; void distributeBudget(double globalCP, double totalIndustryScore) const; [[nodiscard]] std::vector> prepareSubStatesByBudget(const std::shared_ptr& country, @@ -96,27 +109,29 @@ class EconomyManager void negotiateBuilding(const std::shared_ptr& subState, const std::map>& sectors, const std::map& lawsMap, - const std::vector>& subStates) const; + const std::vector>& subStates, + const std::map>& estimatedPMs, + const std::map>& estimatedOwnershipFracs, + double defaultRatio, + MarketTracker& marketTracker) const; [[nodiscard]] static std::shared_ptr getSectorWithMostBudget(const std::map>& sectors); void buildBuilding(const std::shared_ptr& building, const std::shared_ptr& subState, const std::shared_ptr& sector, const std::map& lawsMap, - const std::vector>& subStates) const; + const std::vector>& subStates, + const std::map>& estimatedPMs, + const std::map>& estimatedOwnershipFracs, + double defaultRatio, + MarketTracker& marketTracker) const; void removeSubStateIfFinished(std::vector>& subStates, const std::vector>::iterator& subState, const std::map& lawsMap) const; - [[nodiscard]] int determinePacketSize(const std::shared_ptr& building, - const std::shared_ptr& sector, - const std::shared_ptr& subState, - const std::map& lawsMap, - const std::vector>& subStates) const; - [[nodiscard]] int getClusterPacket(int baseCost, const std::vector>& subStates) const; - [[nodiscard]] std::map apportionInvestors(int levels, const std::map& investorWeights, std::map& investorIOUs) const; + [[nodiscard]] std::map> estimateInvestorBuildings(const Country& country) const; void loadTerrainModifierMatrices(const std::string& filePath = ""); void loadStateTraits(const commonItems::ModFilesystem& modFS); diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Packet.cpp b/EU4ToVic3/Source/V3World/EconomyManager/Packet.cpp new file mode 100644 index 000000000..40bf894a4 --- /dev/null +++ b/EU4ToVic3/Source/V3World/EconomyManager/Packet.cpp @@ -0,0 +1,51 @@ +#include "Packet.h" +#include +#include + +V3::Packet::Packet(const Building& building, + const int sectorBudget, + const double clusterFactor, + const SubState& subState, + const std::vector>& subStates, + const std::map& lawsMap, + const std::map& techMap, + const std::map& traitMap, + const BuildingGroups& buildingGroups) +{ + const int sectorPacket = sectorBudget / building.getConstructionCost(); + const int subStatePacket = subState.getCPBudget() / building.getConstructionCost(); + const int capacityPacket = subState.getBuildingCapacity(building, buildingGroups, lawsMap, techMap, traitMap) - building.getLevel(); + const int clusterPacket = getClusterPacket(building.getConstructionCost(), clusterFactor, subStates); + + size = std::max(std::min({sectorPacket, subStatePacket, capacityPacket, clusterPacket}), 1); +} + +int V3::Packet::getClusterPacket(const int baseCost, const double factor, const std::vector>& subStates) const +{ + if (subStates.empty()) + return 1; + + const int CPAll = std::accumulate(subStates.begin(), subStates.end(), 0, [](const int sum, const std::shared_ptr& subState) { + return sum + subState->getCPBudget(); + }); + const double CPMean = static_cast(CPAll) / static_cast(subStates.size()); + + const int maxCP = subStates[0]->getCPBudget(); + const int minCP = std::max(subStates.back()->getCPBudget(), baseCost); + + // Default, when factor is 0 + int packet = static_cast(CPMean / baseCost); + + if (factor < 0) + { + // Trends toward only building 1 building at a time + packet = static_cast(floor(CPMean * (1.0 + factor) + minCP * -factor) / baseCost); + } + if (factor > 0) + { + // Trends toward building as many buildings as the substate can get away with at a time + packet = static_cast(floor(CPMean * (1 - factor) + maxCP * factor) / baseCost); + } + + return packet; +} diff --git a/EU4ToVic3/Source/V3World/EconomyManager/Packet.h b/EU4ToVic3/Source/V3World/EconomyManager/Packet.h new file mode 100644 index 000000000..c7236d756 --- /dev/null +++ b/EU4ToVic3/Source/V3World/EconomyManager/Packet.h @@ -0,0 +1,39 @@ +#ifndef V3_PACKET_H +#define V3_PACKET_H +#include "Building/Building.h" +#include "ClayManager/State/SubState.h" + +/* A Packet is the limiting factor which determines how many levels of a building a substate can buy in one iteration. + * This prevents each substate from being too dominated by one type of industry. The result is simply the minimum of 4 independent factors + * + * 1. Sector Limit (How many buildings does the country as a whole need of this type, as defined by a configuration file?) + * 2. Budgetary (How many levels can the substate afford by spending all of its points?) + * 3. Capacity (Is this building type limited by resource availability or current laws?) + * 4. Clustering (How much do we want industries to concentrate in fewer states? Set by configuration file.) + */ + +namespace V3 +{ +class Packet +{ + public: + Packet(const Building& building, + int sectorBudget, + double clusterFactor, + const SubState& subState, + const std::vector>& subStates, + const std::map& lawsMap, + const std::map& techMap, + const std::map& traitMap, + const BuildingGroups& buildingGroups); + + [[nodiscard]] const auto& getSize() const { return size; } + + private: + int getClusterPacket(int baseCost, double factor, const std::vector>& subStates) const; + + int size = 1; +}; +} // namespace V3 + +#endif // V3_PACKET_H \ No newline at end of file diff --git a/EU4ToVic3/Source/V3World/Loaders/BuildingLoader/OwnershipLoader/OwnershipLoader.h b/EU4ToVic3/Source/V3World/Loaders/BuildingLoader/OwnershipLoader/OwnershipLoader.h index d63bf3d1b..5b8d9aea4 100644 --- a/EU4ToVic3/Source/V3World/Loaders/BuildingLoader/OwnershipLoader/OwnershipLoader.h +++ b/EU4ToVic3/Source/V3World/Loaders/BuildingLoader/OwnershipLoader/OwnershipLoader.h @@ -12,6 +12,7 @@ class OwnershipLoader: commonItems::parser void loadOwnership(const std::string& filePath); [[nodiscard]] const std::map& getOwnershipsFromBuilding(const std::string& building) const; + [[nodiscard]] const std::map& getBuildingSectorMap() const { return buildingSectorMap; } private: void registerKeys(); diff --git a/EU4ToVic3/Source/V3World/Loaders/DefinesLoader/Vic3DefinesLoader.h b/EU4ToVic3/Source/V3World/Loaders/DefinesLoader/Vic3DefinesLoader.h index 67fcb8338..83458db7c 100644 --- a/EU4ToVic3/Source/V3World/Loaders/DefinesLoader/Vic3DefinesLoader.h +++ b/EU4ToVic3/Source/V3World/Loaders/DefinesLoader/Vic3DefinesLoader.h @@ -35,7 +35,7 @@ class Vic3DefinesLoader: commonItems::parser double minimumInvestmentCost = 0; // The absolute minimum cost in BUR per investment level - double workingAdultRatioBase = 0.0; // Base ratio of working adults to dependents, this can be overridden by pop type definition and country modifiers. + double workingAdultRatioBase = 0.25; // Base ratio of working adults to dependents, this can be overridden by pop type definition and country modifiers. double dependentConsumptionRatio = 0.0; // Dependents consume this multiple of Needs compared to Working Adults. }; } // namespace V3 diff --git a/EU4ToVic3/Source/V3World/Loaders/PopLoader/PopTypeLoader.cpp b/EU4ToVic3/Source/V3World/Loaders/PopLoader/PopTypeLoader.cpp index 129ec00de..cd1e25726 100644 --- a/EU4ToVic3/Source/V3World/Loaders/PopLoader/PopTypeLoader.cpp +++ b/EU4ToVic3/Source/V3World/Loaders/PopLoader/PopTypeLoader.cpp @@ -10,6 +10,21 @@ void V3::PopTypeLoader::loadPopTypes(const commonItems::ModFilesystem& modFS) parseFile(fileName); } clearRegisteredKeywords(); + + // A copy of the default pop type. + if (popTypes.contains("laborers")) + { + PopType unemployed = popTypes.at("laborers"); + unemployed.setType("unemployed"); + popTypes.emplace("unemployed", unemployed); + } + else + { + PopType unemployed; + unemployed.setType("unemployed"); + unemployed.setStrata("poor"); + popTypes.emplace("unemployed", unemployed); + } } void V3::PopTypeLoader::registerKeys() diff --git a/EU4ToVic3/Source/V3World/PoliticalManager/Country/Country.cpp b/EU4ToVic3/Source/V3World/PoliticalManager/Country/Country.cpp index b7ed93b3c..c61158731 100644 --- a/EU4ToVic3/Source/V3World/PoliticalManager/Country/Country.cpp +++ b/EU4ToVic3/Source/V3World/PoliticalManager/Country/Country.cpp @@ -128,11 +128,37 @@ bool V3::Country::hasAnyOfTech(const std::set& techs) const return true; } - return std::ranges::any_of(techs, [&](const std::string& tech) { + return std::ranges::any_of(techs, [this](const std::string& tech) { return processedData.techs.contains(tech); }); } +bool V3::Country::hasAnyOfLawUnlocking(const std::set& laws) const +{ + if (laws.empty()) + { + return true; + } + + return std::ranges::any_of(laws, [this](const std::string& targetLaw) { + return processedData.laws.contains(targetLaw); + }); +} + +bool V3::Country::hasAnyOfLawBlocking(const std::set& laws) const +{ + if (laws.empty()) + { + return false; + } + + return std::ranges::any_of(laws, [this](const std::string& targetLaw) { + return processedData.laws.contains(targetLaw); + }); +} + + + int V3::Country::getGovBuildingMax(const std::string& building, const std::map& lawsMap, const std::map& techMap) const { const auto tech = std::accumulate(processedData.techs.begin(), processedData.techs.end(), 0, [building, techMap](const int sum, const std::string& tech) { @@ -183,20 +209,18 @@ void V3::Country::distributeGovAdmins(const double target, const double stateTarget = target * popProportion; // Calc levels for generation - int levels = static_cast(stateTarget * 100 / 101 / PMGeneration); - double generation = levels * PMGeneration; - if (const int throughputMax = getThroughputMax(techMap); levels > throughputMax) + const double scaledLevels = (-1 + std::sqrt(1 + 0.04 * stateTarget / PMGeneration)) * 50; + int levels = static_cast(std::round(scaledLevels)); + double generation = levels * (0.01 * levels + 1) * PMGeneration; + + if (const int throughputMax = getThroughputMax(techMap); scaledLevels > throughputMax) { - levels = static_cast(stateTarget / (PMGeneration + PMGeneration * (throughputMax / 100.0))); + levels = static_cast(stateTarget / PMGeneration / (throughputMax / 100.0)); generation = levels * PMGeneration * (1 + throughputMax / 100.0); } const auto govAdmin = std::make_shared(blueprint); govAdmin->setLevel(levels); - - // TODO(Gawquon): Still need to decide on this fxn being before or after the building negotiation. - // govAdmin->addInvestor(levels, "national_service", substate->getHomeStateName(), this->tag); - substate->addBuilding(govAdmin); generated += generation; @@ -1057,6 +1081,48 @@ int V3::Country::getPopCount(const std::vector>& theSu }); } +// Returns the percentage breakdown of cultures +std::map V3::Country::getCultureBreakdown() const +{ + int total = 0; + std::map cultureData; + for (const auto& subState: subStates) + { + for (const auto& [culture, amount]: subState->getSubStatePops().getCultureCounts()) + { + cultureData[culture] += amount; + total += amount; + } + } + + total = total == 0 ? 1 : total; + for (auto& amount: cultureData | std::views::values) + { + amount /= total; + } + return cultureData; +} + +// Returns a map of each job as a percentage of all jobs in the market. +std::map V3::Country::getJobBreakdown() const +{ + std::map jobBreakdown; + + for (const auto& subState: subStates) + { + for (const auto& [job, amount]: subState->getEstimatedJobs()) + { + jobBreakdown[job] += amount; + } + } + for (auto [job, amount]: jobBreakdown) + { + jobBreakdown[job] = amount / getPopCount(); + } + + return jobBreakdown; +} + int V3::Country::getVanillaPopCount() const { return std::accumulate(subStates.begin(), subStates.end(), 0, [](int sum, const auto& substate) { diff --git a/EU4ToVic3/Source/V3World/PoliticalManager/Country/Country.h b/EU4ToVic3/Source/V3World/PoliticalManager/Country/Country.h index 0cc0428e2..4920586fb 100644 --- a/EU4ToVic3/Source/V3World/PoliticalManager/Country/Country.h +++ b/EU4ToVic3/Source/V3World/PoliticalManager/Country/Country.h @@ -179,6 +179,8 @@ class Country: commonItems::parser [[nodiscard]] int getVanillaPopCount() const; // vanilla pop count of all the provinces this country holds [[nodiscard]] int getIncorporatedPopCount() const; [[nodiscard]] static int getPopCount(const std::vector>& theSubStates); + [[nodiscard]] std::map getCultureBreakdown() const; + [[nodiscard]] std::map getJobBreakdown() const; void determineWesternizationWealthAndLiteracy(double topTech, double topInstitutions, @@ -225,6 +227,8 @@ class Country: commonItems::parser [[nodiscard]] double getTechInfraMult(const std::map& techMap) const; [[nodiscard]] int getThroughputMax(const std::map& techMap) const; [[nodiscard]] bool hasAnyOfTech(const std::set& techs) const; + [[nodiscard]] bool hasAnyOfLawUnlocking(const std::set& laws) const; + [[nodiscard]] bool hasAnyOfLawBlocking(const std::set& laws) const; [[nodiscard]] int getGovBuildingMax(const std::string& building, const std::map& lawsMap, const std::map& techMap) const; diff --git a/EU4ToVic3/Source/V3World/PopManager/Pops/PopType.h b/EU4ToVic3/Source/V3World/PopManager/Pops/PopType.h index 89cfe3a39..92dbc702a 100644 --- a/EU4ToVic3/Source/V3World/PopManager/Pops/PopType.h +++ b/EU4ToVic3/Source/V3World/PopManager/Pops/PopType.h @@ -11,6 +11,7 @@ class PopType: commonItems::parser explicit PopType(std::istream& theStream); void setType(const std::string& theType) { type = theType; } + void setStrata(const std::string& theStrata) { strata = theStrata; } [[nodiscard]] const auto& getType() const { return type; } [[nodiscard]] const auto& getConsumptionRate() const { return consumptionRate; } diff --git a/EU4ToVic3/Source/V3World/PopManager/Pops/SubStatePops.cpp b/EU4ToVic3/Source/V3World/PopManager/Pops/SubStatePops.cpp index 1204f657f..1c51a4d0a 100644 --- a/EU4ToVic3/Source/V3World/PopManager/Pops/SubStatePops.cpp +++ b/EU4ToVic3/Source/V3World/PopManager/Pops/SubStatePops.cpp @@ -69,6 +69,16 @@ std::optional V3::SubStatePops::getDominantReligion() const return highest->first; } +std::map V3::SubStatePops::getCultureCounts() const +{ + std::map cultureCounts; + for (const auto& pop: pops) + { + cultureCounts[pop.getCulture()] += pop.getSize(); + } + return cultureCounts; +} + void V3::SubStatePops::multiplyPops(double factor) { std::vector replacementPops; diff --git a/EU4ToVic3/Source/V3World/PopManager/Pops/SubStatePops.h b/EU4ToVic3/Source/V3World/PopManager/Pops/SubStatePops.h index 3e39246eb..5af2cfc87 100644 --- a/EU4ToVic3/Source/V3World/PopManager/Pops/SubStatePops.h +++ b/EU4ToVic3/Source/V3World/PopManager/Pops/SubStatePops.h @@ -25,6 +25,7 @@ class SubStatePops [[nodiscard]] std::optional getDominantCulture() const; [[nodiscard]] std::optional getDominantReligion() const; + [[nodiscard]] std::map getCultureCounts() const; private: std::string tag; diff --git a/EU4ToVic3/Source/V3World/V3World.cpp b/EU4ToVic3/Source/V3World/V3World.cpp index 11a2b465a..63bc1548a 100644 --- a/EU4ToVic3/Source/V3World/V3World.cpp +++ b/EU4ToVic3/Source/V3World/V3World.cpp @@ -220,16 +220,17 @@ V3::World::World(const Configuration& configuration, const EU4::World& sourceWor Log(LogLevel::Progress) << "71 %"; economyManager.loadCentralizedStates(politicalManager.getCountries()); Log(LogLevel::Progress) << "72 %"; - economyManager.establishBureaucracy(politicalManager, definesLoader); - Log(LogLevel::Progress) << "73 %"; - economyManager.hardcodePorts(); - Log(LogLevel::Progress) << "74 %"; economyManager.assignCountryCPBudgets(configBlock.economy, configBlock.startDate, datingData, politicalManager, configBlock.vn); + Log(LogLevel::Progress) << "73 %"; economyManager.balanceNationalBudgets(); - Log(LogLevel::Progress) << "75 %"; + Log(LogLevel::Progress) << "74 %"; economyManager.assignSubStateCPBudgets(configBlock.economy); + Log(LogLevel::Progress) << "75 %"; + economyManager.buildBuildings(politicalManager.getLawsMap(), + cultureMapper.getV3CultureDefinitions(), + religionMapper.getV3ReligionDefinitions(), + definesLoader); Log(LogLevel::Progress) << "76 %"; - economyManager.buildBuildings(politicalManager.getLawsMap()); economyManager.investCapital(politicalManager.getCountries()); economyManager.setPMs(); diff --git a/EU4ToVic3Tests/EU4ToVic3Tests.vcxproj b/EU4ToVic3Tests/EU4ToVic3Tests.vcxproj index 609203197..c46705134 100644 --- a/EU4ToVic3Tests/EU4ToVic3Tests.vcxproj +++ b/EU4ToVic3Tests/EU4ToVic3Tests.vcxproj @@ -200,12 +200,16 @@ + + + + diff --git a/EU4ToVic3Tests/EU4ToVic3Tests.vcxproj.filters b/EU4ToVic3Tests/EU4ToVic3Tests.vcxproj.filters index d3c67fac0..ff2bc4b83 100644 --- a/EU4ToVic3Tests/EU4ToVic3Tests.vcxproj.filters +++ b/EU4ToVic3Tests/EU4ToVic3Tests.vcxproj.filters @@ -604,6 +604,18 @@ V3WorldTests\EconomyManagerTests\DemandTests + + V3WorldTests\EconomyManagerTests\BuildingTests + + + V3WorldTests\EconomyManagerTests\DemandTests + + + V3WorldTests\EconomyManagerTests + + + V3WorldTests\EconomyManagerTests\DemandTests + diff --git a/EU4ToVic3Tests/MapperTests/BuildingMapperTests/ProductionMethodEntryTests.cpp b/EU4ToVic3Tests/MapperTests/BuildingMapperTests/ProductionMethodEntryTests.cpp index 04755c1b2..4ad2cccc1 100644 --- a/EU4ToVic3Tests/MapperTests/BuildingMapperTests/ProductionMethodEntryTests.cpp +++ b/EU4ToVic3Tests/MapperTests/BuildingMapperTests/ProductionMethodEntryTests.cpp @@ -9,6 +9,7 @@ TEST(Mappers_ProductionMethodEntryTests, DefaultsDefaultToDefaults) EXPECT_TRUE(rule.pm.empty()); EXPECT_DOUBLE_EQ(1, rule.percent); + EXPECT_FALSE(rule.lawBound); } TEST(V3World_ProductionMethodEntryTests, PMCanBeLoaded) @@ -16,9 +17,12 @@ TEST(V3World_ProductionMethodEntryTests, PMCanBeLoaded) std::stringstream input; input << "name = pm_hardwood\n "; input << "percent = 0.33\n "; + input << "law_bound = yes\n"; + const mappers::ProductionMethodEntry entry(input); const auto& rule = entry.getRule(); EXPECT_EQ("pm_hardwood", rule.pm); EXPECT_DOUBLE_EQ(0.33, rule.percent); + EXPECT_TRUE(rule.lawBound); } \ No newline at end of file diff --git a/EU4ToVic3Tests/MapperTests/BuildingMapperTests/ProductionMethodMapperTests.cpp b/EU4ToVic3Tests/MapperTests/BuildingMapperTests/ProductionMethodMapperTests.cpp index ac025576d..b1785f45d 100644 --- a/EU4ToVic3Tests/MapperTests/BuildingMapperTests/ProductionMethodMapperTests.cpp +++ b/EU4ToVic3Tests/MapperTests/BuildingMapperTests/ProductionMethodMapperTests.cpp @@ -48,6 +48,10 @@ const std::set lumberPmgs = {"pmg_base_building_logging_camp", "pmg_equipment", "pmg_transportation_building_logging_camp", "pmg_ownership_capital_building_logging_camp"}; + +const std::set subsistencePmgs = {"pmg_base_building_subsistence_farms", + "pmg_home_workshops_building_subsistence_farms", + "pmg_serfdom_building_subsistence_farms"}; } // namespace TEST(Mappers_ProductionMethodMapperTests, RulesCanBeLoaded) @@ -60,7 +64,9 @@ TEST(Mappers_ProductionMethodMapperTests, RulesCanBeLoaded) EXPECT_FALSE(mapper.getRules().contains("building_government_administration")); EXPECT_THAT(mapper.getRules().at("building_logging_camp"), - testing::UnorderedElementsAre(mappers::PMRule{"pm_saw_mills"}, mappers::PMRule{"pm_hardwood", 0.65})); + testing::UnorderedElementsAre(mappers::PMRule{"pm_saw_mills"}, + mappers::PMRule{"pm_hardwood", 0.65}, + mappers::PMRule{"pm_merchant_guilds_building_logging_camp", 1, true})); } TEST(Mappers_ProductionMethodMapperTests, ApplyRules) { @@ -191,4 +197,124 @@ TEST(Mappers_ProductionMethodMapperTests, ApplyRulesUnderTeched) // No tech for sawmills EXPECT_EQ(0, std::accumulate(country.getSubStates().begin(), country.getSubStates().end(), 0, sumSawmills)); EXPECT_EQ(123, std::accumulate(country.getSubStates().begin(), country.getSubStates().end(), 0, sumForestry)); +} +TEST(Mappers_ProductionMethodMapperTests, EstimatesWalkPMList) +{ + mappers::ProductionMethodMapper mapper; + mapper.loadRules("TestFiles/configurables/economy/production_method_rules.txt"); + + // Set up countries with no, partial, and full tech + V3::Country countryNoTech; + V3::Country countrySomeTech; + V3::Country countryAllTech; + + countrySomeTech.addTech("steelworking"); + countryAllTech.addTech("steelworking"); + countryAllTech.addTech("electrical_generation"); + + // Prepare Buildings + V3::Building lumberCamp; + lumberCamp.setName("building_logging_camp"); + lumberCamp.setPMGroups(lumberPmgs); + + std::map buildingMap; + buildingMap.emplace("building_logging_camp", lumberCamp); + + // Load in PM and PMGroup definitions + const auto [PMs, PMGroups] = prepPMData(); + + const auto& lowPMEstimates = mapper.estimatePMs(countryNoTech, PMs, PMGroups, buildingMap); + const auto& midPMEstimates = mapper.estimatePMs(countrySomeTech, PMs, PMGroups, buildingMap); + const auto& highPMEstimates = mapper.estimatePMs(countryAllTech, PMs, PMGroups, buildingMap); + + // The rule says advance to saw_mills, not electric_sawmills + + EXPECT_THAT(lowPMEstimates, testing::Contains(testing::Pair("pmg_base_building_logging_camp", std::make_tuple(0, 1.0)))); + EXPECT_THAT(midPMEstimates, testing::Contains(testing::Pair("pmg_base_building_logging_camp", std::make_tuple(1, 1.0)))); + EXPECT_THAT(highPMEstimates, testing::Contains(testing::Pair("pmg_base_building_logging_camp", std::make_tuple(1, 1.0)))); + + /* + * pmg_base_building_logging_camp = { + * production_methods = { + * pm_simple_forestry // countryNoTech + * pm_saw_mills // countrySomeTech & countryAllTech + * pm_electric_saw_mills // not countryAllTech because the rule in mapper.loadRules() limits the highest PM to saw_mills + * } + * } + */ +} +TEST(Mappers_ProductionMethodMapperTests, ConfigFlagSwitchesWalkType) +{ + mappers::ProductionMethodMapper mapper; + mapper.loadRules("TestFiles/configurables/economy/production_method_rules.txt"); + + // Set up a country using command economy to use the 4th pm in the pmg. + V3::Country countryNoTech; + countryNoTech.addLaw("law_command_economy"); + + // Prepare Buildings + V3::Building lumberCamp; + lumberCamp.setName("building_logging_camp"); + lumberCamp.setPMGroups(lumberPmgs); + + std::map buildingMap; + buildingMap.emplace("building_logging_camp", lumberCamp); + + // Load in PM and PMGroup definitions + const auto [PMs, PMGroups] = prepPMData(); + + const auto& lawPMEstimates = mapper.estimatePMs(countryNoTech, PMs, PMGroups, buildingMap); + + EXPECT_THAT(lawPMEstimates, testing::Contains(testing::Pair("pmg_ownership_capital_building_logging_camp", std::make_tuple(3, 1.0)))); + + /* + * pmg_ownership_capital_building_logging_camp = { + * production_methods = { + * pm_merchant_guilds_building_logging_camp + * pm_privately_owned_building_logging_camp + * pm_publicly_traded_building_logging_camp + * pm_government_run_building_logging_camp // this one + * pm_worker_cooperative_building_logging_camp + * } + * } + */ +} +TEST(Mappers_ProductionMethodMapperTests, EstimatesWalkPMListLawMethod) +{ + mappers::ProductionMethodMapper mapper; + mapper.loadRules("TestFiles/configurables/economy/production_method_rules.txt"); + + // Set up countries with different peasant laws + V3::Country countrySerfdom; + V3::Country countryHomesteading; + + countrySerfdom.addLaw("law_serfdom"); + countryHomesteading.addLaw("law_homesteading"); + + // Prepare Buildings + V3::Building subsistence; + subsistence.setName("building_subsistence_farms"); + subsistence.setPMGroups(subsistencePmgs); + + std::map buildingMap; + buildingMap.emplace("building_subsistence_farms", subsistence); + + // Load in PM and PMGroup definitions + const auto [PMs, PMGroups] = prepPMData(); + + const auto& serfPMEstimates = mapper.estimatePMs(countrySerfdom, PMs, PMGroups, buildingMap); + const auto& homesteadPMEstimates = mapper.estimatePMs(countryHomesteading, PMs, PMGroups, buildingMap); + + EXPECT_THAT(serfPMEstimates, testing::Contains(testing::Pair("pmg_serfdom_building_subsistence_farms", std::make_tuple(1, 1.0)))); + EXPECT_THAT(homesteadPMEstimates, testing::Contains(testing::Pair("pmg_serfdom_building_subsistence_farms", std::make_tuple(2, 1.0)))); + + /* + * pmg_serfdom_building_subsistence_farms = { + * production_methods = { + * pm_serfdom_no + * pm_serfdom // countrySerfdom + * pm_homesteading_building_subsistence // countryHomesteading + * } + * } + */ } \ No newline at end of file diff --git a/EU4ToVic3Tests/TestFiles/configurables/economy/production_method_rules.txt b/EU4ToVic3Tests/TestFiles/configurables/economy/production_method_rules.txt index 1aacf6868..8154855ea 100644 --- a/EU4ToVic3Tests/TestFiles/configurables/economy/production_method_rules.txt +++ b/EU4ToVic3Tests/TestFiles/configurables/economy/production_method_rules.txt @@ -1,11 +1,13 @@ # Linked buildings will walk towards the PMs specified until the PM is reached. or the next step is invalid due to tech. +# Law-bound PMs (Like ownership PMs) will walk towards the first PM allowed by law. These should be flagged with law_bound = yes. # The percent is the percent of building levels that will try to move off of default to adopt the defined PM. It is assumed to be 1(100%) unless specified. building_government_administration = { - pm = { name = pm_vertical_filing_cabinets } + pm = { name = pm_vertical_filing_cabinets } } building_logging_camp = { - pm = { name = pm_saw_mills } - pm = { name = pm_hardwood percent = 0.65 } + pm = { name = pm_saw_mills } + pm = { name = pm_hardwood percent = 0.65 } + pm = { name = pm_merchant_guilds_building_logging_camp law_bound = yes } } \ No newline at end of file diff --git a/EU4ToVic3Tests/TestFiles/vic3installation/game/common/production_method_groups/test_pm_groups.txt b/EU4ToVic3Tests/TestFiles/vic3installation/game/common/production_method_groups/test_pm_groups.txt index 11a934d03..67f56b33a 100644 --- a/EU4ToVic3Tests/TestFiles/vic3installation/game/common/production_method_groups/test_pm_groups.txt +++ b/EU4ToVic3Tests/TestFiles/vic3installation/game/common/production_method_groups/test_pm_groups.txt @@ -69,4 +69,28 @@ pmg_ownership_capital_building_logging_camp = { pm_government_run_building_logging_camp pm_worker_cooperative_building_logging_camp } +} + +pmg_serfdom_building_subsistence_farms = { + texture = "gfx/interface/icons/generic_icons/mixed_icon_base.dds" + production_methods = { + pm_serfdom_no + pm_serfdom + pm_homesteading_building_subsistence + } +} + +pmg_base_building_subsistence_farms = { + texture = "gfx/interface/icons/generic_icons/mixed_icon_base.dds" + production_methods = { + default_building_subsistence_farms + } +} + +pmg_home_workshops_building_subsistence_farms = { + texture = "gfx/interface/icons/generic_icons/mixed_icon_refining.dds" + production_methods = { + pm_home_workshops_no_building_subsistence_farms + pm_home_workshops_building_subsistence_farms + } } \ No newline at end of file diff --git a/EU4ToVic3Tests/TestFiles/vic3installation/game/common/production_methods/test_pms.txt b/EU4ToVic3Tests/TestFiles/vic3installation/game/common/production_methods/test_pms.txt index fc4ced085..1911ad429 100644 --- a/EU4ToVic3Tests/TestFiles/vic3installation/game/common/production_methods/test_pms.txt +++ b/EU4ToVic3Tests/TestFiles/vic3installation/game/common/production_methods/test_pms.txt @@ -358,4 +358,102 @@ pm_worker_cooperative_building_logging_camp = { building_machinists_shares_add = 1 } } +} + +pm_serfdom_no = { + texture = "gfx/interface/icons/production_method_icons/no_serfdom.dds" + unlocking_laws = { + law_tenant_farmers + law_commercialized_agriculture + } + + building_modifiers = { + unscaled = { + building_subsistence_output_add = 4 + } + } +} + +pm_homesteading_building_subsistence = { + texture = "gfx/interface/icons/production_method_icons/homesteading.dds" + unlocking_laws = { + law_homesteading + } + + building_modifiers = { + workforce_scaled = { + goods_output_grain_add = 0.25 + } + + unscaled = { + building_subsistence_output_add = 4 + } + + level_scaled = { + building_employment_peasants_add = -100 + building_employment_farmers_add = 100 + } + } +} + +pm_serfdom = { + texture = "gfx/interface/icons/production_method_icons/serfdom.dds" + unlocking_laws = { + law_serfdom + } + building_modifiers = { + workforce_scaled = { + goods_output_grain_add = 0.5 # 10 + } + + unscaled = { + building_subsistence_output_add = 3 + } + } +} +default_building_subsistence_farms = { + texture = "gfx/interface/icons/production_method_icons/subsistence_farming.dds" + + building_modifiers = { + workforce_scaled = { # 55 + goods_output_grain_add = 1.0 # 20 + goods_output_fabric_add = 0.5 # 10 + goods_output_wood_add = 0.5 # 10 + goods_output_services_add = 0.5 # 15 + } + + level_scaled = { + building_employment_peasants_add = 5000 + } + } +} + +pm_home_workshops_no_building_subsistence_farms = { + texture = "gfx/interface/icons/production_method_icons/no_home_workshops.dds" + unlocking_laws = { + law_collectivized_agriculture + } + + building_modifiers = { # 20 + unscaled = { + goods_output_grain_add = 0.5 # 10 + goods_output_fabric_add = 0.25 # 5 + goods_output_wood_add = 0.25 # 5 + } + } +} + +pm_home_workshops_building_subsistence_farms = { + texture = "gfx/interface/icons/production_method_icons/home_workshops.dds" + disallowing_laws = { + law_collectivized_agriculture + } + + building_modifiers = { + workforce_scaled = { # 22.5 + goods_output_clothes_add = 0.25 # 7.5 + goods_output_furniture_add = 0.25 # 7.5 + goods_output_liquor_add = 0.25 # 7.5 + } + } } \ No newline at end of file diff --git a/EU4ToVic3Tests/V3WorldTests/ClayManagerTests/StateTests/StateModifierTests.cpp b/EU4ToVic3Tests/V3WorldTests/ClayManagerTests/StateTests/StateModifierTests.cpp index 43c4d5dba..b67aa8779 100644 --- a/EU4ToVic3Tests/V3WorldTests/ClayManagerTests/StateTests/StateModifierTests.cpp +++ b/EU4ToVic3Tests/V3WorldTests/ClayManagerTests/StateTests/StateModifierTests.cpp @@ -78,8 +78,8 @@ TEST(V3World_StateModifierTests, BuildingGroupModifiersAreSet) V3::StateModifier modifier; modifier.loadStateModifier(input); - EXPECT_DOUBLE_EQ(0.2, modifier.getBuildingGroupModifier("bg_agri", V3::BuildingGroups()).value()); - EXPECT_DOUBLE_EQ(0.3, modifier.getBuildingGroupModifier("bg_fish", V3::BuildingGroups()).value()); + EXPECT_DOUBLE_EQ(0.2, modifier.getBuildingGroupModifier("bg_agri", V3::BuildingGroups())); + EXPECT_DOUBLE_EQ(0.3, modifier.getBuildingGroupModifier("bg_fish", V3::BuildingGroups())); } TEST(V3World_StateModifierTests, BuildingModifiersAreSet) @@ -98,6 +98,7 @@ TEST(V3World_StateModifierTests, BuildingModifiersAreSet) EXPECT_DOUBLE_EQ(0.3, modifier.getBuildingModifier("building_factory").value()); } + TEST(V3World_StateModifierTests, GoodsModifiersAreSet) { std::stringstream input; @@ -129,7 +130,7 @@ TEST(V3World_StateModifierTests, RegexDontCollide) EXPECT_DOUBLE_EQ(0.2, modifier.getBuildingModifier("building_house").value()); EXPECT_DOUBLE_EQ(0.3, modifier.getGoodsModifier("building_output_coconut").value()); - EXPECT_DOUBLE_EQ(0.4, modifier.getBuildingGroupModifier("bg_fish", V3::BuildingGroups()).value()); + EXPECT_DOUBLE_EQ(0.4, modifier.getBuildingGroupModifier("bg_fish", V3::BuildingGroups())); } TEST(V3World_StateModifierTests, GettingBuildingGroupDataTravelsHeirarchy) @@ -147,7 +148,7 @@ TEST(V3World_StateModifierTests, GettingBuildingGroupDataTravelsHeirarchy) buildingGroupLoader.loadBuildingGroups(modFS); auto buildingGroups = buildingGroupLoader.getBuildingGroups(); - EXPECT_DOUBLE_EQ(0.2, modifier.getBuildingGroupModifier("bg_light_industry", buildingGroups).value_or(0)); + EXPECT_DOUBLE_EQ(0.2, modifier.getBuildingGroupModifier("bg_light_industry", buildingGroups)); } TEST(V3World_StateModifierTests, GetAllBonusesCombinesLikeBonuses) @@ -168,4 +169,32 @@ TEST(V3World_StateModifierTests, GetAllBonusesCombinesLikeBonuses) EXPECT_DOUBLE_EQ(0.5, V3::StateModifier::getAllBonuses(modifier.getBuildingGroupModifiersMap())); EXPECT_DOUBLE_EQ(0, V3::StateModifier::getAllBonuses(modifier.getBuildingModifiersMap())); EXPECT_DOUBLE_EQ(0.3, V3::StateModifier::getAllBonuses(modifier.getGoodsModifiersMap())); +} + +TEST(V3World_StateModifierTests, GivenBuildingAllModifiersAreSummed) +{ + std::stringstream buildingInput; + buildingInput << "building_group = bg_light_industry\n"; + + V3::Building building; + building.loadBuilding(buildingInput, {}); + building.setName("building_factory"); + + std::stringstream modInput; + modInput << "\ticon = \"gfx/ignore/me.dds\"\n"; + modInput << "\tmodifier = {\n"; + modInput << "\t\tbuilding_house_throughput_mult = 0.2\n"; + modInput << "\t\tbuilding_factory_throughput_mult = 0.3\n"; + modInput << "\t\tbuilding_group_bg_manufacturing_throughput_mult = 0.4\n"; + modInput << "\t\tbuilding_group_bg_light_industry_throughput_mult = 0.5\n"; + modInput << "\t}\n"; + + V3::BuildingGroupLoader buildingGroupLoader; + buildingGroupLoader.loadBuildingGroups(modFS); + auto buildingGroups = buildingGroupLoader.getBuildingGroups(); + + V3::StateModifier modifier; + modifier.loadStateModifier(modInput); + + EXPECT_DOUBLE_EQ(1.2, modifier.calcBuildingModifiers(building, buildingGroups)); } \ No newline at end of file diff --git a/EU4ToVic3Tests/V3WorldTests/ClayManagerTests/StateTests/StateTests.cpp b/EU4ToVic3Tests/V3WorldTests/ClayManagerTests/StateTests/StateTests.cpp index d1e81eb46..e87d2727a 100644 --- a/EU4ToVic3Tests/V3WorldTests/ClayManagerTests/StateTests/StateTests.cpp +++ b/EU4ToVic3Tests/V3WorldTests/ClayManagerTests/StateTests/StateTests.cpp @@ -19,6 +19,17 @@ TEST(V3World_StateTests, nameCanBeSetAndRetrieved) EXPECT_EQ("test_name", state.getName()); } +TEST(V3World_StateTests, stateSubsistenceBuildingLoads) +{ + std::stringstream input; + input << "id = 3002\n"; + input << "subsistence_building = building_subsistence_farms\n "; + V3::State state; + state.loadState(input); + + EXPECT_EQ(state.getSubsistenceBuilding(), "building_subsistence_farms"); +} + TEST(V3World_StateTests, stateCanInitializeProvinces) { std::stringstream input; diff --git a/EU4ToVic3Tests/V3WorldTests/ClayManagerTests/StateTests/SubStateTests.cpp b/EU4ToVic3Tests/V3WorldTests/ClayManagerTests/StateTests/SubStateTests.cpp index cd17f1f29..0e2c89eb7 100644 --- a/EU4ToVic3Tests/V3WorldTests/ClayManagerTests/StateTests/SubStateTests.cpp +++ b/EU4ToVic3Tests/V3WorldTests/ClayManagerTests/StateTests/SubStateTests.cpp @@ -413,18 +413,4 @@ TEST(V3World_SubStateTests, AddBuilding) EXPECT_EQ(1, substate.getBuildings()[0]->getLevel()); EXPECT_EQ("port", substate.getBuildings()[1]->getName()); EXPECT_EQ(2, substate.getBuildings()[1]->getLevel()); -} - -TEST(DISABLED_V3World_SubStateTests, WeightBuildings) -{ - auto substate = V3::SubState(); - - EXPECT_FALSE(substate.getBuildings().empty()); -} - -TEST(DISABLED_V3World_SubStateTests, CalcBuildingWeight) -{ - auto substate = V3::SubState(); - - EXPECT_FALSE(substate.getBuildings().empty()); } \ No newline at end of file diff --git a/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/BuildingTests/BuildingGroupsTests.cpp b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/BuildingTests/BuildingGroupsTests.cpp index 63eea2d4f..c5062c7bf 100644 --- a/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/BuildingTests/BuildingGroupsTests.cpp +++ b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/BuildingTests/BuildingGroupsTests.cpp @@ -62,4 +62,15 @@ TEST(V3World_BuildingGroupsTests, InvalidGroupReturnsNullAncestor) const auto buildingGroups = buildingGroupLoader.getBuildingGroups(); EXPECT_EQ(std::nullopt, buildingGroups.getAncestralGroup("bg_none")); +} + +TEST(V3World_BuildingGroupsTests, UrbanizationLooksThroughHeirarchy) +{ + V3::BuildingGroupLoader buildingGroupLoader; + buildingGroupLoader.loadBuildingGroups(modFS); + const auto buildingGroups = buildingGroupLoader.getBuildingGroups(); + + EXPECT_EQ(0, buildingGroups.getUrbanization("bg_manufacturing")); + EXPECT_EQ(20, buildingGroups.getUrbanization("bg_light_industry")); + EXPECT_EQ(30, buildingGroups.getUrbanization("bg_giga_industry")); } \ No newline at end of file diff --git a/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/BuildingTests/BuildingResourcesTests.cpp b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/BuildingTests/BuildingResourcesTests.cpp new file mode 100644 index 000000000..ea15b5cc8 --- /dev/null +++ b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/BuildingTests/BuildingResourcesTests.cpp @@ -0,0 +1,90 @@ +#include "EconomyManager/Building/BuildingResources.h" +#include "gtest/gtest.h" +#include + +namespace +{ +std::tuple, std::map> loadPMData() +{ + std::stringstream inputPMGroupOne; + std::stringstream inputPMGroupTwo; + + inputPMGroupOne << "production_methods = { a c }\n"; + inputPMGroupTwo << "production_methods = { b }\n"; + + std::stringstream inputPMA; + std::stringstream inputPMB; + std::stringstream inputPMC; + + inputPMA << "building_modifiers = {\n"; + inputPMA << "workforce_scaled = { goods_input_wood_add = 10 goods_output_coal_add = 5 }\n"; + inputPMA << "level_scaled = { building_employment_laborers_add = 4500 building_employment_machinists_add = 500 }\n"; + inputPMA << "}\n"; + + inputPMB << "building_modifiers = {\n"; + inputPMB << "workforce_scaled = { goods_input_wood_add = -5 goods_input_iron_add = 5 }\n"; + inputPMB << "level_scaled = { building_employment_laborers_add = -1000 }\n"; + inputPMB << "}\n"; + + inputPMC << "building_modifiers = {\n"; + inputPMC << "workforce_scaled = { goods_input_wood_add = 20 goods_output_coal_add = 15 }\n"; + inputPMC << "level_scaled = { building_employment_laborers_add = 4000 building_employment_shopkeepers_add = 500 }\n"; + inputPMC << "}\n"; + + V3::ProductionMethodGroup one, two; + one.loadProductionMethodGroup(inputPMGroupOne); + two.loadProductionMethodGroup(inputPMGroupTwo); + + V3::ProductionMethod a, b, c; + a.loadProductionMethod(inputPMA); + b.loadProductionMethod(inputPMB); + c.loadProductionMethod(inputPMC); + + return {{{"one", one}, {"two", two}}, {{"a", a}, {"b", b}, {"c", c}}}; +} +} // namespace +TEST(V3World_BuildingResources, DefaultsDefaultToDefaults) +{ + V3::BuildingResources buildingResources; + + EXPECT_TRUE(buildingResources.getJobs().empty()); + EXPECT_TRUE(buildingResources.getInputs().empty()); + EXPECT_TRUE(buildingResources.getOutputs().empty()); +} + +TEST(V3World_BuildingResources, NoRuleLoadsDefaultPMResources) +{ + const auto& [PMGroups, PMs] = loadPMData(); + + V3::BuildingResources buildingResources; + buildingResources.evaluateResources({"one", "two"}, {}, PMs, PMGroups); + + EXPECT_THAT(buildingResources.getJobs(), testing::UnorderedElementsAre(testing::Pair("laborers", 3500), testing::Pair("machinists", 500))); + EXPECT_THAT(buildingResources.getInputs(), testing::UnorderedElementsAre(testing::Pair("wood", 5), testing::Pair("iron", 5))); + EXPECT_THAT(buildingResources.getOutputs(), testing::UnorderedElementsAre(testing::Pair("coal", 5))); +} + +TEST(V3World_BuildingResources, MaxRuleLoadsAdvancedPMResources) +{ + const auto& [PMGroups, PMs] = loadPMData(); + + V3::BuildingResources buildingResources; + buildingResources.evaluateResources({"one", "two"}, {{"one", {1, 1.0}}}, PMs, PMGroups); + + EXPECT_THAT(buildingResources.getJobs(), testing::UnorderedElementsAre(testing::Pair("laborers", 3000), testing::Pair("shopkeepers", 500))); + EXPECT_THAT(buildingResources.getInputs(), testing::UnorderedElementsAre(testing::Pair("wood", 15), testing::Pair("iron", 5))); + EXPECT_THAT(buildingResources.getOutputs(), testing::UnorderedElementsAre(testing::Pair("coal", 15))); +} + +TEST(V3World_BuildingResources, MixedRuleLoadsMixedPMResources) +{ + const auto& [PMGroups, PMs] = loadPMData(); + + V3::BuildingResources buildingResources; + buildingResources.evaluateResources({"one", "two"}, {{"one", {1, 0.6}}}, PMs, PMGroups); + + EXPECT_THAT(buildingResources.getJobs(), + testing::UnorderedElementsAre(testing::Pair("laborers", 3200), testing::Pair("shopkeepers", 300), testing::Pair("machinists", 200))); + EXPECT_THAT(buildingResources.getInputs(), testing::UnorderedElementsAre(testing::Pair("wood", 11), testing::Pair("iron", 5))); + EXPECT_THAT(buildingResources.getOutputs(), testing::UnorderedElementsAre(testing::Pair("coal", 11))); +} \ No newline at end of file diff --git a/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/BuildingTests/ProductionMethodTests.cpp b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/BuildingTests/ProductionMethodTests.cpp index e49efa836..be7c5bcf8 100644 --- a/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/BuildingTests/ProductionMethodTests.cpp +++ b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/BuildingTests/ProductionMethodTests.cpp @@ -31,6 +31,7 @@ TEST(V3World_ProductionMethodTests, ProductionMethodLoadsPMData) input << "\tgoods_input_oil_add = 5\n"; input << "\tgoods_output_coal_add = 90\n"; input << "\tgoods_output_grain_add = 10\n"; + input << "\tgoods_output_small_arms_add = 1\n"; input << "\t}\n"; input << "\t}\n"; @@ -43,5 +44,23 @@ TEST(V3World_ProductionMethodTests, ProductionMethodLoadsPMData) EXPECT_EQ(35, PM.getBureaucracy()); EXPECT_THAT(PM.getEmployment(), testing::UnorderedElementsAre(std::make_pair("clerks", 4000), std::make_pair("mounties", 50))); EXPECT_THAT(PM.getInputs(), testing::UnorderedElementsAre(std::make_pair("tools", 15), std::make_pair("oil", 5))); - EXPECT_THAT(PM.getOutputs(), testing::UnorderedElementsAre(std::make_pair("coal", 90), std::make_pair("grain", 10))); + EXPECT_THAT(PM.getOutputs(), testing::UnorderedElementsAre(std::make_pair("coal", 90), std::make_pair("grain", 10), std::make_pair("small_arms", 1))); +} + +TEST(V3World_ProductionMethodTests, ProductionMethodGetTypeExtractsMiddleRegex) +{ + std::stringstream input; + input << "\tbuilding_modifiers = {\n"; + input << "\tworkforce_scaled = {\n"; + input << "\tgoods_output_grain_add = 10\n"; + input << "\tgoods_input_small_arms_add = 1\n"; + input << "\t}\n"; + input << "\t}\n"; + + V3::ProductionMethod PM; + PM.loadProductionMethod(input); + + // Make sure keywords with _s in the name are captured in full, and not just the first part. E.g. "small" vs "small_arms" + EXPECT_THAT(PM.getInputs(), testing::UnorderedElementsAre(std::make_pair("small_arms", 1))); + EXPECT_THAT(PM.getOutputs(), testing::UnorderedElementsAre(std::make_pair("grain", 10))); } \ No newline at end of file diff --git a/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/DemandTests/MarketJobsTests.cpp b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/DemandTests/MarketJobsTests.cpp new file mode 100644 index 000000000..7abc8d899 --- /dev/null +++ b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/DemandTests/MarketJobsTests.cpp @@ -0,0 +1,303 @@ +#include "ClayManager/State/SubState.h" +#include "EconomyManager/Demand/MarketJobs.h" +#include "gtest/gtest.h" +#include +#include + +namespace +{ +std::map getPopTypes() +{ + std::map popTypes; + + std::stringstream input; + input << "working_adult_ratio = 0.2\n"; + + V3::PopType aristocrats(input), laborers, clerks, capitalists, peasants, farmers; + + popTypes.emplace("aristocrats", aristocrats); + popTypes.emplace("laborers", laborers); + popTypes.emplace("clerks", clerks); + popTypes.emplace("capitalists", capitalists); + popTypes.emplace("peasants", peasants); + popTypes.emplace("farmers", farmers); + + return popTypes; +} +} // namespace + +TEST(V3World_MarketJobsTests, CreateSubsistenceLeavesUnemployedPopsWhenNotEnoughLand) +{ + auto subState = std::make_shared(); + subState->addPop({"", "", "", 5000}); + + V3::MarketJobs marketJobs({{"aristocrats", 10}, {"laborers", 90}}); + double levels = marketJobs.createSubsistence({{"peasants", 90}, {"farmers", 10}}, .25, 0, 5, getPopTypes(), subState); + + const double delta = 0.0000001; + EXPECT_THAT(subState->getEstimatedJobs(), + testing::UnorderedElementsAre(testing::Pair("aristocrats", testing::DoubleNear(250, delta)), + testing::Pair("laborers", testing::DoubleNear(1800, delta)), + testing::Pair("peasants", testing::DoubleNear(1800, delta)), + testing::Pair("farmers", testing::DoubleNear(200, delta)), + testing::Pair("unemployed", testing::DoubleNear(950, delta)))); + EXPECT_DOUBLE_EQ(5, levels); +} + +TEST(V3World_MarketJobsTests, CreateSubsistenceLeavesMoreUnemployedPopsWhenMoreWomenWork) +{ + auto subState = std::make_shared(); + subState->addPop({"", "", "", 5000}); + + constexpr double moreWomenWorking = 0.05; + + V3::MarketJobs marketJobs({{"aristocrats", 10}, {"laborers", 90}}); + double levels = marketJobs.createSubsistence({{"peasants", 90}, {"farmers", 10}}, .25, moreWomenWorking, 5, getPopTypes(), subState); + + const double delta = 0.001; + EXPECT_THAT(subState->getEstimatedJobs(), + testing::UnorderedElementsAre(testing::Pair("aristocrats", testing::DoubleNear(200, delta)), + testing::Pair("laborers", testing::DoubleNear(1500, delta)), + testing::Pair("peasants", testing::DoubleNear(1500, delta)), + testing::Pair("farmers", testing::DoubleNear(166.666, delta)), + testing::Pair("unemployed", testing::DoubleNear(1633.333, delta)))); + EXPECT_DOUBLE_EQ(5, levels); +} + +TEST(V3World_MarketJobsTests, CreateSubsistenceLeavesNoUnemployedPopsWhenEnoughLand) +{ + auto subState = std::make_shared(); + subState->addPop({"", "", "", 5000}); + + V3::MarketJobs marketJobs({{"aristocrats", 10}, {"laborers", 90}}); + double levels = marketJobs.createSubsistence({{"peasants", 90}, {"farmers", 10}}, .25, 0, 20, getPopTypes(), subState); + + const double delta = 0.001; + EXPECT_THAT(subState->getEstimatedJobs(), + testing::UnorderedElementsAre(testing::Pair("aristocrats", testing::DoubleNear(308.642, delta)), + testing::Pair("laborers", testing::DoubleNear(2222.222, delta)), + testing::Pair("peasants", testing::DoubleNear(2222.222, delta)), + testing::Pair("farmers", testing::DoubleNear(246.914, delta)), + testing::Pair("unemployed", testing::DoubleNear(0, delta)))); + EXPECT_DOUBLE_EQ(6.17283950617284, levels); +} + +TEST(V3World_MarketJobsTests, CreateSubsistenceUsesHomesteadingLawsToPickNumbersAndTypesOfPopsWorkingSubsistence) +{ + // Just peasants, no farmers + auto subState = std::make_shared(); + subState->addPop({"", "", "", 5000}); + + V3::MarketJobs marketJobs({{"aristocrats", 10}, {"laborers", 90}}); + double levels = marketJobs.createSubsistence({{"peasants", 100}}, .25, 0, 5, getPopTypes(), subState); + + const double delta = 0.0000001; + EXPECT_THAT(subState->getEstimatedJobs(), + testing::UnorderedElementsAre(testing::Pair("aristocrats", testing::DoubleNear(250, delta)), + testing::Pair("laborers", testing::DoubleNear(1800, delta)), + testing::Pair("peasants", testing::DoubleNear(2000, delta)), + testing::Pair("unemployed", testing::DoubleNear(950, delta)))); + EXPECT_DOUBLE_EQ(5, levels); +} + +TEST(V3World_MarketJobsTests, CreateJobsTakesFromUnemployedPopsFirst) +{ + auto subState = std::make_shared(); + subState->addPop({"", "", "", 10000}); + + V3::MarketJobs marketJobs({{"aristocrats", 10}, {"laborers", 90}}); + std::map subsistenceEmployment{{"peasants", 90}, {"farmers", 10}}; + marketJobs.createSubsistence(subsistenceEmployment, .25, 0, 5, getPopTypes(), subState); + + const double delta = 0.0000001; + EXPECT_THAT(subState->getEstimatedJobs(), + testing::UnorderedElementsAre(testing::Pair("aristocrats", testing::DoubleNear(250, delta)), + testing::Pair("laborers", testing::DoubleNear(1800, delta)), + testing::Pair("peasants", testing::DoubleNear(1800, delta)), + testing::Pair("farmers", testing::DoubleNear(200, delta)), + testing::Pair("unemployed", testing::DoubleNear(5950, delta)))); + + const double levels = marketJobs.createJobs({{"laborers", 100}, {"clerks", 100}}, subsistenceEmployment, 5, .25, 0, {}, {}, getPopTypes(), subState); + + EXPECT_THAT(subState->getEstimatedJobs(), + testing::UnorderedElementsAre(testing::Pair("aristocrats", testing::DoubleNear(250, delta)), + testing::Pair("laborers", testing::DoubleNear(3800, delta)), + testing::Pair("peasants", testing::DoubleNear(1800, delta)), + testing::Pair("clerks", testing::DoubleNear(2000, delta)), + testing::Pair("farmers", testing::DoubleNear(200, delta)), + testing::Pair("unemployed", testing::DoubleNear(1950, delta)))); + EXPECT_DOUBLE_EQ(0, levels); +} + +TEST(V3World_MarketJobsTests, CreateJobsTakesFromPeasantPopsAfterUnemployedPops) +{ + auto subState = std::make_shared(); + subState->addPop({"", "", "", 10000}); + + V3::MarketJobs marketJobs({{"aristocrats", 10}, {"laborers", 90}}); + std::map subsistenceEmployment{{"peasants", 90}, {"farmers", 10}}; + marketJobs.createSubsistence(subsistenceEmployment, .25, 0, 10, getPopTypes(), subState); + + const double delta = 0.001; + EXPECT_THAT(subState->getEstimatedJobs(), + testing::UnorderedElementsAre(testing::Pair("aristocrats", testing::DoubleNear(500, delta)), + testing::Pair("laborers", testing::DoubleNear(3600, delta)), + testing::Pair("peasants", testing::DoubleNear(3600, delta)), + testing::Pair("farmers", testing::DoubleNear(400, delta)), + testing::Pair("unemployed", testing::DoubleNear(1900, delta)))); + + const double levels = marketJobs.createJobs({{"laborers", 100}, {"clerks", 100}}, subsistenceEmployment, 5, .25, 0, {}, {}, getPopTypes(), subState); + + EXPECT_THAT(subState->getEstimatedJobs(), + testing::UnorderedElementsAre(testing::Pair("aristocrats", testing::DoubleNear(237.5, delta)), + testing::Pair("laborers", testing::DoubleNear(3710, delta)), + testing::Pair("peasants", testing::DoubleNear(1710, delta)), + testing::Pair("clerks", testing::DoubleNear(2000, delta)), + testing::Pair("farmers", testing::DoubleNear(190, delta)), + testing::Pair("unemployed", testing::DoubleNear(0, delta)))); + EXPECT_DOUBLE_EQ(5.25, levels); +} + +TEST(V3World_MarketJobsTests, CreateJobsTakesFromPeasantsOnlyWhenNoUnemployed) +{ + auto subState = std::make_shared(); + subState->addPop({"", "", "", 10000}); + + V3::MarketJobs marketJobs({{"aristocrats", 10}, {"laborers", 90}}); + std::map subsistenceEmployment{{"peasants", 90}, {"farmers", 10}}; + marketJobs.createSubsistence(subsistenceEmployment, .25, 0, 20, getPopTypes(), subState); + + const double delta = 0.001; + EXPECT_THAT(subState->getEstimatedJobs(), + testing::UnorderedElementsAre(testing::Pair("aristocrats", testing::DoubleNear(617.284, delta)), + testing::Pair("laborers", testing::DoubleNear(4444.444, delta)), + testing::Pair("peasants", testing::DoubleNear(4444.444, delta)), + testing::Pair("farmers", testing::DoubleNear(493.828, delta)), + testing::Pair("unemployed", testing::DoubleNear(0, delta)))); + + const double levels = marketJobs.createJobs({{"laborers", 100}, {"clerks", 100}}, subsistenceEmployment, 5, .25, 0, {}, {}, getPopTypes(), subState); + + EXPECT_THAT(subState->getEstimatedJobs(), + testing::UnorderedElementsAre(testing::Pair("aristocrats", testing::DoubleNear(117.284, delta)), + testing::Pair("laborers", testing::DoubleNear(2844.444, delta)), + testing::Pair("peasants", testing::DoubleNear(844.444, delta)), + testing::Pair("clerks", testing::DoubleNear(2000, delta)), + testing::Pair("farmers", testing::DoubleNear(93.828, delta)), + testing::Pair("unemployed", testing::DoubleNear(0, delta)))); + EXPECT_DOUBLE_EQ(10, levels); +} + +TEST(V3World_MarketJobsTests, CreateJobsMakesFewerDependantsWithMoreEmpoweredWomen) +{ + auto subState = std::make_shared(); + subState->addPop({"", "", "", 5000}); + + constexpr double moreWomenWorking = 0.05; + + V3::MarketJobs marketJobs({{"aristocrats", 10}, {"laborers", 90}}); + std::map subsistenceEmployment{{"peasants", 90}, {"farmers", 10}}; + marketJobs.createSubsistence(subsistenceEmployment, .25, moreWomenWorking, 5, getPopTypes(), subState); + + const double delta = 0.001; + EXPECT_THAT(subState->getEstimatedJobs(), + testing::UnorderedElementsAre(testing::Pair("aristocrats", testing::DoubleNear(200, delta)), + testing::Pair("laborers", testing::DoubleNear(1500, delta)), + testing::Pair("peasants", testing::DoubleNear(1500, delta)), + testing::Pair("farmers", testing::DoubleNear(166.666, delta)), + testing::Pair("unemployed", testing::DoubleNear(1633.333, delta)))); + + const double levels = marketJobs.createJobs({{"laborers", 50}, {"clerks", 50}}, subsistenceEmployment, 3, .25, 0.05, {}, {}, getPopTypes(), subState); + + EXPECT_THAT(subState->getEstimatedJobs(), + testing::UnorderedElementsAre(testing::Pair("aristocrats", testing::DoubleNear(200, delta)), + testing::Pair("laborers", testing::DoubleNear(2000, delta)), + testing::Pair("peasants", testing::DoubleNear(1500, delta)), + testing::Pair("clerks", testing::DoubleNear(500, delta)), + testing::Pair("farmers", testing::DoubleNear(166.666, delta)), + testing::Pair("unemployed", testing::DoubleNear(633.333, delta)))); + EXPECT_DOUBLE_EQ(0, levels); +} + + +TEST(V3World_MarketJobsTests, CreateJobsAddsAdditionalOwnershipJobs) +{ + auto subState = std::make_shared(); + subState->addPop({"", "", "", 10000}); + + V3::MarketJobs marketJobs({{"aristocrats", 10}, {"laborers", 90}}); + std::map subsistenceEmployment{{"peasants", 90}, {"farmers", 10}}; + marketJobs.createSubsistence(subsistenceEmployment, .25, 0, 10, getPopTypes(), subState); + + const double delta = 0.001; + EXPECT_THAT(subState->getEstimatedJobs(), + testing::UnorderedElementsAre(testing::Pair("aristocrats", testing::DoubleNear(500, delta)), + testing::Pair("laborers", testing::DoubleNear(3600, delta)), + testing::Pair("peasants", testing::DoubleNear(3600, delta)), + testing::Pair("farmers", testing::DoubleNear(400, delta)), + testing::Pair("unemployed", testing::DoubleNear(1900, delta)))); + + + std::map rgoEmployment{{"laborers", 100}, {"clerks", 100}}; + std::map estOwnerships{{"building_financial_district", 0.5}, {"building_manor_house", 0.25}}; + std::map capEmployment{{"capitalists", 10}}; + std::map ariEmployment{{"aristocrats", 10}, {"laborers", 90}}; + + std::map> ownEmployments; + ownEmployments.emplace("building_financial_district", capEmployment); + ownEmployments.emplace("building_manor_house", ariEmployment); + + const double levels = marketJobs.createJobs(rgoEmployment, subsistenceEmployment, 5, .25, 0, estOwnerships, ownEmployments, getPopTypes(), subState); + + EXPECT_THAT(subState->getEstimatedJobs(), + testing::UnorderedElementsAre(testing::Pair("aristocrats", testing::DoubleNear(223.438, delta)), + testing::Pair("capitalists", testing::DoubleNear(100, delta)), + testing::Pair("laborers", testing::DoubleNear(3608.75, delta)), + testing::Pair("peasants", testing::DoubleNear(1158.75, delta)), + testing::Pair("clerks", testing::DoubleNear(2000, delta)), + testing::Pair("farmers", testing::DoubleNear(128.75, delta)), + testing::Pair("unemployed", testing::DoubleNear(0, delta)))); + EXPECT_DOUBLE_EQ(2712.5 / 400.0, levels); +} + +TEST(V3World_MarketJobsTests, CreateJobsYieldsWarningIfASubsistenceBuildingEmploysNoPeasants) +{ + // Just peasants, no farmers + auto subState = std::make_shared(); + subState->addPop({"", "", "", 5000}); + + V3::MarketJobs marketJobs({{"aristocrats", 10}, {"laborers", 90}}); + + marketJobs.createSubsistence({{"farmers", 10}, {"peasants", 90}}, .25, 0, 5, getPopTypes(), subState); + + std::stringstream log; + std::streambuf* cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(log.rdbuf()); + + marketJobs.createJobs({{"laborers", 50}, {"clerks", 50}}, {{"farmers", 100}}, 3, .25, 0.05, {}, {}, getPopTypes(), subState); + + std::cout.rdbuf(cout_buffer); + + EXPECT_THAT(log.str(), + testing::HasSubstr(R"([ERROR] Supposed subsistence building contains no peasants in its production methods. Job predictions will be unreliable.)")); +} + +TEST(DISABLED_V3World_MarketJobsTests, CreateJobsNoWorkers) +{ + // Just peasants, no farmers + auto subState = std::make_shared(); + subState->addPop({"", "", "", 5000}); + + V3::MarketJobs marketJobs({{"aristocrats", 10}, {"laborers", 90}}); + + std::stringstream log; + std::streambuf* cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(log.rdbuf()); + + marketJobs.createJobs({{"laborers", 50}, {"clerks", 50}}, {{"peasants", 100}}, 3, .25, 0.05, {}, {}, getPopTypes(), subState); + + std::cout.rdbuf(cout_buffer); + + EXPECT_THAT(log.str(), testing::HasSubstr(R"([WARNING] No subsistence workers available.)")); +} + +// Arable land can change as farms/plantations are built. \ No newline at end of file diff --git a/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/DemandTests/MarketTests.cpp b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/DemandTests/MarketTests.cpp index ac9a37b68..74956fdbe 100644 --- a/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/DemandTests/MarketTests.cpp +++ b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/DemandTests/MarketTests.cpp @@ -39,6 +39,21 @@ TEST(V3World_MarketTests, MarketMutatorsMutateMarket) EXPECT_THAT(market.getMarketBalance(), testing::UnorderedElementsAre(testing::Pair("oil", -5))); } +TEST(V3World_MarketTests, ClearMarketResetsMarket) +{ + V3::Market market({"oil"}); + + market.sell("oil", 10.0); + market.buyForBuilding("oil", 5.0); + market.buyForPop("oil", 10.0); + + EXPECT_THAT(market.getMarketBalance(), testing::UnorderedElementsAre(testing::Pair("oil", -5))); + + market.clearMarket(); + + EXPECT_THAT(market.getMarketBalance(), testing::UnorderedElementsAre(testing::Pair("oil", 0))); +} + TEST(V3World_MarketTests, MarketShareFollowsFormula) { V3::Market market({"oil", "wood", "fabric", "grain"}); @@ -1110,6 +1125,7 @@ TEST(V3World_MarketTests, FormulaEstimatesCretianNeed) }, {}, {}); + EXPECT_THAT(market.getMarketBalance(), testing::UnorderedElementsAre(testing::Pair("small_arms", testing::DoubleNear(30.16, 0.02)), testing::Pair("clothes", testing::DoubleNear(248.3, 0.6)), diff --git a/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/DemandTests/MarketTrackerTests.cpp b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/DemandTests/MarketTrackerTests.cpp new file mode 100644 index 000000000..755ad5095 --- /dev/null +++ b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/DemandTests/MarketTrackerTests.cpp @@ -0,0 +1,3 @@ +// #include "EconomyManager/Demand/MarketTracker.h" +// #include "gtest/gtest.h" +// #include \ No newline at end of file diff --git a/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/EconomyManagerTests.cpp b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/EconomyManagerTests.cpp index 5748b9ae6..0cc90ec7b 100644 --- a/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/EconomyManagerTests.cpp +++ b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/EconomyManagerTests.cpp @@ -144,40 +144,4 @@ TEST(V3World_EconomyManagerTests, GlobalCPScalesByPopulation) EXPECT_THAT(log.str(), testing::HasSubstr(R"([INFO] <> The world is 84% Centralized by population. Adjusting global CP values by: -14%)")); EXPECT_THAT(log.str(), testing::HasSubstr(R"([INFO] <> The world has 1245324 CP to spend on industry.)")); -} - -TEST(DISABLED_V3World_EconomyManagerTests, GlobalCPScalesByDate) -{ - auto politicalManager = prepWorld(); - - V3::EconomyManager econManager; - econManager.loadCentralizedStates(politicalManager.getCountries()); -} - -TEST(DISBALED_V3World_EconomyManagerTests, GlobalCPDistributionTechGroup) -{ - auto politicalManager = prepWorld(); - - V3::EconomyManager econManager; - econManager.loadCentralizedStates(politicalManager.getCountries()); - - std::stringstream log; - std::streambuf* cout_buffer = std::cout.rdbuf(); - std::cout.rdbuf(log.rdbuf()); - - econManager.assignCountryCPBudgets(Configuration::ECONOMY::CivLevel, Configuration::STARTDATE::Vanilla, DatingData(), politicalManager); - - std::cout.rdbuf(cout_buffer); -} - -TEST(DISABLED_V3World_EconomyManagerTests, GlobalCPDistributionDev) -{ -} - -TEST(DISABLED_V3World_EconomyManagerTests, AssignSubStateCPBudgetsTechGroup) -{ -} - -TEST(DISABLED_V3World_EconomyManagerTests, AssignSubStateCPBudgetsDev) -{ -} +} \ No newline at end of file diff --git a/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/PacketTests.cpp b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/PacketTests.cpp new file mode 100644 index 000000000..ddb2c9958 --- /dev/null +++ b/EU4ToVic3Tests/V3WorldTests/EconomyManagerTests/PacketTests.cpp @@ -0,0 +1,112 @@ +#include "ClayManager/State/State.h" +#include "EconomyManager/Packet.h" +#include "PoliticalManager/Country/Country.h" + +#include + +TEST(V3World_PacketTests, ZeroInputReturnsMinimumPacket) +{ + V3::Building b; + V3::SubState s; + V3::BuildingGroups bgs; + + EXPECT_EQ(1, V3::Packet(b, 0, 0, s, {}, {}, {}, {}, bgs).getSize()); +} + +TEST(V3World_PacketTests, SectorPacket) +{ + V3::Building b; + auto s = std::make_shared(); + V3::BuildingGroups bgs; + + std::stringstream buildingInput; + buildingInput << "required_construction = 30"; + b.loadBuilding(buildingInput, {}); + + s->setCPBudget(INT_MAX); + + + EXPECT_EQ(4, V3::Packet(b, 120, 1, *s, {s}, {}, {}, {}, bgs).getSize()); +} + +TEST(V3World_PacketTests, SubStatePacket) +{ + V3::Building b; + auto s = std::make_shared(); + V3::BuildingGroups bgs; + + std::stringstream buildingInput; + buildingInput << "required_construction = 30"; + b.loadBuilding(buildingInput, {}); + + s->setCPBudget(150); + + + EXPECT_EQ(5, V3::Packet(b, INT_MAX, 1, *s, {s}, {}, {}, {}, bgs).getSize()); +} + +TEST(V3World_PacketTests, CapacityPacket) +{ + V3::BuildingGroups bgs; + + auto c = std::make_shared(); + V3::ProcessedData data; + data.techs = {"t"}; + c->setProcessedData(data); + + auto hs = std::make_shared(); + + V3::Building b; + std::stringstream buildingInput; + buildingInput << "required_construction = 30\n"; + buildingInput << "has_max_level = yes\n"; + b.loadBuilding(buildingInput, {}); + b.setName("b"); + + auto s = std::make_shared(); + s->setCPBudget(INT_MAX); + s->setOwner(c); + s->setHomeState(hs); + + V3::Tech t; + t.maxBuildingLevels["b"] = 2; + + + EXPECT_EQ(2, V3::Packet(b, INT_MAX, 1, *s, {s}, {}, {{"t", t}}, {}, bgs).getSize()); +} + +TEST(V3World_PacketTests, ClusterPacketMinimumFactor) +{ + V3::Building b; + auto s = std::make_shared(); + auto s2 = std::make_shared(); + V3::BuildingGroups bgs; + + std::stringstream buildingInput; + buildingInput << "required_construction = 30"; + b.loadBuilding(buildingInput, {}); + + s->setCPBudget(INT_MAX - 90); + s2->setCPBudget(90); + + + EXPECT_EQ(3, V3::Packet(b, INT_MAX, -1, *s, {s, s2}, {}, {}, {}, bgs).getSize()); +} + +TEST(V3World_PacketTests, ClusterPacketZeroFactor) +{ + V3::Building b; + auto s = std::make_shared(); + auto s2 = std::make_shared(); + V3::BuildingGroups bgs; + + std::stringstream buildingInput; + buildingInput << "required_construction = 30"; + b.loadBuilding(buildingInput, {}); + + s->setCPBudget(INT_MAX - 90); + s2->setCPBudget(90); + + + EXPECT_EQ(35791394, V3::Packet(b, INT_MAX, 0, *s, {s, s2}, {}, {}, {}, bgs).getSize()); +} \ No newline at end of file diff --git a/EU4ToVic3Tests/V3WorldTests/LoaderTests/BuildingLoaderTests/ProductionMethodGroupLoaderTests.cpp b/EU4ToVic3Tests/V3WorldTests/LoaderTests/BuildingLoaderTests/ProductionMethodGroupLoaderTests.cpp index 1cab76086..9f32947f7 100644 --- a/EU4ToVic3Tests/V3WorldTests/LoaderTests/BuildingLoaderTests/ProductionMethodGroupLoaderTests.cpp +++ b/EU4ToVic3Tests/V3WorldTests/LoaderTests/BuildingLoaderTests/ProductionMethodGroupLoaderTests.cpp @@ -14,5 +14,5 @@ TEST(V3World_ProductionMethodGroupLoaderTests, ProductionMethodGroupLoaderCanLoa EXPECT_TRUE(PMGroupLoader.getPMGroups().empty()); PMGroupLoader.loadPMGroups(modFS); const auto PMGroups = PMGroupLoader.getPMGroups(); - EXPECT_EQ(8, PMGroups.size()); + EXPECT_EQ(11, PMGroups.size()); } \ No newline at end of file diff --git a/EU4ToVic3Tests/V3WorldTests/LoaderTests/BuildingLoaderTests/ProductionMethodLoaderTests.cpp b/EU4ToVic3Tests/V3WorldTests/LoaderTests/BuildingLoaderTests/ProductionMethodLoaderTests.cpp index 02e02ab81..2d2a35b5d 100644 --- a/EU4ToVic3Tests/V3WorldTests/LoaderTests/BuildingLoaderTests/ProductionMethodLoaderTests.cpp +++ b/EU4ToVic3Tests/V3WorldTests/LoaderTests/BuildingLoaderTests/ProductionMethodLoaderTests.cpp @@ -14,5 +14,5 @@ TEST(V3World_ProductionMethodLoaderTests, ProductionMethodLoaderCanLoadProductio EXPECT_TRUE(PMLoader.getPMs().empty()); PMLoader.loadPMs(modFS); const auto PMs = PMLoader.getPMs(); - EXPECT_EQ(18, PMs.size()); + EXPECT_EQ(24, PMs.size()); } \ No newline at end of file diff --git a/EU4ToVic3Tests/V3WorldTests/LoaderTests/PopLoaderTests/PopTypeLoaderTests.cpp b/EU4ToVic3Tests/V3WorldTests/LoaderTests/PopLoaderTests/PopTypeLoaderTests.cpp index 7a5fe6e98..81a309bf6 100644 --- a/EU4ToVic3Tests/V3WorldTests/LoaderTests/PopLoaderTests/PopTypeLoaderTests.cpp +++ b/EU4ToVic3Tests/V3WorldTests/LoaderTests/PopLoaderTests/PopTypeLoaderTests.cpp @@ -14,7 +14,7 @@ TEST(V3World_PopTypeLoaderTests, PopTypeLoaderCanLoadPopTypeComponents) popTypeLoader.loadPopTypes(modFS); - EXPECT_EQ(2, popTypeLoader.getPopTypes().size()); + EXPECT_EQ(3, popTypeLoader.getPopTypes().size()); EXPECT_EQ("peasants", popTypeLoader.getPopTypes().at("peasants").getType()); EXPECT_DOUBLE_EQ(0.05, popTypeLoader.getPopTypes().at("peasants").getConsumptionRate()); EXPECT_EQ(std::nullopt, popTypeLoader.getPopTypes().at("peasants").getDependentRatio()); diff --git a/EU4ToVic3Tests/V3WorldTests/PoliticalManagerTests/CountryTests/CountryTests.cpp b/EU4ToVic3Tests/V3WorldTests/PoliticalManagerTests/CountryTests/CountryTests.cpp index 679a85fea..1ed6b86cd 100644 --- a/EU4ToVic3Tests/V3WorldTests/PoliticalManagerTests/CountryTests/CountryTests.cpp +++ b/EU4ToVic3Tests/V3WorldTests/PoliticalManagerTests/CountryTests/CountryTests.cpp @@ -281,6 +281,39 @@ TEST(V3World_CountryTests, PopCounterSumsSubStatePop) EXPECT_EQ(1500, country.getPopCount()); } +TEST(V3World_CountryTests, CultureBreakdownSumsSubStateCultures) +{ + V3::Country country; + std::vector> substates; + + EXPECT_EQ(0, country.getPopCount()); + EXPECT_EQ(0, V3::Country::getPopCount(substates)); + + auto sub0 = std::make_shared(); + auto sub1 = std::make_shared(); + + V3::Pop pop0, pop1, pop2; + pop0.setSize(1000); + pop1.setSize(500); + pop2.setSize(500); + + pop0.setCulture("english"); + pop1.setCulture("german"); + pop2.setCulture("english"); + + sub0->addPop(pop0); + sub1->addPop(pop1); + sub1->addPop(pop2); + + substates.push_back(sub0); + substates.push_back(sub1); + + country.addSubState(sub0); + country.addSubState(sub1); + + EXPECT_THAT(country.getCultureBreakdown(), testing::UnorderedElementsAre(testing::Pair("english", 0.75), testing::Pair("german", 0.25))); +} + TEST(V3World_CountryTests, InfrastructureFromCountryTech) { V3::Country country; @@ -333,4 +366,106 @@ TEST(V3World_CountryTests, YearCapFactorHitsCurve) EXPECT_NEAR(0.3, V3::Country::yearCapFactor(date("1650.1.1")), 0.015); EXPECT_NEAR(0.2, V3::Country::yearCapFactor(date("1490.1.1")), 0.01); EXPECT_NEAR(0.15, V3::Country::yearCapFactor(date("1350.1.1")), 0.01); +} + +TEST(V3World_CountryTests, HasAnyOfLawUnlockingPositiveCase) +{ + V3::Country country; + + V3::ProcessedData data; + data.laws.emplace("law_2"); + data.laws.emplace("law_3"); + data.laws.emplace("law_4"); + + country.setProcessedData(data); + + EXPECT_TRUE(country.hasAnyOfLawUnlocking({"law_2"})); + EXPECT_TRUE(country.hasAnyOfLawUnlocking({"law_1", "law_2"})); + EXPECT_TRUE(country.hasAnyOfLawUnlocking({"law_2", "law_3"})); +} + +TEST(V3World_CountryTests, HasAnyOfLawUnlockingNegativeCase) +{ + V3::Country country; + + V3::ProcessedData data; + data.laws.emplace("law_2"); + data.laws.emplace("law_3"); + data.laws.emplace("law_4"); + + country.setProcessedData(data); + + EXPECT_FALSE(country.hasAnyOfLawUnlocking({"law_1"})); + EXPECT_FALSE(country.hasAnyOfLawUnlocking({"law_1", "law_5"})); +} + +TEST(V3World_CountryTests, HasAnyOfLawBlockingPositiveCase) +{ + V3::Country country; + + V3::ProcessedData data; + data.laws.emplace("law_2"); + data.laws.emplace("law_3"); + data.laws.emplace("law_4"); + + country.setProcessedData(data); + + EXPECT_TRUE(country.hasAnyOfLawBlocking({"law_2"})); + EXPECT_TRUE(country.hasAnyOfLawBlocking({"law_1", "law_2"})); + EXPECT_TRUE(country.hasAnyOfLawBlocking({"law_2", "law_3"})); +} + +TEST(V3World_CountryTests, HasAnyOfLawBlockingNegativeCase) +{ + V3::Country country; + + V3::ProcessedData data; + data.laws.emplace("law_2"); + data.laws.emplace("law_3"); + data.laws.emplace("law_4"); + + country.setProcessedData(data); + + EXPECT_FALSE(country.hasAnyOfLawBlocking({"law_1"})); + EXPECT_FALSE(country.hasAnyOfLawBlocking({"law_1", "law_5"})); +} + +TEST(V3World_CountryTests, GetCultureBreakdownReturnsPopulationFractions) +{ + V3::Country country; + + std::shared_ptr subState = std::make_shared(); + subState->addPop(V3::Pop("german", "no_religion", "", 650)); + subState->addPop(V3::Pop("khmer", "crusader_kings_two", "", 250)); + + std::shared_ptr subState2 = std::make_shared(); + subState2->addPop(V3::Pop("english", "no_religion", "", 100)); + + country.addSubState(subState); + country.addSubState(subState2); + + EXPECT_THAT(country.getCultureBreakdown(), + testing::UnorderedElementsAre(testing::Pair("german", 0.65), testing::Pair("khmer", 0.25), testing::Pair("english", 0.1))); +} + +TEST(V3World_CountryTests, GetJobBreakdownReturnsPopTypeFractions) +{ + V3::Country country; + + std::shared_ptr subState = std::make_shared(); + subState->addJob("peasants", 500); + subState->addJob("machinists", 400); + subState->addPop(V3::Pop("german", "no_religion", "", 650)); + subState->addPop(V3::Pop("khmer", "crusader_kings_two", "", 250)); + + + std::shared_ptr subState2 = std::make_shared(); + subState2->addJob("laborers", 100); + subState2->addPop(V3::Pop("english", "no_religion", "", 100)); + + country.addSubState(subState); + country.addSubState(subState2); + + EXPECT_THAT(country.getJobBreakdown(), + testing::UnorderedElementsAre(testing::Pair("peasants", 0.5), testing::Pair("machinists", 0.4), testing::Pair("laborers", 0.1))); } \ No newline at end of file diff --git a/EU4ToVic3lib/EU4ToVic3lib.vcxproj b/EU4ToVic3lib/EU4ToVic3lib.vcxproj index 828e44609..0fa46544a 100644 --- a/EU4ToVic3lib/EU4ToVic3lib.vcxproj +++ b/EU4ToVic3lib/EU4ToVic3lib.vcxproj @@ -159,6 +159,7 @@ + @@ -166,9 +167,12 @@ + + + @@ -403,6 +407,7 @@ + @@ -412,9 +417,12 @@ + + + diff --git a/EU4ToVic3lib/EU4ToVic3lib.vcxproj.filters b/EU4ToVic3lib/EU4ToVic3lib.vcxproj.filters index 0e493b9a1..8bf832a9e 100644 --- a/EU4ToVic3lib/EU4ToVic3lib.vcxproj.filters +++ b/EU4ToVic3lib/EU4ToVic3lib.vcxproj.filters @@ -1045,6 +1045,18 @@ Vic3World\EconomyManager\Demand + + Vic3World\EconomyManager\Building + + + Vic3World\EconomyManager\Demand + + + Vic3World\EconomyManager + + + Vic3World\EconomyManager\Demand + @@ -1801,5 +1813,17 @@ Vic3World\EconomyManager\Demand + + Vic3World\EconomyManager\Building + + + Vic3World\EconomyManager\Demand + + + Vic3World\EconomyManager + + + Vic3World\EconomyManager\Demand + \ No newline at end of file