Symphony Of Empires
economy.cpp
Go to the documentation of this file.
1 // Symphony of Empires
2 // Copyright (C) 2021, Symphony of Empires contributors
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <https://www.gnu.org/licenses/>.
16 //
17 // ----------------------------------------------------------------------------
18 // Name:
19 // server/economy.cpp
20 //
21 // Abstract:
22 // Does some important stuff.
23 // ----------------------------------------------------------------------------
24 
25 #include <algorithm>
26 #include <cstdio>
27 #include <map>
28 #include <tbb/blocked_range.h>
29 #include <tbb/concurrent_vector.h>
30 #include <tbb/parallel_for.h>
31 #include <tbb/combinable.h>
32 #include <glm/gtx/compatibility.hpp>
33 
34 #include "eng3d/log.hpp"
35 #include "eng3d/serializer.hpp"
36 #include "eng3d/rand.hpp"
37 
38 #include "action.hpp"
39 #include "server/economy.hpp"
40 #include "world.hpp"
42 #include "product.hpp"
43 #include "emigration.hpp"
44 
45 #undef min
46 #undef max
47 
48 // Visual Studio does not define ssize_t because it's a POSIX-only type
49 #ifdef _MSC_VER
50 typedef signed int ssize_t;
51 #endif
52 
53 struct PopNeed {
54  float life_needs_met = 0.f;
55  float budget = 0.f;
56 };
57 
58 struct NewUnit {
60  float size;
63  NewUnit(UnitTypeId _type_id, float _size, ProvinceId _province_id, PopTypeId _pop_id)
64  : type_id{ _type_id },
65  size{ _size },
66  province_id{ _province_id },
67  pop_id{ _pop_id }
68  {
69 
70  }
71 };
72 
73 constexpr auto scale_speed(auto c, auto target) {
74  constexpr auto friction = 0.1f;
75  return (c * (1.f - friction)) + friction * target;
76 }
77 
78 // Updates supply, demand, and set wages for workers
79 static void update_industry_production(World& world, Building& building, const BuildingType& building_type, Province& province, float& artisans_amount, float& artisan_payment)
80 {
81  constexpr auto artisan_production_rate = 0.01f;
82  auto& output = world.commodities[building_type.output_id];
83  auto& output_product = province.products[output];
84  if(!building.can_do_output(province, building_type.input_ids) || building.level == 0.f) { // Artisans take place of industry
85  if(output_product.supply < output_product.demand) { // Artisans only produce iff suitable so
86  const auto output_amount = artisans_amount * artisan_production_rate; // Reduced rate of production
87  artisan_payment += output_product.produce(output_amount);
88  }
89  return;
90  }
91 
92  // TODO add input modifier
93  building.expenses.inputs_cost = 0.f; // Buy the inputs for the industry
94  for(const auto& [product_id, amount] : building_type.req_goods) {
95  auto& product = province.products[product_id];
96  const auto max_amount = glm::min(amount * building.production_scale, product.supply);
97  building.expenses.inputs_cost += product.buy(max_amount);
98  }
99  output_product.produce(building.get_output_amount());
100 }
101 
102 static void update_industry_accounting(World& world, Building& building, const BuildingType& building_type, Province& province, float& pop_payment, float& state_payment, float& private_payment)
103 {
104  // Barracks and so on
105  if(Commodity::is_invalid(building_type.output_id)) return;
106  const auto& nation = world.nations[province.controller_id];
107 
108  // TODO: Make it so burgeoise aren't duplicating money out of thin air
109  const auto private_investment = private_payment * 0.8f * nation.current_policy.private_ownership;
110  building.estate_private.invest(private_investment);
111  building.budget += private_investment;
112 
113  auto& output = world.commodities[building_type.output_id];
114  auto& output_product = province.products[output];
115  if(!building.can_do_output(province, building_type.input_ids) || building.level == 0.f) // Artisans take place of industry
116  return;
117 
118  // TODO add output modifier
119  // Calculate outputs
120 
121  // TODO set min wages
122  float min_wage = glm::max(nation.current_policy.min_wage, glm::epsilon<float>());
123 
124  building.expenses.wages = min_wage * building.workers;
125  pop_payment += building.expenses.wages;
126  auto profit = building.get_profit();
127 
128  // Taxation occurs even without a surplus
129  building.expenses.state_taxes = 0.f;
130  if(profit > 0.f) {
131  building.expenses.state_taxes = profit * nation.current_policy.industry_profit_tax;
132  state_payment += building.expenses.state_taxes;
133  profit -= building.expenses.state_taxes;
134  }
135 
136  // Dividends that will be paid to the shareholders
137  building.expenses.state_dividends = building.expenses.pop_dividends = building.expenses.private_dividends = 0.f; // Dividends are paid after calculating the surplus
138  auto surplus = profit - building.expenses.get_total();
139  if(surplus > 0.f) {
140  // Disperse profits to holders
141  // TODO: Should surplus be decremented between each payout?
142  const auto state_dividends = building.estate_state.get_dividends(surplus,
143  building.estate_state.get_ownership(building.get_total_investment()));
144  state_payment += state_dividends;
145  building.expenses.state_dividends += state_dividends;
146  surplus -= state_dividends;
147 
148  const auto private_dividends = building.estate_private.get_dividends(surplus,
149  building.estate_private.get_ownership(building.get_total_investment()));
150  private_payment += private_dividends;
151  building.expenses.private_dividends += private_dividends;
152  surplus -= private_dividends;
153 
154  const auto collective_dividends = building.estate_collective.get_dividends(surplus,
156  pop_payment += collective_dividends;
157  building.expenses.pop_dividends += collective_dividends;
158  surplus -= collective_dividends;
159 
160  profit -= building.expenses.get_dividends();
161  }
162 
163  building.budget += profit; // Pay the remainder profit to the building
164  // TODO: Make building inoperate for the legnth of the upgrade (need to acquire materials)
165  const auto upgrade_cost = building.get_upgrade_cost();
166  if(building.budget >= upgrade_cost) {
167  building.budget -= upgrade_cost;
168  building.level += 1.f;
169  } else if(building.budget <= -(upgrade_cost * 0.5f)) {
170  building.budget += upgrade_cost;
171  building.level -= 1.f;
172  }
173 
175  // auto base_production = 1.f;
176  // // Capitalist nations require having a stake first; whereas non-capitalist nations do not
177  // // require a pevious stake to exist at all to enforce their production scales
178  // if(!nation.can_directly_control_factories()) {
179  // base_production = building.state_ownership * nation.commodity_production[output] * building.level;
180  // } else {
181  // base_production = nation.commodity_production[output] * building.level;
182  // }
183 
184  // Rescale production
185  // This is used to set how much the of the maximum capacity the industry produce
186  const auto max_revenue = output_product.price * building.get_max_output_amount(building_type.num_req_workers);
187  if(max_revenue == 0.f) {
188  building.production_scale = scale_speed(building.production_scale, 0.f);
189  } else if(building.expenses.get_total() == 0.f) {
190  building.production_scale = scale_speed(building.production_scale, building.level);
191  } else {
192  building.production_scale = scale_speed(building.production_scale, building.level * glm::clamp(max_revenue / building.expenses.get_total(), 0.f, 1.f));
193  }
194 }
195 
196 // Update the industry employment
197 static void update_factories_employment(const World& world, Province& province, std::vector<float>& new_workers) {
198  auto unallocated_workers = province.pops[(int)PopGroup::LABORER].size;
199  // Sort factories by production scale, which is suppose to represent how profitable the industry is
200  // Might be better to calculate how profitable it really is and use that instead
201  std::vector<std::pair<size_t, float>> factories_by_profitability;
202  for(size_t i = 0; i < province.buildings.size(); i++)
203  factories_by_profitability.emplace_back(i, province.buildings[i].production_scale);
204  std::sort(factories_by_profitability.begin(), factories_by_profitability.end(), [](const auto& a, const auto& b) { return a.second > b.second; });
205 
206  float is_operating = province.controller_id == province.owner_id ? 1.f : 0.f;
207  for(const auto& [industry_index, _] : factories_by_profitability) {
208  auto& building = province.buildings[industry_index];
209  const auto& type = world.building_types[industry_index];
210  auto industry_workers = building.production_scale * type.num_req_workers;
211  auto allocated_workers = glm::max(glm::min(industry_workers, unallocated_workers), 0.f);
212  // Average with how much the industry had before
213  // Makes is more stable so everyone don't change workplace immediately
214  new_workers[industry_index] = (allocated_workers / 16.0f + (building.workers * 15.0f) / 16.0f) * is_operating;
215  unallocated_workers -= building.workers * is_operating;
216  }
217 }
218 
220 void update_pop_needs(World& world, Province& province, std::vector<PopNeed>& pop_needs, float& state_payment) {
221  auto& nation = world.nations[province.controller_id];
222 
223  std::vector<float> goods_payment(world.commodities.size(), 0.f);
224  for(size_t i = 0; i < province.pops.size(); i++) {
225  auto& pop_need = pop_needs[i];
226  auto& pop = province.pops[i];
227  const auto& needs_amounts = world.pop_types[pop.type_id].basic_needs_amount;
228  if(pop.size == 0.f || pop_need.budget == 0.f) return;
229 
230  const auto percentage_to_spend = 0.8f;
231  const auto budget_alloc = pop_need.budget * percentage_to_spend;
232  pop_need.budget -= budget_alloc;
233 
234  // If we are going to have value added taxes we should separate them from income taxes
235  state_payment += budget_alloc * nation.current_policy.pop_tax;
236  const auto budget_after_VAT = budget_alloc * (1.f - nation.current_policy.pop_tax);
237  const auto budget_per_pop = budget_after_VAT / pop.size;
238 
239  auto total_factor = std::reduce(needs_amounts.begin(), needs_amounts.end());
240  for(const auto& commodity : world.commodities) {
241  if(needs_amounts[commodity] <= 0.f) continue;
242 
243  auto& product = province.products[commodity];
244  const auto need_factor = needs_amounts[commodity] / total_factor;
245  auto amount = glm::min(budget_per_pop * need_factor / product.price, product.supply);
246 
247  pop_need.life_needs_met += amount * need_factor;
248  const auto payment = product.buy(amount);
249  goods_payment[commodity] += payment;
250  }
251  }
252  for(const auto& building_type : world.building_types)
253  province.buildings[building_type].revenue.outputs += goods_payment[building_type.output_id];
254 }
255 
256 std::vector<Economy::Market> init_markets(const World& world) {
257  std::vector<Economy::Market> markets(world.commodities.size());
258  for(const auto& commodity : world.commodities)
259  markets[commodity].commodity = commodity.get_id();
260  tbb::parallel_for(tbb::blocked_range(markets.begin(), markets.end()), [&world](const auto& markets_range) {
261  for(auto& market : markets_range) {
262  market.price.resize(world.provinces.size());
263  market.supply.resize(world.provinces.size());
264  market.demand.resize(world.provinces.size());
265  market.global_demand = std::vector(world.provinces.size(), 0.f);
266  for(const auto& province : world.provinces) {
267  const auto& product = province.products[market.commodity];
268  market.demand[province] = product.demand;
269  market.price[province] = product.price;
270  market.supply[province] = product.supply;
271  }
272  }
273  });
274  return markets;
275 }
276 
277 void update_markets(const World& world, std::vector<Economy::Market> markets) {
278  tbb::parallel_for(tbb::blocked_range(markets.begin(), markets.end()), [&world](const auto& markets_range) {
279  for(auto& market : markets_range) {
280  for(const auto& province : world.provinces) {
281  const auto& product = province.products[market.commodity];
282  market.demand[province] = product.demand;
283  market.price[province] = product.price;
284  market.supply[province] = product.supply;
285  }
286  }
287  });
288 }
289 
290 void Economy::do_tick(World& world, EconomyState& economy_state) {
291  // Distrobute products accross
292  world.profiler.start("E-init");
293 
294  auto& markets = economy_state.commodity_market;
295  if(markets.empty())
296  markets = init_markets(world);
297  update_markets(world, markets);
298  world.profiler.stop("E-init");
299 
300  world.profiler.start("E-trade");
301  economy_state.trade.recalculate(world);
302  auto& trade = economy_state.trade;
303 
304  tbb::parallel_for(tbb::blocked_range(markets.begin(), markets.end()), [&world, &trade](const auto& markets_range) {
305  std::vector<float> values(world.provinces.size(), 0.f);
306  for(auto& market : markets_range) {
307  for(const auto province_id : trade.cost_eval) {
308  auto& province = world.provinces[province_id];
309  if(Nation::is_invalid(province.owner_id)) continue;
310  auto& nation = world.nations[province.owner_id];
311  auto sum_weightings = 0.f;
312  for(const auto other_province_id : nation.owned_provinces) {
313  auto price = market.price[other_province_id];
314  auto apparent_price = price + 0.01f * trade.trade_costs[province_id][other_province_id];
315  apparent_price += glm::epsilon<float>();
316  values[other_province_id] = market.supply[other_province_id] / (apparent_price * apparent_price);
317  sum_weightings += values[other_province_id];
318  }
319  if(sum_weightings == 0.f) continue;
320  for(const auto other_province_id : nation.owned_provinces) {
321  values[other_province_id] /= sum_weightings;
322  auto demand = market.demand[other_province_id];
323  market.global_demand[province_id] += demand * values[other_province_id];
324  }
325  }
326  for(const auto province_id : trade.cost_eval) {
327  const auto price_change_speed = 0.9f;
328  const auto price_factor = market.global_demand[province_id] / (market.supply[province_id] + 0.01f);
329  const auto new_price = market.price[province_id] * (1.f - price_change_speed) + (market.price[province_id] * price_factor) * price_change_speed;
330 
331  auto& province = world.provinces[province_id];
332  if(Nation::is_invalid(province.owner_id)) continue;
333  auto& product = province.products[market.commodity];
334  //product.price = std::max(new_price, 0.01f);
335  product.global_demand = market.global_demand[province_id];
336  }
337  }
338  });
339  world.profiler.stop("E-trade");
340 
341  world.profiler.start("E-big");
342  tbb::combinable<std::vector<NewUnit>> province_new_units;
344  std::vector<std::vector<float>> buildings_new_worker(world.provinces.size());
345  std::vector<std::vector<PopNeed>> pops_new_needs(world.provinces.size());
346 
347  tbb::parallel_for(static_cast<size_t>(0), world.provinces.size(), [&world, &buildings_new_worker, &province_new_units, &pops_new_needs, &paid_taxes](const auto province_id) {
348  auto& province = world.provinces[province_id];
349  if(Nation::is_invalid(province.controller_id)) return;
350  for(auto& product : province.products)
351  product.close_market();
352 
353  auto& new_needs = pops_new_needs[province_id];
354  new_needs.assign(province.pops.size(), PopNeed{});
355 
356  auto laborers_payment = 0.f, artisans_payment = 0.f, state_payment = 0.f, private_payment = 0.f, bureaucrats_payment = 0.f;
357 
358  auto artisans_amount = province.pops[(int)PopGroup::ARTISAN].size;
359  auto burgeoise_amount = province.pops[(int)PopGroup::BURGEOISE].size;
360  auto laborers_amount = province.pops[(int)PopGroup::LABORER].size;
361  auto bureaucrats_amount = province.pops[(int)PopGroup::BUREAUCRAT].size;
362  auto bureaucracy_pts = bureaucrats_amount
363  * (1.f - province.pops[(int)PopGroup::BUREAUCRAT].militancy)
364  * province.pops[(int)PopGroup::BUREAUCRAT].literacy;
365 
366  for(auto& building_type : world.building_types) {
367  auto& building = province.buildings[building_type];
368  building.revenue.outputs = 0.f;
369  update_industry_production(world, building, building_type, province, artisans_amount, artisans_payment);
370  }
371 
372  for(size_t i = 0; i < province.pops.size(); i++) {
373  const auto& pop = province.pops[i];
374  new_needs[i].budget = pop.budget;
375  new_needs[i].life_needs_met = glm::clamp(pop.life_needs_met - 0.1f, -1.f, 1.f);
376  }
377 
378  // Bureaucracy points
379  auto bureaucracy_eff = (province.total_pops() * province.average_militancy()) / bureaucracy_pts;
380 
381  auto& new_workers = buildings_new_worker[province_id];
382  new_workers.assign(world.building_types.size(), 0.f);
383  update_factories_employment(world, province, new_workers);
384  for(auto& building_type : world.building_types) {
385  auto& building = province.buildings[building_type];
386  update_industry_accounting(world, building, building_type, province, laborers_payment, state_payment, private_payment);
387  }
388 
389  new_needs[(int)PopGroup::LABORER].budget += laborers_payment;
390  new_needs[(int)PopGroup::ARTISAN].budget += artisans_payment;
391  new_needs[(int)PopGroup::BURGEOISE].budget += private_payment;
392  new_needs[(int)PopGroup::BUREAUCRAT].budget += bureaucrats_payment;
393 
394  update_pop_needs(world, province, new_needs, state_payment);
395 
396  paid_taxes.local().resize(world.nations.size());
397  paid_taxes.local()[province.controller_id] = state_payment;
398  for(auto& building : province.buildings) {
399  // There must not be conflict ongoing otherwise they wont be able to build shit
400  if(province.controller_id == province.owner_id && building.can_build_unit() && building.is_working_on_unit()) {
401  auto& pop = province.pops[(int)PopGroup::SOLDIER];
402  const auto final_size = glm::min(pop.size, 100.f);
403  province_new_units.local().emplace_back(building.working_unit_type_id, final_size, province, pop.type_id);
404  building.stop_working_on_unit();
405  }
406  }
407  });
408  world.profiler.stop("E-big");
409 
410  world.profiler.start("E-mutex");
411  // Collect list of nations that exist
412  std::vector<Nation*> eval_nations;
413  for(auto& nation : world.nations)
414  if(nation.exists())
415  eval_nations.push_back(&nation);
416 
417  // -------------------------- MUTEX PROTECTED WORLD CHANGES BELOW -------------------------------
418  const std::scoped_lock lock(world.world_mutex);
419 
420  tbb::parallel_for(static_cast<size_t>(0), world.provinces.size(), [&world, &pops_new_needs, &buildings_new_worker](const auto province_id) {
421  auto& province = world.provinces[province_id];
422  if(Nation::is_invalid(province.controller_id)) return;
423  const auto& new_needs = pops_new_needs[province_id];
424  for(size_t i = 0; i < province.pops.size(); i++) {
425  auto& pop = province.pops[i];
426  pop.budget = new_needs[i].budget;
427  pop.life_needs_met = new_needs[i].life_needs_met;
428  const auto growth = glm::clamp(pop.size * pop.life_needs_met * 0.1f, -100.f, 100.f);
429  pop.size = glm::max(pop.size + growth, 1.f);
430  pop.militancy = glm::clamp(pop.militancy + 0.01f * -pop.life_needs_met, 0.f, 1.f);
431  }
432  const auto& new_workers = buildings_new_worker[province_id];
433  for(size_t i = 0; i < province.buildings.size(); i++)
434  province.buildings[i].workers = new_workers[i];
435  });
436 
437  province_new_units.combine_each([&world](auto& new_unit_list) {
438  for(auto& new_unit : new_unit_list) { // Now commit the transaction of the new units into the main world area
439  const auto& province = world.provinces[new_unit.province_id];
440  const auto& nation = world.nations[province.controller_id];
441  Unit unit{};
442  unit.pop_id = PopId(size_t(new_unit.pop_id));
443  unit.type_id = new_unit.type_id;
444  unit.size = new_unit.size;
445  unit.base = world.unit_types[unit.type_id].max_health;
446  unit.set_owner(nation);
447  world.unit_manager.add_unit(unit, province);
448 
449  Eng3D::Log::debug("economy", string_format("%s has built an unit %s", province.ref_name.c_str(), world.unit_types[unit.type_id].ref_name.c_str()));
450  }
451  });
452 
453  paid_taxes.combine_each([&world](auto& paid_taxes_list) {
454  for(auto& nation : world.nations)
455  nation.budget += paid_taxes_list[nation];
456  });
457 
458  world.profiler.start("Emigration");
459  //do_emigration(world);
460  world.profiler.stop("Emigration");
461  world.profiler.stop("E-mutex");
462 }
A single province, which is used to simulate economy in a "bulk-tiles" way instead of doing economica...
Definition: province.hpp:48
float average_militancy() const
Definition: province.hpp:58
std::array< Pop, 7 > pops
Definition: province.hpp:107
std::vector< Product > products
Definition: province.hpp:109
NationId controller_id
Definition: province.hpp:104
NationId owner_id
Definition: province.hpp:103
float total_pops() const
Definition: province.hpp:51
std::vector< Building > buildings
Definition: province.hpp:110
Roughly a batallion, consisting of approximately 500 soldiers each.
Definition: unit.hpp:80
PopId pop_id
Definition: unit.hpp:129
void add_unit(Unit unit, ProvinceId unit_current_province)
Definition: unit.cpp:121
Definition: world.hpp:114
UnitManager unit_manager
Definition: world.hpp:145
std::mutex world_mutex
Definition: world.hpp:225
Eng3D::Profiler profiler
Definition: world.hpp:130
void update_pop_needs(World &world, Province &province, std::vector< PopNeed > &pop_needs, float &state_payment)
Calculate the budget that we spend on each needs.
Definition: economy.cpp:220
void update_markets(const World &world, std::vector< Economy::Market > markets)
Definition: economy.cpp:277
std::vector< Economy::Market > init_markets(const World &world)
Definition: economy.cpp:256
constexpr auto scale_speed(auto c, auto target)
Definition: economy.cpp:73
void do_tick(World &world, EconomyState &economy_state)
Definition: economy.cpp:290
void debug(const std::string_view category, const std::string_view msg)
Definition: log.cpp:58
std::string string_format(const std::string_view format, Args &&... args)
String formatter.
Definition: string.hpp:100
void parallel_for(T range, F &&func)
void invest(float amount)
Definition: building.hpp:138
float get_ownership(float total_investments) const
Definition: building.hpp:142
float get_dividends(float profit, float ownership) const
Definition: building.hpp:146
A military outpost, on land serves as a "spawn" place for units When adjacent to a water tile this se...
Definition: building.hpp:90
bool is_working_on_unit() const
Definition: building.hpp:125
struct Building::@1 expenses
float get_profit() const
Definition: building.hpp:104
float production_scale
Definition: building.hpp:169
float private_dividends
Definition: building.hpp:191
Investment estate_state
Definition: building.hpp:152
float inputs_cost
Definition: building.hpp:187
float level
Definition: building.hpp:167
Investment estate_private
Definition: building.hpp:151
float outputs
Definition: building.hpp:180
float get_upgrade_cost() const
Definition: building.hpp:99
float get_output_amount() const
Definition: building.hpp:117
float workers
Definition: building.hpp:168
bool can_build_unit() const
Definition: building.hpp:93
float pop_dividends
Definition: building.hpp:190
struct Building::@0 revenue
float get_max_output_amount(float max_workers) const
Definition: building.hpp:121
float budget
Definition: building.hpp:166
float state_taxes
Definition: building.hpp:188
void stop_working_on_unit()
Definition: building.hpp:131
UnitTypeId working_unit_type_id
Definition: building.hpp:171
Investment estate_collective
Definition: building.hpp:153
float state_dividends
Definition: building.hpp:189
bool can_do_output(const Province &province, const std::vector< CommodityId > &inputs) const
Checks if the building can produce output (if it has enough input)
Definition: building.cpp:33
float get_total_investment() const
Definition: building.hpp:156
float wages
Definition: building.hpp:186
Type for military outposts.
Definition: building.hpp:38
std::vector< std::pair< CommodityId, float > > req_goods
Definition: building.hpp:65
float num_req_workers
Definition: building.hpp:61
std::vector< CommodityId > input_ids
Definition: building.hpp:63
CommodityId output_id
Definition: building.hpp:62
std::vector< Market > commodity_market
Definition: economy.hpp:44
void recalculate(const World &world)
Definition: trade.cpp:38
void stop(const std::string &name)
Definition: profiler.cpp:95
void start(const std::string &name)
Definition: profiler.cpp:84
const char * c_str() const
Definition: string.hpp:55
constexpr bool is_invalid() const
Checks if the current id is invalid.
Definition: entity.hpp:133
ProvinceId province_id
Definition: economy.cpp:61
float size
Definition: economy.cpp:60
PopTypeId pop_id
Definition: economy.cpp:62
NewUnit(UnitTypeId _type_id, float _size, ProvinceId _province_id, PopTypeId _pop_id)
Definition: economy.cpp:63
UnitTypeId type_id
Definition: economy.cpp:59
float budget
Definition: economy.cpp:55
float life_needs_met
Definition: economy.cpp:54
Eng3D::StringRef ref_name
Definition: entity.hpp:161
const T & local() const
Definition: combinable.hpp:34
void combine_each(F &&func)
Definition: combinable.hpp:43