Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
72 changes: 69 additions & 3 deletions src/economy/advanced_province_buildings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ void update_national_size(sys::state& state) {
auto tmod = state.world.nation_get_modifier_values(owner, sys::national_mod_offsets::education_efficiency) + 1.0f;
auto nmod = state.world.nation_get_modifier_values(owner, sys::national_mod_offsets::education_efficiency_modifier) + 1.0f;

auto cost_of_input = state.world.province_get_labor_price(pids, def.throughput_labour_type);
auto cost_of_input = state.world.province_get_labor_price(pids, def.throughput_labour_type) + 0.001f;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This value is extremely big relatively to vanilla wages. Would be better to figure out why it happens and which provinces have zero price of labor in your case and prevent it.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

That's true, but is there a sufficiently small value that would work as a temporary fix?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The issue is that changing value there would have cascading effects. Game would generate less demand on labor due to higher price but then unsatisfied demand is refunded with the original price. Better alternative in this case is to take max with min labor price, I suppose. After all, if this max is actually doing some work, it means that something had went wrong and if it's idle, then it works the same way. Ideally, there could be an assert against zero price, but then Sneakbug would have to eliminate the source of zeros in his case and I suppose that this value was added to avoid spending time on it in the first place.

Copy link
Copy Markdown
Contributor Author

@SneakBug8 SneakBug8 Mar 1, 2026

Choose a reason for hiding this comment

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

As far as I get it, zero labour price appears when the province doesn't have any supply of the labour type in question.

Thus, having the price as zero seems reasonable to the case.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In all land provinces price is set to not zero from the very start and every update is supposed to maintain this invariant in land provinces.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Is this the only place where we assume non-zero labour prices?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Every single usage of prices assumes that they are non-zero

auto cost_of_output = state.world.province_get_service_price(pids, def.output) * tmod * nmod * def.output_amount;

auto spending_scale = state.world.nation_get_spending_level(owner);
Expand All @@ -354,6 +354,36 @@ void update_national_size(sys::state& state) {
auto weight = ve::select(total_population == 0.f, 0.f, local_population / total_population);
auto local_education_budget = weight * education_budget;
state.world.province_set_advanced_province_building_national_size(pids, bid, ve::max(0.f, local_education_budget / cost_of_input));

#ifndef NDEBUG
ve::apply([&](float s) {
assert(!std::isnan(s));
}, cost_of_input);
ve::apply([&](float s) {
assert(!std::isnan(s));
}, cost_of_output);
ve::apply([&](float s) {
assert(!std::isnan(s));
}, spending_scale);
ve::apply([&](float s) {
assert(!std::isnan(s));
}, national_budget);
ve::apply([&](float s) {
assert(!std::isnan(s));
}, education_priority);
ve::apply([&](float s) {
assert(!std::isnan(s));
}, total_population);
ve::apply([&](float s) {
assert(!std::isnan(s));
}, local_population);
ve::apply([&](float s) {
assert(!std::isnan(s));
}, local_education_budget);
ve::apply([&](float s) {
assert(!std::isnan(s));
}, cost_of_input);
#endif
});
}
}
Expand All @@ -377,9 +407,34 @@ void update_production(sys::state& state) {
state.world.province_set_service_supply_private(pids, def.output, current_private_supply + current_private_size * output);
state.world.province_set_advanced_province_building_private_output(pids, bid, current_private_size * output);

#ifndef NDEBUG
ve::apply([&](float s) {
assert(!std::isnan(s));
}, current_private_supply);
ve::apply([&](float s) {
assert(!std::isnan(s));
}, current_private_size);
ve::apply([&](float s) {
assert(!std::isnan(s));
}, output);
#endif

auto current_public_size = state.world.province_get_advanced_province_building_national_size(pids, bid);
auto current_public_supply = state.world.province_get_service_supply_public(pids, def.output);
state.world.province_set_service_supply_public(pids, def.output, current_public_supply + current_public_size * output);
state.world.province_set_advanced_province_building_private_output(pids, bid, current_public_size * output);

#ifndef NDEBUG
ve::apply([&](float s) {
assert(!std::isnan(s));
}, current_public_supply);
ve::apply([&](float s) {
assert(!std::isnan(s));
}, current_public_size);
ve::apply([&](float s) {
assert(!std::isnan(s));
}, output);
#endif
});

