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;
1720ArtisanalProducer::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+
3048void 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}
0 commit comments