Skip to content

Commit bf7e237

Browse files
committed
Add artisanal production switching
1 parent a77f984 commit bf7e237

18 files changed

Lines changed: 379 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: 165 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
#include "ArtisanalProducer.hpp"
22

33
#include <cstddef>
4+
#include <random>
45

56
#include "openvic-simulation/country/CountryInstance.hpp"
67
#include "openvic-simulation/economy/GoodDefinition.hpp"
8+
#include "openvic-simulation/economy/GoodInstance.hpp"
79
#include "openvic-simulation/economy/production/ProductionType.hpp"
810
#include "openvic-simulation/economy/trading/MarketInstance.hpp"
911
#include "openvic-simulation/map/ProvinceInstance.hpp"
1012
#include "openvic-simulation/modifier/ModifierEffectCache.hpp"
1113
#include "openvic-simulation/pop/Pop.hpp"
14+
#include "openvic-simulation/pop/PopValuesFromProvince.hpp"
1215
#include "openvic-simulation/types/fixed_point/FixedPoint.hpp"
1316
#include "openvic-simulation/utility/Typedefs.hpp"
1417

@@ -17,25 +20,55 @@ using namespace OpenVic;
1720
ArtisanalProducer::ArtisanalProducer(
1821
ModifierEffectCache const& new_modifier_effect_cache,
1922
fixed_point_map_t<GoodDefinition const*>&& new_stockpile,
20-
ProductionType const& new_production_type,
23+
ProductionType const* const new_production_type,
2124
fixed_point_t new_current_production
2225
) : modifier_effect_cache { new_modifier_effect_cache },
2326
stockpile { std::move(new_stockpile) },
24-
production_type { new_production_type },
27+
production_type_nullable { nullptr },
2528
current_production { new_current_production }
2629
{
27-
max_quantity_to_buy_per_good.reserve(new_production_type.get_input_goods().size());
30+
set_production_type(production_type_nullable);
2831
}
2932

33+
void ArtisanalProducer::set_production_type(ProductionType const* const new_production_type) {
34+
if (production_type_nullable == new_production_type) {
35+
return;
36+
}
37+
38+
production_type_nullable = new_production_type;
39+
if (production_type_nullable == nullptr) {
40+
return;
41+
}
42+
43+
ProductionType const& production_type = *production_type_nullable;
44+
max_quantity_to_buy_per_good.clear();
45+
max_quantity_to_buy_per_good.reserve(production_type.get_input_goods().size());
46+
}
47+
3048
void ArtisanalProducer::artisan_tick(
3149
Pop& pop,
32-
const fixed_point_t max_cost_multiplier,
50+
PopValuesFromProvince const& values_from_province,
3351
IndexedFlatMap<GoodDefinition, char>& reusable_goods_mask,
3452
memory::vector<fixed_point_t>& pop_max_quantity_to_buy_per_good,
3553
memory::vector<fixed_point_t>& pop_money_to_spend_per_good,
3654
memory::vector<fixed_point_t>& reusable_map_0,
3755
memory::vector<fixed_point_t>& reusable_map_1
3856
) {
57+
ProductionType const* const old_production_type = production_type_nullable;
58+
set_production_type(pick_production_type(pop, values_from_province));
59+
if (production_type_nullable == nullptr) {
60+
return;
61+
}
62+
63+
ProductionType const& production_type = *production_type_nullable;
64+
if (production_type_nullable != old_production_type) {
65+
//TODO sell stockpile no longer used
66+
stockpile.clear();
67+
for (auto const& [input_good, base_demand] : production_type.get_input_goods()) {
68+
stockpile[input_good] = base_demand * pop.get_size() / production_type.get_base_workforce_size();
69+
}
70+
}
71+
3972
CountryInstance* country_to_report_economy_nullable = pop.get_location()->get_country_to_report_economy();
4073
max_quantity_to_buy_per_good.clear();
4174
IndexedFlatMap<GoodDefinition, char>& wants_more_mask = reusable_goods_mask;
@@ -137,7 +170,7 @@ void ArtisanalProducer::artisan_tick(
137170
}
138171

139172
//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;
173+
const fixed_point_t total_cash_to_spend = pop.get_cash().get_copy_of_value() / values_from_province.get_max_cost_multiplier();
141174
MarketInstance const& market_instance = pop.get_market_instance();
142175

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

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
}

0 commit comments

Comments
 (0)