// TODO:
Expand All @@ -403,8 +458,19 @@ void update_production(sys::state& state) {
//assume that low trade volume doesn't require any additional infrastructure or workers
auto output = 100.f + current_private_size * input_satisfaction * def.output_amount * efficiency;
auto current_private_supply = state.world.province_get_service_supply_private(pids, def.output);
state.world.province_set_service_supply_private(pids, def.output, current_private_supply + output);
state.world.province_set_advanced_province_building_private_output(pids, bid, output);
state.world.province_set_service_supply_private(pids, def.output, current_private_supply + current_private_size * output);

#ifndef NDEBUG
ve::apply([&](float s) {
assert(!std::isnan(s));
}, current_private_supply);
ve::apply([&](float s) {
assert(!std::isnan(s));
}, current_private_size);
ve::apply([&](float s) {
assert(!std::isnan(s));
}, output);
#endif
});
}
}
Expand Down
38 changes: 23 additions & 15 deletions src/economy/economy_trade_routes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ namespace economy {
// 1 means that trade profit due to price difference is pocketed by importers
constexpr inline float import_profit_priority = 0.5f;


template<typename TRADE_ROUTE>
auto trade_route_effect_of_scale(sys::state& state, TRADE_ROUTE trade_route) {
using MARKET = convert_value_type<TRADE_ROUTE, dcon::trade_route_id, dcon::market_id>;
Expand All @@ -47,7 +46,8 @@ auto trade_route_effect_of_scale(sys::state& state, TRADE_ROUTE trade_route) {
}


// US3AC2 Labour demand for a single trade route
// US3AC2
// Labour demand for a single trade route
float trade_route_labour_demand(sys::state& state, dcon::trade_route_id trade_route, dcon::province_id A_capital, dcon::province_id B_capital) {
auto cargo = 0.f;
state.world.for_each_commodity([&](auto cid) {
Expand All @@ -64,7 +64,8 @@ float trade_route_labour_demand(sys::state& state, dcon::trade_route_id trade_ro
return total_demanded_labor;
}

// US3AC2 Calculate labour demand for trade routes between markets
// US3AC2 Markets have associated labour costs for trade routes management
// Calculate labour demand for trade routes between markets
float transportation_between_markets_labor_demand(sys::state& state, dcon::market_id market) {

auto total_demanded_labour = 0.f;
Expand All @@ -83,7 +84,8 @@ float transportation_between_markets_labor_demand(sys::state& state, dcon::marke
}


// US3AC3 Calculate labour demand for trade inside the market
// US3AC3 Markets have associated labour costs for trade inside the state
// Calculate labour demand for trade inside the market
float transportation_inside_market_labor_demand(sys::state& state, dcon::market_id market, dcon::province_id capital) {
auto base_cargo_transport_demand = 0.f;

Expand Down Expand Up @@ -360,19 +362,20 @@ trade_route_volume_change_reasons predict_trade_route_volume_change(
auto is_land_route = state.world.trade_route_get_is_land_route(route);
auto same_nation = controller_capital_A == controller_capital_B;

// US3AC7. Ban international sea routes or international land routes based on the corresponding modifiers
// Ban international sea routes or international land routes based on the corresponding modifiers
// US3AC7 If disallow_naval_trade national modifier is non-zero then its states can't trade by sea
auto A_bans_sea_trade = state.world.nation_get_modifier_values(controller_capital_A, sys::national_mod_offsets::disallow_naval_trade) > 0.f;
auto B_bans_sea_trade = state.world.nation_get_modifier_values(controller_capital_B, sys::national_mod_offsets::disallow_naval_trade) > 0.f;
auto sea_trade_banned = A_bans_sea_trade || B_bans_sea_trade;
// US3AC8. Ban international sea routes or international land routes based on the corresponding modifiers
// US3AC8 If disallow_land_trade national modifier is non-zero then its states can't trade by land
auto A_bans_land_trade = state.world.nation_get_modifier_values(controller_capital_A, sys::national_mod_offsets::disallow_land_trade) > 0.f;
auto B_bans_land_trade = state.world.nation_get_modifier_values(controller_capital_B, sys::national_mod_offsets::disallow_land_trade) > 0.f;
auto land_trade_banned = A_bans_land_trade || B_bans_land_trade;
auto trade_banned = (is_sea_route && sea_trade_banned && !same_nation) || (is_land_route && land_trade_banned && !same_nation);

is_sea_route = is_sea_route && !is_A_blockaded && !is_B_blockaded;

// US3AC9. Wartime embargoes
// US3AC9
auto A_joins_sphere_wide_embargo = ve::apply([&](auto n_a, auto n_b) {
return military::are_at_war(state, n_a, n_b);
}, market_leader_A, market_leader_B);
Expand All @@ -381,9 +384,9 @@ trade_route_volume_change_reasons predict_trade_route_volume_change(
return military::are_at_war(state, n_a, n_b);
}, market_leader_B, market_leader_A);

// US3AC10. diplomatic embargos
// US3AC11. sphere joins embargo
// US3AC12 subject joins embargo
// US3AC10
// US3AC11
// US3AC12
auto A_has_embargo = non_war_embargo_status(state, controller_capital_A, controller_capital_B, market_leader_A, market_leader_B);
A_joins_sphere_wide_embargo = A_has_embargo || A_joins_sphere_wide_embargo;

Expand Down Expand Up @@ -411,7 +414,8 @@ trade_route_volume_change_reasons predict_trade_route_volume_change(

auto trade_good_loss_mult = std::max(0.f, 1.f - trade_loss_per_distance_unit * distance);

// US3AC2 we assume that 2 uneducated persons (1 from each market) can transport 1 unit of goods along path of 1 effective day length
// US3AC2
// We assume that 2 uneducated persons (1 from each market) can transport 1 unit of goods along path of 1 effective day length
// we do it this way to avoid another assymetry in calculations
auto transport_cost =
distance / trade_distance_covered_by_pair_of_workers_per_unit_of_good
Expand Down Expand Up @@ -551,7 +555,8 @@ void update_trade_routes_volume(
auto is_sea_route = state.world.trade_route_get_is_sea_route(trade_route) && !is_A_blockaded && !is_B_blockaded;
auto is_land_route = state.world.trade_route_get_is_land_route(trade_route);
auto same_nation = controller_A == controller_B;
// US3AC7 US3AC8 Ban international sea routes or international land routes based on the corresponding modifiers
// US3AC7
// US3AC8
auto A_bans_sea_trade = state.world.nation_get_modifier_values(controller_A, sys::national_mod_offsets::disallow_naval_trade) > 0.f;
auto B_bans_sea_trade = state.world.nation_get_modifier_values(controller_B, sys::national_mod_offsets::disallow_naval_trade) > 0.f;
auto sea_trade_banned = A_bans_sea_trade || B_bans_sea_trade;
Expand Down Expand Up @@ -583,7 +588,8 @@ void update_trade_routes_volume(
// US3AC18
auto trade_good_loss_mult = ve::max(0.f, 1.f - trade_loss_per_distance_unit * distance);

// US3AC2. we assume that 2 uneducated persons (1 from each market) can transport 1 unit of goods along path of 1 effective day length
// US3AC2
// We assume that 2 uneducated persons (1 from each market) can transport 1 unit of goods along path of 1 effective day length
// we do it this way to avoid another assymetry in calculations

auto transport_availability_A = ve::select(is_land_route, ve::fp_vector{ state.world.province_get_labor_demand_satisfaction(capital_A, labor::no_education) }, ve::fp_vector{ 0.f });
Expand Down Expand Up @@ -718,7 +724,8 @@ void update_trade_routes_consumption(sys::state& state) {
});
});

// US3AC2 register trade demand on transportation labor:
// US3AC2
// Register trade demand on transportation labor:
// money are paid during calculation of trade route profits and actual movement of goods

auto port_services_buffer = state.world.market_make_vectorizable_float_buffer();
Expand Down Expand Up @@ -760,7 +767,8 @@ void update_trade_routes_consumption(sys::state& state) {
state.world.province_set_service_demand_forbidden_public_supply(pid, services::list::port_capacity, local_demand + market_demand * local_weight / total_weight);
});

// US3AC3 register demand on local transportation/accounting due to trade
// US3AC3
// register demand on local transportation/accounting due to trade
// all trade generates uneducated labor demand for goods transport locally
// labor demand satisfaction does not set limits on transportation: it would be way too jumpy
// we assume that 1 human could move 100 units of goods daily locally
Expand Down
2 changes: 2 additions & 0 deletions src/gamestate/system_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2631,7 +2631,9 @@ void state::load_scenario_data(parsers::error_handler& err, sys::year_month_day

for(const auto ft : world.in_factory_type) {
if(!bool(world.factory_type_get_output(ft))) {
err.fatal = true;
err.accumulated_errors += "No output defined for factory " + std::string(text::produce_simple_string(*this, world.factory_type_get_name(ft))) + " (" + err.file_name + ")\n";
assert(world.factory_type_get_output(ft));
}
}
if(!new_context.found_worker_types) {
Expand Down
5 changes: 4 additions & 1 deletion src/nations/nations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,10 @@ void generate_sea_trade_routes(sys::state& state) {
auto continent_origin = state.world.province_get_continent(state_owner_capital);

float mult = 1.f;
mult += std::min(naval_base_origin, naval_base_target) * naval_base_level_to_market_attractiveness;
// US3AC5 Naval bases increase appeal of a state for trade routes by 25%
mult += std::min(naval_base_origin, naval_base_target) * naval_base_level_to_market_attractiveness +
// US3AC22 Trade Route attraction modifier
std::min(state.world.nation_get_modifier_values(owner, sys::national_mod_offsets::trade_routes_attraction), state.world.nation_get_modifier_values(target_owner, sys::national_mod_offsets::trade_routes_attraction));
bool must_connect = same_owner && different_region && capital_and_connected_region;

auto distance_approximation = province::direct_distance(state, coast_0, coast_1) / base_speed;
Expand Down
Loading