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