Skip to content

Commit 6196003

Browse files
authored
Merge pull request #605 from OpenVicProject/artisanal_production_switching
Add artisanal production switching
2 parents cdb0d9d + 4c38346 commit 6196003

17 files changed

Lines changed: 362 additions & 179 deletions

src/openvic-simulation/InstanceManager.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,6 @@ InstanceManager::InstanceManager(
2323
new_definition_manager.get_define_manager().get_country_defines(),
2424
good_instance_manager
2525
},
26-
artisanal_producer_factory_pattern {
27-
good_instance_manager,
28-
new_definition_manager.get_modifier_manager().get_modifier_effect_cache(),
29-
new_definition_manager.get_economy_manager().get_production_type_manager()
30-
},
3126
global_flags { "global" },
3227
country_instance_manager {
3328
thread_pool,
@@ -177,7 +172,11 @@ bool InstanceManager::setup() {
177172
}
178173

179174
thread_pool.initialise_threadpool(
175+
game_rules_manager,
176+
good_instance_manager,
177+
definition_manager.get_modifier_manager().get_modifier_effect_cache(),
180178
definition_manager.get_define_manager().get_pops_defines(),
179+
definition_manager.get_economy_manager().get_production_type_manager(),
181180
good_instance_manager.get_good_definition_manager().get_good_definitions(),
182181
definition_manager.get_pop_manager().get_stratas(),
183182
good_instance_manager.get_good_instances(),
@@ -222,8 +221,7 @@ bool InstanceManager::load_bookmark(Bookmark const* new_bookmark) {
222221
country_instance_manager,
223222
// TODO - the following argument is for generating test pop attributes
224223
definition_manager.get_politics_manager().get_issue_manager(),
225-
market_instance,
226-
artisanal_producer_factory_pattern
224+
market_instance
227225
);
228226

229227
// It is important that province history is applied before country history as province history includes

src/openvic-simulation/InstanceManager.hpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
#include "openvic-simulation/country/CountryInstance.hpp"
55
#include "openvic-simulation/diplomacy/CountryRelation.hpp"
66
#include "openvic-simulation/economy/GoodInstance.hpp"
7-
#include "openvic-simulation/economy/production/ArtisanalProducerFactoryPattern.hpp"
87
#include "openvic-simulation/economy/trading/MarketInstance.hpp"
98
#include "openvic-simulation/map/MapInstance.hpp"
109
#include "openvic-simulation/map/Mapmode.hpp"
@@ -37,7 +36,6 @@ namespace OpenVic {
3736
GameRulesManager const& game_rules_manager;
3837
GoodInstanceManager PROPERTY_REF(good_instance_manager);
3938
MarketInstance PROPERTY_REF(market_instance);
40-
ArtisanalProducerFactoryPattern artisanal_producer_factory_pattern;
4139

4240
FlagStrings PROPERTY_REF(global_flags);
4341

src/openvic-simulation/economy/production/ArtisanalProducer.cpp

Lines changed: 150 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44

55
#include "openvic-simulation/country/CountryInstance.hpp"
66
#include "openvic-simulation/economy/GoodDefinition.hpp"
7+
#include "openvic-simulation/economy/GoodInstance.hpp"
78
#include "openvic-simulation/economy/production/ProductionType.hpp"
89
#include "openvic-simulation/economy/trading/MarketInstance.hpp"
910
#include "openvic-simulation/map/ProvinceInstance.hpp"
1011
#include "openvic-simulation/modifier/ModifierEffectCache.hpp"
1112
#include "openvic-simulation/pop/Pop.hpp"
13+
#include "openvic-simulation/pop/PopValuesFromProvince.hpp"
1214
#include "openvic-simulation/types/fixed_point/FixedPoint.hpp"
1315
#include "openvic-simulation/utility/Typedefs.hpp"
1416

@@ -17,25 +19,55 @@ using namespace OpenVic;
1719
ArtisanalProducer::ArtisanalProducer(
1820
ModifierEffectCache const& new_modifier_effect_cache,
1921
fixed_point_map_t<GoodDefinition const*>&& new_stockpile,
20-
ProductionType const& new_production_type,
22+
ProductionType const* const new_production_type,
2123
fixed_point_t new_current_production
2224
) : modifier_effect_cache { new_modifier_effect_cache },
2325
stockpile { std::move(new_stockpile) },
24-
production_type { new_production_type },
26+
production_type_nullable { nullptr },
2527
current_production { new_current_production }
2628
{
27-
max_quantity_to_buy_per_good.reserve(new_production_type.get_input_goods().size());
29+
set_production_type(production_type_nullable);
2830
}
2931

32+
void ArtisanalProducer::set_production_type(ProductionType const* const new_production_type) {
33+
if (production_type_nullable == new_production_type) {
34+
return;
35+
}
36+
37+
production_type_nullable = new_production_type;
38+
if (production_type_nullable == nullptr) {
39+
return;
40+
}
41+
42+
ProductionType const& production_type = *production_type_nullable;
43+
max_quantity_to_buy_per_good.clear();
44+
max_quantity_to_buy_per_good.reserve(production_type.get_input_goods().size());
45+
}
46+
3047
void ArtisanalProducer::artisan_tick(
3148
Pop& pop,
32-
const fixed_point_t max_cost_multiplier,
49+
PopValuesFromProvince const& values_from_province,
3350
IndexedFlatMap<GoodDefinition, char>& reusable_goods_mask,
3451
memory::vector<fixed_point_t>& pop_max_quantity_to_buy_per_good,
3552
memory::vector<fixed_point_t>& pop_money_to_spend_per_good,
3653
memory::vector<fixed_point_t>& reusable_map_0,
3754
memory::vector<fixed_point_t>& reusable_map_1
3855
) {
56+
ProductionType const* const old_production_type = production_type_nullable;
57+
set_production_type(pick_production_type(pop, values_from_province));
58+
if (production_type_nullable == nullptr) {
59+
return;
60+
}
61+
62+
ProductionType const& production_type = *production_type_nullable;
63+
if (production_type_nullable != old_production_type) {
64+
//TODO sell stockpile no longer used
65+
stockpile.clear();
66+
for (auto const& [input_good, base_demand] : production_type.get_input_goods()) {
67+
stockpile[input_good] = base_demand * pop.get_size() / production_type.get_base_workforce_size();
68+
}
69+
}
70+
3971
CountryInstance* country_to_report_economy_nullable = pop.get_location()->get_country_to_report_economy();
4072
max_quantity_to_buy_per_good.clear();
4173
IndexedFlatMap<GoodDefinition, char>& wants_more_mask = reusable_goods_mask;
@@ -137,7 +169,7 @@ void ArtisanalProducer::artisan_tick(
137169
}
138170

139171
//executed once per pop while nothing else uses it.
140-
const fixed_point_t total_cash_to_spend = pop.get_cash().get_copy_of_value() / max_cost_multiplier;
172+
const fixed_point_t total_cash_to_spend = pop.get_cash().get_copy_of_value() / values_from_province.get_max_cost_multiplier();
141173
MarketInstance const& market_instance = pop.get_market_instance();
142174

143175
if (total_cash_to_spend > 0 && distinct_goods_to_buy > 0) {
@@ -257,4 +289,117 @@ fixed_point_t ArtisanalProducer::add_to_stockpile(GoodDefinition const& good, co
257289
stockpile.at(&good) += quantity_added_to_stockpile;
258290
max_quantity_to_buy -= quantity_added_to_stockpile;
259291
return quantity_added_to_stockpile;
292+
}
293+
294+
std::optional<fixed_point_t> ArtisanalProducer::estimate_production_type_score(
295+
GoodInstanceManager const& good_instance_manager,
296+
ProductionType const& production_type,
297+
ProvinceInstance& location,
298+
const fixed_point_t max_cost_multiplier
299+
) {
300+
if (production_type.get_template_type() != ProductionType::template_type_t::ARTISAN) {
301+
return std::nullopt;
302+
}
303+
304+
if (!production_type.is_valid_for_artisan_in(location)) {
305+
return std::nullopt;
306+
}
307+
308+
GoodInstance const& output_good = good_instance_manager.get_good_instance_by_definition(production_type.get_output_good());
309+
if (!output_good.get_is_available()) {
310+
return std::nullopt;
311+
}
312+
313+
fixed_point_t estimated_costs = 0;
314+
for (auto const& [input_good, input_quantity] : production_type.get_input_goods()) {
315+
estimated_costs += input_quantity * good_instance_manager.get_good_instance_by_definition(*input_good).get_price();
316+
}
317+
estimated_costs *= max_cost_multiplier;
318+
319+
const fixed_point_t estimated_revenue = production_type.get_base_output_quantity() * output_good.get_price();
320+
return calculate_production_type_score(
321+
estimated_revenue,
322+
estimated_costs,
323+
production_type.get_base_workforce_size()
324+
);
325+
}
326+
327+
fixed_point_t ArtisanalProducer::calculate_production_type_score(
328+
const fixed_point_t revenue,
329+
const fixed_point_t costs,
330+
const pop_size_t workforce
331+
) {
332+
constexpr fixed_point_t k = fixed_point_t::_0_50;
333+
return (
334+
k * fixed_point_t::mul_div(costs, costs, revenue)
335+
-(1+k)*costs
336+
+ revenue
337+
) * Pop::size_denominator / workforce; //factor out pop size without making values too small
338+
}
339+
340+
ProductionType const* ArtisanalProducer::pick_production_type(
341+
Pop& pop,
342+
PopValuesFromProvince const& values_from_province
343+
) const {
344+
bool should_pick_new_production_type;
345+
const auto ranked_artisanal_production_types = values_from_province.get_ranked_artisanal_production_types();
346+
347+
if (ranked_artisanal_production_types.empty()) {
348+
return production_type_nullable;
349+
}
350+
351+
const fixed_point_t revenue = pop.get_artisanal_income();
352+
const fixed_point_t costs = pop.get_artisan_inputs_expense();
353+
if (production_type_nullable == nullptr || (revenue <= costs)) {
354+
should_pick_new_production_type = true;
355+
} else {
356+
const fixed_point_t current_score = calculate_production_type_score(
357+
revenue,
358+
costs,
359+
pop.get_size()
360+
);
361+
362+
fixed_point_t relative_score = ranked_artisanal_production_types.empty() ? fixed_point_t::_1 : fixed_point_t::_0;
363+
for (auto it = ranked_artisanal_production_types.begin(); it < ranked_artisanal_production_types.end(); ++it) {
364+
auto const& [production_type, score_estimate] = *it;
365+
size_t i = it - ranked_artisanal_production_types.begin();
366+
if (current_score > score_estimate) {
367+
if (i == 0) {
368+
relative_score = fixed_point_t::_1;
369+
} else {
370+
const fixed_point_t previous_score_estimate = ranked_artisanal_production_types[i-1].second;
371+
relative_score = (
372+
(current_score - score_estimate)
373+
/ (previous_score_estimate - score_estimate)
374+
+ ranked_artisanal_production_types.size() - i
375+
) / (1 + ranked_artisanal_production_types.size());
376+
}
377+
break;
378+
}
379+
380+
if (current_score == score_estimate) {
381+
relative_score = fixed_point_t::parse(ranked_artisanal_production_types.size() - i) / (1 + ranked_artisanal_production_types.size());
382+
}
383+
}
384+
385+
//TODO decide based on score and randomness and defines.economy.GOODS_FOCUS_SWAP_CHANCE
386+
should_pick_new_production_type = relative_score < fixed_point_t::_0_50;
387+
}
388+
389+
if (!should_pick_new_production_type) {
390+
return production_type_nullable;
391+
}
392+
393+
fixed_point_t weights_sum = 0;
394+
memory::vector<fixed_point_t> weights {};
395+
weights.reserve(ranked_artisanal_production_types.size());
396+
for (auto const& [production_type, score_estimate] : ranked_artisanal_production_types) {
397+
//TODO calculate actual scores including availability of goods
398+
const fixed_point_t weight = score_estimate * score_estimate;
399+
weights.push_back(weight);
400+
weights_sum += weight;
401+
}
402+
403+
//TODO randomly sample using weights
404+
return ranked_artisanal_production_types[0].first;
260405
}

src/openvic-simulation/economy/production/ArtisanalProducer.hpp

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
#pragma once
22

3+
#include <optional>
4+
35
#include "openvic-simulation/types/IndexedFlatMap.hpp"
46
#include "openvic-simulation/types/fixed_point/FixedPoint.hpp"
57
#include "openvic-simulation/types/fixed_point/FixedPointMap.hpp"
8+
#include "openvic-simulation/types/PopSize.hpp"
69
#include "openvic-simulation/utility/Getters.hpp"
710

811
namespace OpenVic {
912
struct GoodDefinition;
1013
struct GoodInstanceManager;
1114
struct ModifierEffectCache;
1215
struct Pop;
16+
struct PopValuesFromProvince;
1317
struct ProductionType;
18+
struct ProvinceInstance;
1419

1520
struct ArtisanalProducer {
1621
private:
@@ -20,20 +25,38 @@ namespace OpenVic {
2025
//only used during day tick (from artisan_tick() until MarketInstance.execute_orders())
2126
fixed_point_map_t<GoodDefinition const*> max_quantity_to_buy_per_good;
2227

23-
ProductionType const& PROPERTY(production_type);
28+
ProductionType const* PROPERTY(production_type_nullable);
2429
fixed_point_t PROPERTY(current_production);
2530

31+
void set_production_type(ProductionType const* const new_production_type);
32+
ProductionType const* pick_production_type(
33+
Pop& pop,
34+
PopValuesFromProvince const& values_from_province
35+
) const;
36+
37+
static fixed_point_t calculate_production_type_score(
38+
const fixed_point_t revenue,
39+
const fixed_point_t costs,
40+
const pop_size_t workforce
41+
);
42+
2643
public:
2744
ArtisanalProducer(
2845
ModifierEffectCache const& new_modifier_effect_cache,
2946
fixed_point_map_t<GoodDefinition const*>&& new_stockpile,
30-
ProductionType const& new_production_type,
31-
fixed_point_t new_current_production
47+
ProductionType const* const new_production_type,
48+
const fixed_point_t new_current_production
3249
);
50+
ArtisanalProducer(ModifierEffectCache const& new_modifier_effect_cache) : ArtisanalProducer(
51+
new_modifier_effect_cache,
52+
fixed_point_map_t<GoodDefinition const*> {},
53+
nullptr,
54+
fixed_point_t::_0
55+
) { };
3356

3457
void artisan_tick(
3558
Pop& pop,
36-
const fixed_point_t max_cost_multiplier,
59+
PopValuesFromProvince const& values_from_province,
3760
IndexedFlatMap<GoodDefinition, char>& reusable_goods_mask,
3861
memory::vector<fixed_point_t>& pop_max_quantity_to_buy_per_good,
3962
memory::vector<fixed_point_t>& pop_money_to_spend_per_good,
@@ -44,5 +67,13 @@ namespace OpenVic {
4467
//thread safe if called once per good and stockpile already has an entry.
4568
//adds to stockpile up to max_quantity_to_buy and returns quantity added to stockpile
4669
fixed_point_t add_to_stockpile(GoodDefinition const& good, const fixed_point_t quantity);
70+
71+
//optional to handle invalid
72+
static std::optional<fixed_point_t> estimate_production_type_score(
73+
GoodInstanceManager const& good_instance_manager,
74+
ProductionType const& production_type,
75+
ProvinceInstance& location,
76+
const fixed_point_t max_cost_multiplier
77+
);
4778
};
4879
}

src/openvic-simulation/economy/production/ArtisanalProducerFactoryPattern.cpp

Lines changed: 0 additions & 54 deletions
This file was deleted.

0 commit comments

Comments
 (0)