Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Factory potentials & Economies of scale for factories #1892

Merged
merged 18 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion assets/localisation/en-US/alice.csv
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ available_for;available for:
support;support
percentage;percentage
stockpile;stockpile
potentials;Potentials
this_nat_religion;the national religion of this country
from_nat_religion;the national religion of the from country
event_show_requirements;Requirements for the event
Expand Down Expand Up @@ -501,6 +502,8 @@ factory_upgrade_condition_7;The investment target is not at war with you
factory_upgrade_condition_8;The ruling party allows upgrading factories
factory_upgrade_condition_9;The factory is not already being upgraded
factory_upgrade_condition_10;The factory is not at max level
factory_build_condition_11;State has available resource potentials
factory_upgrade_condition_11;State has available resource potentials
factory_delete_header;Delete factory
factory_delete_not_allowed;The ruling party does not allow us to delete factories
factory_stats_1;This factory is currently operating at $val$ capacity
Expand All @@ -509,6 +512,7 @@ factory_stats_3;It produced $val$ units of $x$ in the previous day
factory_stats_4;It made a profit of $val$ in the previous day
factory_stats_5;Inputs availability:
factory_stats_6;Efficiency inputs availability:
factory_stats_7;Economy of scale throughput bonus: ?G+$val$?!
factory_stats_wage;Wage:
factory_stats_expenses;Total expenses
factory_stats_desired_income;Desired raw profit
Expand Down Expand Up @@ -1404,4 +1408,4 @@ alice_culture_filter_header;Culture
alice_religion_filter_header;Religion
alice_location_filter_header;Location
alice_pop_show_details;Individual Details
alice_demographics_window_header;Demographics
alice_demographics_window_header;Demographics
16 changes: 16 additions & 0 deletions docs/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -729,3 +729,19 @@ rgo_distribution_add = {
}
}
```

### Factory potentials

Factory potentials (e.g. resource potentials) is a feature that allows modders to limit number of factories per province (state).

To limit factory creation to provinces having potentials, factory definition must have `uses_potentials = yes`.

In province history files define limits as follows:
```
factory_limit = {
entry = {
trade_good = iron
max_level = 16
}
}
```
20 changes: 12 additions & 8 deletions src/ai/ai.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1127,13 +1127,14 @@ void get_craved_factory_types(sys::state& state, dcon::nation_id nid, dcon::mark
assert(desired_types.empty());
auto n = dcon::fatten(state.world, nid);
auto m = dcon::fatten(state.world, mid);
auto sid = m.get_zone_from_local_market();

auto const tax_eff = nations::tax_efficiency(state, n);
auto const rich_effect = (1.0f - tax_eff * float(state.world.nation_get_rich_tax(n)) / 100.0f);

if(desired_types.empty()) {
for(auto type : state.world.in_factory_type) {
if(n.get_active_building(type) || type.get_is_available_from_start()) {
if(command::can_begin_factory_building_construction(state, nid, sid, type, false) || command::can_begin_factory_building_construction(state, nid, sid, type, true)) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SneakBug8 This is changing a very cheap check into two very expensive checks. If the expensive checks are necessary, the code should be written so that they are taken a minimal number of times (for example, it shouldn't be doing duplicate work checking for both upgrades and new construction)

float cost = economy::factory_type_build_cost(state, n, m, type);
float output = economy::factory_type_output_cost(state, n, m, type);
float input = economy::factory_type_input_cost(state, n, m, type);
Expand All @@ -1152,6 +1153,7 @@ void get_desired_factory_types(sys::state& state, dcon::nation_id nid, dcon::mar
assert(desired_types.empty());
auto n = dcon::fatten(state.world, nid);
auto m = dcon::fatten(state.world, mid);
auto sid = m.get_zone_from_local_market();

auto const tax_eff = nations::tax_efficiency(state, n);
auto const rich_effect = (1.0f - tax_eff * float(state.world.nation_get_rich_tax(n)) / 100.0f);
Expand All @@ -1161,7 +1163,7 @@ void get_desired_factory_types(sys::state& state, dcon::nation_id nid, dcon::mar
// which are impossible to ignore if you are sane
if(desired_types.empty()) {
for(auto type : state.world.in_factory_type) {
if(n.get_active_building(type) || type.get_is_available_from_start()) {
if(command::can_begin_factory_building_construction(state, nid, sid, type, false) || command::can_begin_factory_building_construction(state, nid, sid, type, true)) {
auto& inputs = type.get_inputs();
bool lacking_input = false;
bool lacking_output = m.get_demand_satisfaction(type.get_output()) < 0.98f;
Expand Down Expand Up @@ -1204,7 +1206,7 @@ void get_desired_factory_types(sys::state& state, dcon::nation_id nid, dcon::mar
// or have very high margins
if(desired_types.empty()) {
for(auto type : state.world.in_factory_type) {
if(n.get_active_building(type) || type.get_is_available_from_start()) {
if(command::can_begin_factory_building_construction(state, nid, sid, type, false) || command::can_begin_factory_building_construction(state, nid, sid, type, true)) {
auto& inputs = type.get_inputs();
bool lacking_input = false;
bool lacking_output = m.get_demand_satisfaction(type.get_output()) < 0.98f;
Expand Down Expand Up @@ -1245,7 +1247,7 @@ void get_desired_factory_types(sys::state& state, dcon::nation_id nid, dcon::mar
// second pass: try to create factories which have a good profit margin
if(desired_types.empty()) {
for(auto type : state.world.in_factory_type) {
if(n.get_active_building(type) || type.get_is_available_from_start()) {
if(command::can_begin_factory_building_construction(state, nid, sid, type, false) || command::can_begin_factory_building_construction(state, nid, sid, type, true)) {
auto& inputs = type.get_inputs();
bool lacking_input = false;
bool lacking_output = m.get_demand_satisfaction(type.get_output()) < 0.98f;
Expand Down Expand Up @@ -1288,11 +1290,13 @@ void get_state_craved_factory_types(sys::state& state, dcon::nation_id nid, dcon
assert(desired_types.empty());
auto n = dcon::fatten(state.world, nid);
auto m = dcon::fatten(state.world, mid);
auto sid = m.get_zone_from_local_market();
auto treasury = n.get_stockpiles(economy::money);

if(desired_types.empty()) {
for(auto type : state.world.in_factory_type) {
if(n.get_active_building(type) || type.get_is_available_from_start()) {
if(command::can_begin_factory_building_construction(state, nid, sid, type, false) || command::can_begin_factory_building_construction(state, nid, sid, type, true)) {

float cost = economy::factory_type_build_cost(state, n, m, type) + 0.1f;
float output = economy::factory_type_output_cost(state, n, m, type);
float input = economy::factory_type_input_cost(state, n, m, type) + 0.1f;
Expand All @@ -1308,13 +1312,13 @@ void get_state_desired_factory_types(sys::state& state, dcon::nation_id nid, dco
assert(desired_types.empty());
auto n = dcon::fatten(state.world, nid);
auto m = dcon::fatten(state.world, mid);

auto sid = m.get_zone_from_local_market();
auto treasury = n.get_stockpiles(economy::money);

// first pass: try to create factories which will pay back investment fast - in a year at most:
if(desired_types.empty()) {
for(auto type : state.world.in_factory_type) {
if(n.get_active_building(type) || type.get_is_available_from_start()) {
if(command::can_begin_factory_building_construction(state, nid, sid, type, false) || command::can_begin_factory_building_construction(state, nid, sid, type, true)) {
auto& inputs = type.get_inputs();
bool lacking_input = false;
bool lacking_output = m.get_demand_satisfaction(type.get_output()) < 0.98f;
Expand All @@ -1341,7 +1345,7 @@ void get_state_desired_factory_types(sys::state& state, dcon::nation_id nid, dco
// second pass: try to create factories which have a good profit margin
if(desired_types.empty()) {
for(auto type : state.world.in_factory_type) {
if(n.get_active_building(type) || type.get_is_available_from_start()) {
if(command::can_begin_factory_building_construction(state, nid, sid, type, false) || command::can_begin_factory_building_construction(state, nid, sid, type, true)) {
auto& inputs = type.get_inputs();
bool lacking_input = false;
bool lacking_output = m.get_demand_satisfaction(type.get_output()) < 0.98f;
Expand Down
50 changes: 46 additions & 4 deletions src/economy/economy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1884,7 +1884,7 @@ float factory_input_multiplier(sys::state const& state, dcon::factory_id fac, dc
));
}

float factory_throughput_multiplier(sys::state const& state, dcon::factory_type_id fac_type, dcon::nation_id n, dcon::province_id p, dcon::state_instance_id s) {
float factory_throughput_multiplier(sys::state const& state, dcon::factory_type_id fac_type, dcon::nation_id n, dcon::province_id p, dcon::state_instance_id s, int levels) {
auto output = state.world.factory_type_get_output(fac_type);
auto national_t = state.world.nation_get_factory_goods_throughput(n, output);
auto provincial_fac_t = state.world.province_get_modifier_values(p, sys::provincial_mod_offsets::local_factory_throughput);
Expand All @@ -1893,7 +1893,8 @@ float factory_throughput_multiplier(sys::state const& state, dcon::factory_type_
auto result = 1.f
* std::max(0.f, 1.f + national_t)
* std::max(0.f, 1.f + provincial_fac_t)
* std::max(0.f, 1.f + nationnal_fac_t);
* std::max(0.f, 1.f + nationnal_fac_t) *
(1.f + levels / 100.f) /* Economies of scale throughput bonus */;

return production_throughput_multiplier * result;
}
Expand Down Expand Up @@ -1955,7 +1956,7 @@ void update_single_factory_consumption(

float input_multiplier = factory_input_multiplier(state, fac, n, p, s);
auto const mfactor = state.world.nation_get_modifier_values(n, sys::national_mod_offsets::factory_maintenance) + 1.0f;
float throughput_multiplier = factory_throughput_multiplier(state, fac_type, n, p, s);
float throughput_multiplier = factory_throughput_multiplier(state, fac_type, n, p, s, fac.get_level());
float output_multiplier = factory_output_multiplier(state, fac, n, m, p);

float bonus_profit_thanks_to_max_e_input = fac_type.get_output_amount()
Expand Down Expand Up @@ -7540,7 +7541,7 @@ float nation_factory_consumption(sys::state& state, dcon::nation_id n, dcon::com
//modifiers

float input_multiplier = factory_input_multiplier(state, fac, n, p, s);
float throughput_multiplier = factory_throughput_multiplier(state, fac_type, n, p, s);
float throughput_multiplier = factory_throughput_multiplier(state, fac_type, n, p, s, fac.get_level());
float output_multiplier = factory_output_multiplier(state, fac, n, m, p);

//this value represents total production if 1 lvl of this factory is filled with workers
Expand Down Expand Up @@ -8969,4 +8970,45 @@ float estimate_investment_pool_daily_loss(sys::state& state, dcon::nation_id n)
return state.world.nation_get_private_investment(n) * 0.001f;
}

// Does this commodity has any factory using potentials mechanic
// Since in vanilla there are no such factories, it will return false.
bool get_commodity_uses_potentials(sys::state& state, dcon::commodity_id c) {
for(auto type : state.world.in_factory_type) {
if(type.get_output() == c && type.get_uses_potentials()) {
return true;
}
}
return false;
}

int8_t calculate_state_factory_limit(sys::state& state, dcon::state_instance_id sid, dcon::commodity_id c) {
int8_t res = 0;
for(auto p : state.world.in_province) {
if(p.get_state_membership() != sid) {
continue;
}

if(p.get_factory_limit_was_set_during_scenario_creation()) {
res += p.get_factory_max_level_per_good(c);
}
}

return res;
}

int32_t calculate_nation_factory_limit(sys::state& state, dcon::nation_id nid, dcon::commodity_id c) {
int32_t res = 0;
for(auto p : state.world.in_province) {
if(p.get_nation_from_province_ownership() != nid) {
continue;
}

if(p.get_factory_limit_was_set_during_scenario_creation()) {
res += p.get_factory_max_level_per_good(c);
}
}

return res;
}

} // namespace economy
6 changes: 5 additions & 1 deletion src/economy/economy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ float factory_e_input_total_cost(sys::state const& state, dcon::market_id m, dco
// abstract modifiers

float factory_input_multiplier(sys::state const& state, dcon::factory_id fac, dcon::nation_id n, dcon::province_id p, dcon::state_instance_id s);
float factory_throughput_multiplier(sys::state const& state, dcon::factory_type_id fac_type, dcon::nation_id n, dcon::province_id p, dcon::state_instance_id s);
float factory_throughput_multiplier(sys::state const& state, dcon::factory_type_id fac_type, dcon::nation_id n, dcon::province_id p, dcon::state_instance_id s, int levels);
float factory_output_multiplier(sys::state const& state, dcon::factory_id fac, dcon::nation_id n, dcon::market_id m, dcon::province_id p);

float factory_desired_raw_profit(dcon::factory_id fac, float spendings);
Expand Down Expand Up @@ -453,6 +453,10 @@ float max_loan(sys::state& state, dcon::nation_id n);

float estimate_investment_pool_daily_loss(sys::state& state, dcon::nation_id n);

bool get_commodity_uses_potentials(sys::state& state, dcon::commodity_id c);
int8_t calculate_state_factory_limit(sys::state& state, dcon::state_instance_id sid, dcon::commodity_id c);
int32_t calculate_nation_factory_limit(sys::state& state, dcon::nation_id nid, dcon::commodity_id c);

command::budget_settings_data budget_minimums(sys::state& state, dcon::nation_id n);
command::budget_settings_data budget_maximums(sys::state& state, dcon::nation_id n);
} // namespace economy
46 changes: 46 additions & 0 deletions src/gamestate/commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,52 @@ bool can_begin_factory_building_construction(sys::state& state, dcon::nation_id
}
}

/* If mod uses Factory Province limits */
if(state.world.factory_type_get_uses_potentials(type)) {
auto output = state.world.factory_type_get_output(type);
auto limit = economy::calculate_state_factory_limit(state, location, output);
auto d = state.world.state_instance_get_definition(location);
if(is_upgrade) {
// Will upgrade put us over the limit?
for(auto p : state.world.state_definition_get_abstract_state_membership(d)) {
if(p.get_province().get_nation_from_province_ownership() == owner) {
for(auto f : p.get_province().get_factory_location()) {
if(f.get_factory().get_building_type() == type && f.get_factory().get_level() + 1 > limit) {
return false;
}
}
}
}
} else if(refit_target) {
auto refit_levels = 0;
// How many levels changed factory has
for(auto p : state.world.state_definition_get_abstract_state_membership(d)) {
if(p.get_province().get_nation_from_province_ownership() == owner) {
for(auto f : p.get_province().get_factory_location()) {
if(f.get_factory().get_building_type() == type) {
refit_levels = f.get_factory().get_level();
}
}
}
}
// Will that put us over the limit?
for(auto p : state.world.state_definition_get_abstract_state_membership(d)) {
if(p.get_province().get_nation_from_province_ownership() == owner) {
for(auto f : p.get_province().get_factory_location()) {
if(f.get_factory().get_building_type() == refit_target && f.get_factory().get_level() + refit_levels > limit) {
return false;
}
}
}
}
} else {
// Is there the limit?
if(limit <= 1) {
return false;
}
}
}

if(is_upgrade) {
// no double upgrade
for(auto p : state.world.state_instance_get_state_building_construction(location)) {
Expand Down
15 changes: 15 additions & 0 deletions src/gamestate/dcon_generated.txt
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,11 @@ object {
type{ bitfield }
tag{ scenario }
}
property{
name{ uses_potentials }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this being stored in the factory type when everything else is tied to the commodity? I don't think that your syntax for this feature allows it to attach to some factory types and not others if they happen to produce the same output

type{ bitfield }
tag{ scenario }
}
property{
name{ inputs }
type{ economy::commodity_set }
Expand Down Expand Up @@ -1534,6 +1539,16 @@ object {
type{ array{commodity_id}{float} }
tag{ save }
}
property{
name{ factory_limit_was_set_during_scenario_creation }
type{ bitfield }
tag{ scenario }
}
property{
name{ factory_max_level_per_good }
type{ array{commodity_id}{uint8_t} }
tag{ scenario }
}
property{
name{ rgo_target_employment_per_good }
type{ array{commodity_id}{float} }
Expand Down
1 change: 1 addition & 0 deletions src/gamestate/system_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2334,6 +2334,7 @@ void state::load_scenario_data(parsers::error_handler& err, sys::year_month_day
});

world.province_resize_rgo_max_size_per_good(world.commodity_size());
world.province_resize_factory_max_level_per_good(world.commodity_size());

// load province history files
auto history = open_directory(root, NATIVE("history"));
Expand Down
19 changes: 19 additions & 0 deletions src/gui/economy_viewer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ void update(sys::state& state) {
state.iui_state.per_nation_data[n.index()] = economy::stockpile(state, n, state.selected_trade_good);
break;

case iui::commodity_info_mode::potentials:
{
state.iui_state.per_nation_data[n.index()] = (float) economy::calculate_nation_factory_limit(state, n, state.selected_trade_good);
break;
}

case iui::commodity_info_mode::balance:
{
auto supply = economy::supply(state, n, state.selected_trade_good);
Expand Down Expand Up @@ -241,6 +247,15 @@ void update(sys::state& state) {
state.iui_state.per_market_data[market.index()] = state.world.market_get_stockpile(market, state.selected_trade_good);
break;

case iui::commodity_info_mode::potentials:
{
auto loc = state.world.market_get_zone_from_local_market(market);
state.iui_state.per_market_data[market.index()] = (float) economy::calculate_state_factory_limit(state, loc, state.selected_trade_good);
cut_away_negative = true;
start_min_at_zero = true;
break;
}

case iui::commodity_info_mode::balance:
{
auto supply = state.world.market_get_supply(market, state.selected_trade_good);
Expand Down Expand Up @@ -806,6 +821,10 @@ void render(sys::state& state) {
for(uint8_t i = 0; i < uint8_t(iui::commodity_info_mode::total); i++) {
iui::rect button_rect = { size_selector_w + 10.f, screen_size.y - 350.f + i * view_mode_height, view_mode_width, view_mode_height };

// Don't show Potentials buttons if there are no potentials for this good
if(!economy::get_commodity_uses_potentials(state, state.selected_trade_good) && (uint8_t)iui::commodity_info_mode::potentials == i) {
continue;
}
if(state.iui_state.button_textured(
state, (int32_t)(static_elements::commodities_mode_selector) + i,
button_rect, 3, state.iui_state.top_bar_button.texture_handle,
Expand Down
4 changes: 3 additions & 1 deletion src/gui/immediate_mode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ enum class alignment_horizontal : uint8_t {

enum class commodity_info_mode : uint8_t {
price, supply, demand, balance, trade_in, trade_out, trade_balance,
production, consumption, stockpiles,
production, consumption, stockpiles, potentials,
total
};

Expand All @@ -55,6 +55,8 @@ std::string inline localize_commodity_info_mode(commodity_info_mode mode) {
return "trade_out";
case commodity_info_mode::trade_balance:
return "trade_balance";
case commodity_info_mode::potentials:
return "potentials";
case commodity_info_mode::total:
return "INVALID";
default:
Expand Down
Loading
Loading