From 2f1e57317d586153d99f4364b50a5d76acf8b49d Mon Sep 17 00:00:00 2001 From: Paul Nakonechnyy Date: Thu, 9 Jan 2025 16:40:17 +0200 Subject: [PATCH 1/6] Factory refit v0.1 --- assets/alice.gui | 58 ++++ assets/localisation/en-US/alice.csv | 4 +- docs/Devlogs/v1.2.1/README.md | 3 + src/economy/economy.cpp | 93 ++++-- src/economy/economy.hpp | 1 + src/economy/economy_templates.hpp | 9 +- src/gamestate/commands.cpp | 48 +++- src/gamestate/commands.hpp | 5 +- src/gamestate/dcon_generated.txt | 5 + src/gui/gui_outliner_window.hpp | 3 +- .../gui_production_window.hpp | 33 ++- .../gui_factory_refit_window.hpp | 264 ++++++++++++++++++ .../gui_projects_window.hpp | 3 +- src/parsing/defines.hpp | 2 +- 14 files changed, 493 insertions(+), 38 deletions(-) create mode 100644 src/gui/topbar_subwindows/production_subwindows/gui_factory_refit_window.hpp diff --git a/assets/alice.gui b/assets/alice.gui index 1d026c648..21e936748 100644 --- a/assets/alice.gui +++ b/assets/alice.gui @@ -5613,4 +5613,62 @@ guiTypes = { clicksound = click } } + + windowType = { + name = "factory_refit_window" + position = { x=320 y=-80 } + size = { x=260 y=355 } + horizontalBorder = "0" + orientation="UPPER_LEFT" + + iconType = + { + name ="factory_refit_window_bg" + spriteType = "GFX_attach_bg" + position = { x= 0 y = 0 } + Orientation = "UPPER_LEFT" + } + + listboxType = { + name ="factory_refit_type_list" + position = { x = 10 y = 10 } + backGround="" + size = { x=260 y =340 } + Orientation = "UPPER_LEFT" + spacing = 1 + scrollbartype = "standardlistbox_slider" + borderSize = {x = 0 y = 0} + } + } + + windowType = { + name = "factory_refit_type_item" + position = { x=0 y=0 } + size = { x=220 y=40 } + horizontalBorder = "0" + orientation="UPPER_LEFT" + + iconType = { + name ="factory_type_icon" + spriteType = "GFX_resources" + position = { x= 0 y = 0 } + } + + guiButtonType = { + name = "factory_type_select" + position = { x = 40 y = 0 } + size = { x=180 y=40 } + quadTextureSprite ="GFX_diplo_wargoal_button" + buttonFont = "vic_18_black" + buttonText = "Test" + clicksound = click + } + } + + guiButtonType = { + name = "factory_refit_button" + position = { x = 25 y = 15 } + quadTextureSprite ="GFX_unit_upgrade_button" + extends="factory_info" + } } diff --git a/assets/localisation/en-US/alice.csv b/assets/localisation/en-US/alice.csv index ddc2fbc87..aac7c28c1 100644 --- a/assets/localisation/en-US/alice.csv +++ b/assets/localisation/en-US/alice.csv @@ -1087,6 +1087,8 @@ CANCEL_WARSUBSIDIES_WE_ACCEPT_LOG;$ACTOR$ stopped giving subsidies to us; CANCEL_WARSUBSIDIES_OTHER_ACCEPT_LOG;$ACTOR$ stopped giving subsidies to $RECIPIENT$; CANCEL_WARSUBSIDIES_DESC;Cancel monetary help to this country; CANCEL_WARSUBSIDIES_BUTTON;Cancel Subsidies; +production_refit_factory_tooltip;Refit factory into different line of production +factory_refit_condition_1;Old and new types match in input or output goods alice_budget_scaled_16;Diplomatic interest: ?G$value$ alice_budget_scaled_17;Gold production: ?G$value$ alice_budget_scaled_net;Net revenue: ?Y$value$ @@ -1361,4 +1363,4 @@ alice_budget_debt_enable;Enable alice_budget_tariffs_import;Import Duties alice_budget_tariffs_export;Export Duties alice_budget_overseas_maintanance;Colonial -alice_budget_social_spending;Benefits \ No newline at end of file +alice_budget_social_spending;Benefits diff --git a/docs/Devlogs/v1.2.1/README.md b/docs/Devlogs/v1.2.1/README.md index b8a1c8480..2a1370928 100644 --- a/docs/Devlogs/v1.2.1/README.md +++ b/docs/Devlogs/v1.2.1/README.md @@ -4,6 +4,7 @@ Assembled by SneakBug8 ## by Schombert - Complete rework of macrobuilder window UI + - Complete rework of budget window UI ## by Peter @@ -42,6 +43,8 @@ War justification for state-level CBs (such as demand state) requires selection - Regiment/ship types must be researched and available to the owner nation. - Small ships cannot be converted into capitol (big) ships. +**Player password in MP.** To protect himself from impersonators player can set a password in launcher. Password is passed to the PA game as a cmd argument. Client sends password to the server in plain form. If player is known, salt is taken from DCON and hashes are compared to check whether to drop connection. If player is new, hash and salt are stored in DCON. If player previously didn't have a password and sets one in Launcher - it'll update password. In persistent mode, hash and salt are stored in playerlist csv file. + Other bits and pieces: - New tooltips for private investment pool under current treasury in the topbar - Expanded Web API diff --git a/src/economy/economy.cpp b/src/economy/economy.cpp index ccc1219dd..dca290787 100644 --- a/src/economy/economy.cpp +++ b/src/economy/economy.cpp @@ -3407,6 +3407,7 @@ void populate_construction_consumption(sys::state& state) { float admin_eff = state.world.nation_get_administrative_efficiency(owner); float admin_cost_factor = 2.0f - admin_eff; + float refit_discount = (c.get_refit_target()) ? state.defines.alice_factory_refit_cost_modifier : 1.0f; for(uint32_t i = 0; i < commodity_set::set_size; ++i) { auto cid = base_cost.commodity_type[i]; @@ -3417,7 +3418,7 @@ void populate_construction_consumption(sys::state& state) { auto can_purchase_budget = std::max(budget_limit, base_budget) / (price(state, market, cid) + 0.001f); auto can_purchase_construction = base_cost.commodity_amounts[i] * admin_cost_factor - * factory_mod + * factory_mod * refit_discount / construction_time; auto can_purchase = std::min(can_purchase_budget, can_purchase_construction); @@ -8395,31 +8396,76 @@ float unit_construction_progress(sys::state& state, dcon::province_naval_constru void add_factory_level_to_state(sys::state& state, dcon::state_instance_id s, dcon::factory_type_id t, bool is_upgrade) { - if(is_upgrade) { - auto d = state.world.state_instance_get_definition(s); - auto o = state.world.state_instance_get_nation_from_state_ownership(s); - for(auto p : state.world.state_definition_get_abstract_state_membership(d)) { - if(p.get_province().get_nation_from_province_ownership() == o) { - for(auto f : p.get_province().get_factory_location()) { - if(f.get_factory().get_building_type() == t) { - auto factory_level = f.get_factory().get_level(); - auto new_factory_level = std::min(float(std::numeric_limits::max()), float(factory_level) + 1.f); - f.get_factory().get_level() = uint8_t(new_factory_level); - return; - } + // Since construction may be queued together with refit, check if there is factory to add level to + auto d = state.world.state_instance_get_definition(s); + auto o = state.world.state_instance_get_nation_from_state_ownership(s); + for(auto p : state.world.state_definition_get_abstract_state_membership(d)) { + if(p.get_province().get_nation_from_province_ownership() == o) { + for(auto f : p.get_province().get_factory_location()) { + if(f.get_factory().get_building_type() == t) { + auto factory_level = f.get_factory().get_level(); + auto new_factory_level = std::min(float(std::numeric_limits::max()), float(factory_level) + 1.f); + f.get_factory().get_level() = uint8_t(new_factory_level); + return; } } } } + + // Only then create new factory auto state_cap = state.world.state_instance_get_capital(s); auto new_fac = fatten(state.world, state.world.create_factory()); new_fac.set_building_type(t); new_fac.set_level(uint8_t(1)); new_fac.set_production_scale(1.0f); - state.world.try_create_factory_location(new_fac, state_cap); } +void change_factory_type_in_state(sys::state& state, dcon::state_instance_id s, dcon::factory_type_id t, dcon::factory_type_id refit_target) { + assert(refit_target); + auto d = state.world.state_instance_get_definition(s); + auto o = state.world.state_instance_get_nation_from_state_ownership(s); + + dcon::factory_id factory_target; + + // Find factory in question + for(auto p : state.world.state_definition_get_abstract_state_membership(d)) { + if(p.get_province().get_nation_from_province_ownership() == o) { + for(auto f : p.get_province().get_factory_location()) { + if(f.get_factory().get_building_type() == t) { + factory_target = f.get_factory(); + } + } + } + } + + // Find existing factories to sum + for(auto p : state.world.state_definition_get_abstract_state_membership(d)) + if(p.get_province().get_nation_from_province_ownership() == o) + for(auto f : p.get_province().get_factory_location()) + if(f.get_factory().get_building_type() == refit_target) { + auto factory_level_1 = f.get_factory().get_level(); + auto factory_level_2 = state.world.factory_get_level(factory_target); + auto new_factory_level = std::min(float(std::numeric_limits::max()), float(factory_level_1) + float(factory_level_2)); + f.get_factory().get_level() = uint8_t(new_factory_level); + + state.world.delete_factory(factory_target); + return; + } + + // Change type of the given factory + for(auto p : state.world.state_definition_get_abstract_state_membership(d)) { + if(p.get_province().get_nation_from_province_ownership() == o) { + for(auto f : p.get_province().get_factory_location()) { + if(f.get_factory().get_building_type() == t) { + f.get_factory().set_building_type(refit_target); + return; + } + } + } + } +} + void resolve_constructions(sys::state& state) { for(auto c : state.world.in_province_land_construction) { @@ -8596,12 +8642,13 @@ void resolve_constructions(sys::state& state) { float admin_cost_factor = 2.0f - admin_eff; float factory_mod = state.world.nation_get_modifier_values(n, sys::national_mod_offsets::factory_cost) + 1.0f; + float refit_discount = (c.get_refit_target()) ? state.defines.alice_factory_refit_cost_modifier : 1.0f; bool all_finished = true; if(!(n == state.local_player_nation && state.cheat_data.instant_industry)) { for(uint32_t j = 0; j < commodity_set::set_size && all_finished; ++j) { if(base_cost.commodity_type[j]) { - if(current_purchased.commodity_amounts[j] < base_cost.commodity_amounts[j] * factory_mod * admin_cost_factor) { + if(current_purchased.commodity_amounts[j] < base_cost.commodity_amounts[j] * factory_mod * admin_cost_factor * refit_discount) { all_finished = false; } } else { @@ -8609,7 +8656,12 @@ void resolve_constructions(sys::state& state) { } } } - if(all_finished) { + if(all_finished && c.get_refit_target()) { + change_factory_type_in_state(state, state.world.state_building_construction_get_state(c), type, + c.get_refit_target()); + state.world.delete_state_building_construction(c); + } + else if (all_finished) { add_factory_level_to_state(state, state.world.state_building_construction_get_state(c), type, state.world.state_building_construction_get_is_upgrade(c)); state.world.delete_state_building_construction(c); @@ -8617,12 +8669,13 @@ void resolve_constructions(sys::state& state) { } else { float factory_mod = (state.world.nation_get_modifier_values(n, sys::national_mod_offsets::factory_cost) + 1.0f) * std::max(0.1f, state.world.nation_get_modifier_values(n, sys::national_mod_offsets::factory_owner_cost)); + float refit_discount = (c.get_refit_target()) ? state.defines.alice_factory_refit_cost_modifier : 1.0f; bool all_finished = true; if(!(n == state.local_player_nation && state.cheat_data.instant_industry)) { for(uint32_t j = 0; j < commodity_set::set_size && all_finished; ++j) { if(base_cost.commodity_type[j]) { - if(current_purchased.commodity_amounts[j] < base_cost.commodity_amounts[j] * factory_mod) { + if(current_purchased.commodity_amounts[j] < base_cost.commodity_amounts[j] * factory_mod * refit_discount) { all_finished = false; } } else { @@ -8630,7 +8683,11 @@ void resolve_constructions(sys::state& state) { } } } - if(all_finished) { + if(all_finished && c.get_refit_target()) { + change_factory_type_in_state(state, state.world.state_building_construction_get_state(c), type, + c.get_refit_target()); + state.world.delete_state_building_construction(c); + } else if(all_finished) { add_factory_level_to_state(state, state.world.state_building_construction_get_state(c), type, state.world.state_building_construction_get_is_upgrade(c)); diff --git a/src/economy/economy.hpp b/src/economy/economy.hpp index 4b66dccde..5124206ff 100644 --- a/src/economy/economy.hpp +++ b/src/economy/economy.hpp @@ -543,6 +543,7 @@ struct new_factory { struct upgraded_factory { float progress = 0.0f; dcon::factory_type_id type; + dcon::factory_type_id target_type; }; bool state_contains_constructed_factory(sys::state& state, dcon::state_instance_id si, dcon::factory_type_id ft); diff --git a/src/economy/economy_templates.hpp b/src/economy/economy_templates.hpp index 3d4c435e2..081efe522 100644 --- a/src/economy/economy_templates.hpp +++ b/src/economy/economy_templates.hpp @@ -6,7 +6,7 @@ namespace economy { template void for_each_new_factory(sys::state& state, dcon::state_instance_id s, F&& func) { for(auto st_con : state.world.state_instance_get_state_building_construction(s)) { - if(!st_con.get_is_upgrade()) { + if(!st_con.get_is_upgrade() && !st_con.get_refit_target()) { float admin_eff = state.world.nation_get_administrative_efficiency(st_con.get_nation()); float factory_mod = state.world.nation_get_modifier_values(st_con.get_nation(), sys::national_mod_offsets::factory_cost) + 1.0f; float pop_factory_mod = std::max(0.1f, state.world.nation_get_modifier_values(st_con.get_nation(), sys::national_mod_offsets::factory_owner_cost)); @@ -29,22 +29,23 @@ void for_each_new_factory(sys::state& state, dcon::state_instance_id s, F&& func template void for_each_upgraded_factory(sys::state& state, dcon::state_instance_id s, F&& func) { for(auto st_con : state.world.state_instance_get_state_building_construction(s)) { - if(st_con.get_is_upgrade()) { + if(st_con.get_is_upgrade() || st_con.get_refit_target()) { float admin_eff = state.world.nation_get_administrative_efficiency(st_con.get_nation()); float factory_mod = state.world.nation_get_modifier_values(st_con.get_nation(), sys::national_mod_offsets::factory_cost) + 1.0f; float pop_factory_mod = std::max(0.1f, state.world.nation_get_modifier_values(st_con.get_nation(), sys::national_mod_offsets::factory_owner_cost)); float admin_cost_factor = (st_con.get_is_pop_project() ? pop_factory_mod : (2.0f - admin_eff)) * factory_mod; + float refit_discount = (st_con.get_refit_target()) ? state.defines.alice_factory_refit_cost_modifier : 1.0f; float total = 0.0f; float purchased = 0.0f; auto& goods = state.world.factory_type_get_construction_costs(st_con.get_type()); for(uint32_t i = 0; i < commodity_set::set_size; ++i) { - total += goods.commodity_amounts[i] * admin_cost_factor; + total += goods.commodity_amounts[i] * admin_cost_factor * refit_discount; purchased += st_con.get_purchased_goods().commodity_amounts[i]; } - func(upgraded_factory{total > 0.0f ? purchased / total : 0.0f, st_con.get_type().id}); + func(upgraded_factory{total > 0.0f ? purchased / total : 0.0f, st_con.get_type().id, st_con.get_refit_target().id}); } } } diff --git a/src/gamestate/commands.cpp b/src/gamestate/commands.cpp index a8256fd17..bf0d7ef07 100644 --- a/src/gamestate/commands.cpp +++ b/src/gamestate/commands.cpp @@ -511,7 +511,7 @@ void execute_cancel_factory_building_construction(sys::state& state, dcon::natio } } } -void begin_factory_building_construction(sys::state& state, dcon::nation_id source, dcon::state_instance_id location, dcon::factory_type_id type, bool is_upgrade) { +void begin_factory_building_construction(sys::state& state, dcon::nation_id source, dcon::state_instance_id location, dcon::factory_type_id type, bool is_upgrade, dcon::factory_type_id refit_target) { payload p; memset(&p, 0, sizeof(payload)); p.type = command_type::begin_factory_building_construction; @@ -519,10 +519,11 @@ void begin_factory_building_construction(sys::state& state, dcon::nation_id sour p.data.start_factory_building.location = location; p.data.start_factory_building.type = type; p.data.start_factory_building.is_upgrade = is_upgrade; + p.data.start_factory_building.refit_target = refit_target; add_to_command_queue(state, p); } -bool can_begin_factory_building_construction(sys::state& state, dcon::nation_id source, dcon::state_instance_id location, dcon::factory_type_id type, bool is_upgrade) { +bool can_begin_factory_building_construction(sys::state& state, dcon::nation_id source, dcon::state_instance_id location, dcon::factory_type_id type, bool is_upgrade, dcon::factory_type_id refit_target) { auto owner = state.world.state_instance_get_nation_from_state_ownership(location); @@ -537,7 +538,7 @@ bool can_begin_factory_building_construction(sys::state& state, dcon::nation_id return false; /* There can't be duplicate factories... */ - if(!is_upgrade) { + if(!is_upgrade && !refit_target) { // Check factories being built bool has_dup = false; economy::for_each_new_factory(state, location, [&](economy::new_factory const& nf) { has_dup = has_dup || nf.type == type; }); @@ -553,6 +554,40 @@ bool can_begin_factory_building_construction(sys::state& state, dcon::nation_id return false; } + // For refit factories must match in output good or inputs. + if(refit_target) { + if(type == refit_target) { + return false; + } + + // Check if this factory is already being refit + bool has_dup = false; + economy::for_each_upgraded_factory(state, location, [&](economy::upgraded_factory const& nf) { has_dup = has_dup || nf.type == type; }); + if(has_dup) + return false; + + // We deliberately allow for duplicates to existing factories as this scenario is handled when construction is finished + + auto output_1 = state.world.factory_type_get_output(type); + auto output_2 = state.world.factory_type_get_output(refit_target); + auto inputs_1 = state.world.factory_type_get_inputs(type); + auto inputs_2 = state.world.factory_type_get_inputs(refit_target); + auto inputs_match = true; + + for(uint32_t i = 0; i < economy::commodity_set::set_size; ++i) { + auto input_1 = inputs_1.commodity_type[i]; + auto input_2 = inputs_2.commodity_type[i]; + + if(input_1 != input_2) { + inputs_match = false; + break; + } + } + if(output_1 != output_2 && !inputs_match) { + return false; + } + } + if(state.world.nation_get_is_civilized(source) == false) return false; @@ -625,11 +660,12 @@ bool can_begin_factory_building_construction(sys::state& state, dcon::nation_id } } -void execute_begin_factory_building_construction(sys::state& state, dcon::nation_id source, dcon::state_instance_id location, dcon::factory_type_id type, bool is_upgrade) { +void execute_begin_factory_building_construction(sys::state& state, dcon::nation_id source, dcon::state_instance_id location, dcon::factory_type_id type, bool is_upgrade, dcon::factory_type_id refit_target) { auto new_up = fatten(state.world, state.world.force_create_state_building_construction(location, source)); new_up.set_is_pop_project(false); new_up.set_is_upgrade(is_upgrade); new_up.set_type(type); + new_up.set_refit_target(refit_target); if(source != state.world.state_instance_get_nation_from_state_ownership(location)) { float amount = 0.0f; @@ -5339,7 +5375,7 @@ bool can_perform_command(sys::state& state, payload& c) { case command_type::begin_factory_building_construction: return can_begin_factory_building_construction(state, c.source, c.data.start_factory_building.location, - c.data.start_factory_building.type, c.data.start_factory_building.is_upgrade); + c.data.start_factory_building.type, c.data.start_factory_building.is_upgrade, c.data.start_factory_building.refit_target); case command_type::begin_naval_unit_construction: return can_start_naval_unit_construction(state, c.source, c.data.naval_unit_construction.location, @@ -5713,7 +5749,7 @@ void execute_command(sys::state& state, payload& c) { break; case command_type::begin_factory_building_construction: execute_begin_factory_building_construction(state, c.source, c.data.start_factory_building.location, - c.data.start_factory_building.type, c.data.start_factory_building.is_upgrade); + c.data.start_factory_building.type, c.data.start_factory_building.is_upgrade, c.data.start_factory_building.refit_target); break; case command_type::begin_naval_unit_construction: execute_start_naval_unit_construction(state, c.source, c.data.naval_unit_construction.location, diff --git a/src/gamestate/commands.hpp b/src/gamestate/commands.hpp index bc3443929..688bf479c 100644 --- a/src/gamestate/commands.hpp +++ b/src/gamestate/commands.hpp @@ -167,6 +167,7 @@ struct factory_building_data { dcon::state_instance_id location; dcon::factory_type_id type; bool is_upgrade; + dcon::factory_type_id refit_target; }; struct diplo_action_data { @@ -575,8 +576,8 @@ bool can_decrease_relations(sys::state& state, dcon::nation_id source, dcon::nat void begin_province_building_construction(sys::state& state, dcon::nation_id source, dcon::province_id p, economy::province_building_type type); bool can_begin_province_building_construction(sys::state& state, dcon::nation_id source, dcon::province_id p, economy::province_building_type type); -void begin_factory_building_construction(sys::state& state, dcon::nation_id source, dcon::state_instance_id location, dcon::factory_type_id type, bool is_upgrade); -bool can_begin_factory_building_construction(sys::state& state, dcon::nation_id source, dcon::state_instance_id location, dcon::factory_type_id type, bool is_upgrade); +void begin_factory_building_construction(sys::state& state, dcon::nation_id source, dcon::state_instance_id location, dcon::factory_type_id type, bool is_upgrade, dcon::factory_type_id refit_target = dcon::factory_type_id{}); +bool can_begin_factory_building_construction(sys::state& state, dcon::nation_id source, dcon::state_instance_id location, dcon::factory_type_id type, bool is_upgrade, dcon::factory_type_id refit_target = dcon::factory_type_id{}); void cancel_factory_building_construction(sys::state& state, dcon::nation_id source, dcon::state_instance_id location, dcon::factory_type_id type); bool can_cancel_factory_building_construction(sys::state& state, dcon::nation_id source, dcon::state_instance_id location, dcon::factory_type_id type); diff --git a/src/gamestate/dcon_generated.txt b/src/gamestate/dcon_generated.txt index 6941157c9..725b205f8 100644 --- a/src/gamestate/dcon_generated.txt +++ b/src/gamestate/dcon_generated.txt @@ -4704,6 +4704,11 @@ relationship{ type{ bitfield } tag{ save } } + property{ + name{ refit_target } + type{ factory_type_id } + tag{ save } + } } relationship{ diff --git a/src/gui/gui_outliner_window.hpp b/src/gui/gui_outliner_window.hpp index f2010ce73..666c87126 100644 --- a/src/gui/gui_outliner_window.hpp +++ b/src/gui/gui_outliner_window.hpp @@ -431,9 +431,10 @@ class outliner_entry_text : public color_text_element { float pop_factory_mod = std::max(0.1f, state.world.nation_get_modifier_values(st_con.get_nation(), sys::national_mod_offsets::factory_owner_cost)); auto admin_eff = st_con.get_nation().get_administrative_efficiency(); float admin_cost_factor = (st_con.get_is_pop_project() ? pop_factory_mod : (2.0f - admin_eff)) * factory_mod; + float refit_discount = (st_con.get_refit_target()) ? state.defines.alice_factory_refit_cost_modifier : 1.0f; for(uint32_t i = 0; i < economy::commodity_set::set_size; ++i) { - total += goods.commodity_amounts[i] * admin_cost_factor; + total += goods.commodity_amounts[i] * admin_cost_factor * refit_discount; purchased += st_con.get_purchased_goods().commodity_amounts[i]; } auto progress = total > 0.0f ? purchased / total : 0.0f; diff --git a/src/gui/topbar_subwindows/gui_production_window.hpp b/src/gui/topbar_subwindows/gui_production_window.hpp index d05de7625..36f7e42dc 100644 --- a/src/gui/topbar_subwindows/gui_production_window.hpp +++ b/src/gui/topbar_subwindows/gui_production_window.hpp @@ -12,6 +12,7 @@ #include "economy_templates.hpp" #include "province_templates.hpp" #include "widgets/table.hpp" +#include "gui_factory_refit_window.hpp" namespace ui { @@ -569,6 +570,7 @@ class factory_upgrade_progress_bar : public progress_bar { float factory_mod = state.world.nation_get_modifier_values(p.get_nation(), sys::national_mod_offsets::factory_cost) + 1.0f; float pop_factory_mod = std::max(0.1f, state.world.nation_get_modifier_values(p.get_nation(), sys::national_mod_offsets::factory_owner_cost)); float admin_cost_factor = (p.get_is_pop_project() ? pop_factory_mod : (2.0f - admin_eff)) * factory_mod; + float refit_discount = (p.get_refit_target()) ? state.defines.alice_factory_refit_cost_modifier : 1.0f; auto& goods = state.world.factory_type_get_construction_costs(nf.type); auto& cgoods = p.get_purchased_goods(); @@ -584,7 +586,7 @@ class factory_upgrade_progress_bar : public progress_bar { text::add_to_layout_box(state, contents, box, std::string_view{ ": " }); text::add_to_layout_box(state, contents, box, text::fp_one_place{ cgoods.commodity_amounts[i] }); text::add_to_layout_box(state, contents, box, std::string_view{ " / " }); - text::add_to_layout_box(state, contents, box, text::fp_one_place{ goods.commodity_amounts[i] * admin_cost_factor }); + text::add_to_layout_box(state, contents, box, text::fp_one_place{ goods.commodity_amounts[i] * admin_cost_factor * refit_discount }); text::close_layout_box(contents, box); } } @@ -1075,6 +1077,10 @@ class production_factory_info : public window_element_base { auto ptr = make_element_by_type(state, id); factory_elements.push_back(ptr.get()); return ptr; + } else if(name == "factory_refit_button") { + auto ptr = make_element_by_type(state, id); + factory_elements.push_back(ptr.get()); + return ptr; } else if(name == "subsidise") { auto ptr = make_element_by_type(state, id); factory_elements.push_back(ptr.get()); @@ -1118,6 +1124,7 @@ class production_factory_info : public window_element_base { // New factory economy::new_factory nf = std::get(content.activity); fat_btid = dcon::fatten(state.world, nf.type); + output_commodity = fat_btid.get_output().id; for(auto const& e : factory_elements) e->set_visible(state, false); @@ -1128,9 +1135,10 @@ class production_factory_info : public window_element_base { for(auto const& e : closed_elements) e->set_visible(state, false); } else if(std::holds_alternative(content.activity)) { - // Upgrade + // Upgrade or refit economy::upgraded_factory uf = std::get(content.activity); fat_btid = dcon::fatten(state.world, uf.type); + output_commodity = (uf.target_type) ? state.world.factory_type_get_output(uf.target_type).id : fat_btid.get_output().id; for(auto const& e : factory_elements) e->set_visible(state, true); @@ -1144,6 +1152,7 @@ class production_factory_info : public window_element_base { // "Normal" factory, not being upgraded or built dcon::factory_id fid = content.id; fat_btid = state.world.factory_get_building_type(fid); + output_commodity = fat_btid.get_output().id; bool is_closed = dcon::fatten(state.world, fid).get_production_scale() < economy::factory_closed_threshold; for(auto const& e : factory_elements) @@ -1169,7 +1178,6 @@ class production_factory_info : public window_element_base { input_lack_icons[size_t(i)]->set_visible(state, is_lack); } } - output_commodity = fat_btid.get_output().id; } } @@ -1253,7 +1261,7 @@ class production_factory_info_bounds_window : public window_element_base { ++index; } }); - // Then, the factories being upgraded + // Then, the factories being upgraded or refit economy::for_each_upgraded_factory(state, state_id, [&](economy::upgraded_factory const& uf) { dcon::commodity_id cid = state.world.factory_type_get_output(uf.type).id; if(!visited_types[uf.type.index()] && get_filter(state, cid) && index < state.defines.factories_per_state) { @@ -1271,6 +1279,7 @@ class production_factory_info_bounds_window : public window_element_base { ++index; } }); + // Finally, factories "doing nothing" are accounted for province::for_each_province_in_state_instance(state, state_id, [&](dcon::province_id pid) { dcon::fatten(state.world, pid).for_each_factory_location_as_province([&](dcon::factory_location_id flid) { @@ -1961,6 +1970,9 @@ class production_window : public generic_tabbed_window { element_base* project_window = nullptr; production_foreign_investment_window* foreign_invest_win = nullptr; + dcon::factory_id selected_factory; + factory_refit_window* factory_refit_win = nullptr; + sys::commodity_group curr_commodity_group{}; dcon::state_instance_id focus_state{}; dcon::nation_id foreign_nation{}; @@ -2012,6 +2024,11 @@ class production_window : public generic_tabbed_window { project_window = win2.get(); add_child_to_front(std::move(win2)); + auto win3 = make_element_by_type(state, state.ui_state.defs_by_name.find(state.lookup_key("factory_refit_window"))->second.definition); + factory_refit_win = win3.get(); + factory_refit_win->set_visible(state, false); + add_child_to_front(std::move(win3)); + show_output_commodity = std::unique_ptr(new bool[state.world.commodity_size()]); set_visible(state, false); } @@ -2159,6 +2176,14 @@ class production_window : public generic_tabbed_window { } else if(payload.holds_type()) { payload.emplace(production_foreign_invest_target{foreign_invest_win->curr_nation}); return message_result::consumed; + } else if(payload.holds_type()) { + auto val = any_cast(payload); + selected_factory = val.factory_selection; + factory_refit_win->set_visible(state, factory_refit_win->is_visible() ? false : true); + return message_result::consumed; + } else if(payload.holds_type()) { + payload.emplace(selected_factory); + return message_result::consumed; } else if(payload.holds_type()) { hide_sub_windows(state); auto target = any_cast(payload).id; diff --git a/src/gui/topbar_subwindows/production_subwindows/gui_factory_refit_window.hpp b/src/gui/topbar_subwindows/production_subwindows/gui_factory_refit_window.hpp new file mode 100644 index 000000000..9fa9c995c --- /dev/null +++ b/src/gui/topbar_subwindows/production_subwindows/gui_factory_refit_window.hpp @@ -0,0 +1,264 @@ +#include "gui_element_types.hpp" + +namespace ui { + +struct open_factory_refit { + dcon::factory_id factory_selection; +}; + +class factory_refit_button : public button_element_base { +public: + void on_update(sys::state& state) noexcept override { + const dcon::factory_id fid = retrieve(state, parent); + auto fat = dcon::fatten(state.world, fid); + const dcon::state_instance_id sid = retrieve(state, parent); + const dcon::nation_id n = retrieve(state, parent); + auto type = retrieve(state, parent); + + // no double upgrade + bool is_not_upgrading = true; + for(auto p : state.world.state_instance_get_state_building_construction(sid)) { + if(p.get_type() == type) + is_not_upgrading = false; + } + if(!is_not_upgrading) { + disabled = true; return; + } + + bool is_civ = state.world.nation_get_is_civilized(state.local_player_nation); + if(!is_civ) { + disabled = true; return; + } + + bool state_is_not_colonial = !state.world.province_get_is_colonial(state.world.state_instance_get_capital(sid)); + if(!state_is_not_colonial) { + disabled = true; return; + } + + auto rules = state.world.nation_get_combined_issue_rules(state.local_player_nation); + if((rules & issue_rule::expand_factory) == 0) { + disabled = true; return; + } + + disabled = false; + } + void render(sys::state& state, int32_t x, int32_t y) noexcept override { + auto fid = retrieve(state, parent); + auto sid = retrieve(state, parent); + auto type = state.world.factory_get_building_type(fid); + + // no double upgrade + bool is_not_upgrading = true; + for(auto p : state.world.state_instance_get_state_building_construction(sid)) { + if(p.get_type() == type) + is_not_upgrading = false; + } + if(is_not_upgrading) { + button_element_base::render(state, x, y); + } + } + void button_action(sys::state& state) noexcept override { + const dcon::factory_id fid = retrieve(state, parent); + + send(state, parent, open_factory_refit{fid}); + } + + tooltip_behavior has_tooltip(sys::state& state) noexcept override { + return tooltip_behavior::variable_tooltip; + } + + + void update_tooltip(sys::state& state, int32_t x, int32_t y, text::columnar_layout& contents) noexcept override { + const dcon::factory_id fid = retrieve(state, parent); + auto fat = dcon::fatten(state.world, fid); + const dcon::state_instance_id sid = retrieve(state, parent); + const dcon::nation_id n = retrieve(state, parent); + auto type = retrieve(state, parent); + + // no double upgrade + bool is_not_upgrading = true; + for(auto p : state.world.state_instance_get_state_building_construction(sid)) { + if(p.get_type() == type) + is_not_upgrading = false; + } + + text::add_line(state, contents, "production_refit_factory_tooltip"); + + text::add_line_break_to_layout(state, contents); + + bool is_civ = state.world.nation_get_is_civilized(state.local_player_nation); + text::add_line_with_condition(state, contents, "factory_upgrade_condition_1", is_civ); + + bool state_is_not_colonial = !state.world.province_get_is_colonial(state.world.state_instance_get_capital(sid)); + text::add_line_with_condition(state, contents, "factory_upgrade_condition_2", state_is_not_colonial); + + bool is_activated = state.world.nation_get_active_building(n, type) == true || state.world.factory_type_get_is_available_from_start(type); + text::add_line_with_condition(state, contents, "factory_upgrade_condition_3", is_activated); + + auto rules = state.world.nation_get_combined_issue_rules(state.local_player_nation); + text::add_line_with_condition(state, contents, "factory_upgrade_condition_8", (rules & issue_rule::expand_factory) != 0); + + text::add_line_with_condition(state, contents, "factory_upgrade_condition_9", is_not_upgrading); + text::add_line_with_condition(state, contents, "factory_upgrade_condition_10", fat.get_level() < 255); + } +}; + + +class factory_refit_type_listbox_entry_label : public button_element_base { +public: + void on_update(sys::state& state) noexcept override { + auto refit_target_id = retrieve(state, parent); + auto fid = retrieve(state, parent); + auto fat = dcon::fatten(state.world, fid); + auto sid = fat.get_factory_location().get_province().get_state_membership(); + + if(refit_target_id) { + set_button_text(state, text::produce_simple_string(state, state.world.factory_type_get_name(refit_target_id))); + } + + disabled = !command::can_begin_factory_building_construction(state, state.local_player_nation, sid, + fat.get_building_type().id, false, refit_target_id); + + } + + void button_action(sys::state& state) noexcept override { + auto refit_target_id = retrieve(state, parent); + auto fid = retrieve(state, parent); + auto fat = dcon::fatten(state.world, fid); + auto sid = fat.get_factory_location().get_province().get_state_membership(); + command::begin_factory_building_construction(state, state.local_player_nation, sid, fat.get_building_type().id, false, refit_target_id); + // Close the window and reset selected factory + send(state, parent, open_factory_refit{ dcon::factory_id{} }); + } + + tooltip_behavior has_tooltip(sys::state& state) noexcept override { + return tooltip_behavior::variable_tooltip; + } + + void update_tooltip(sys::state& state, int32_t x, int32_t y, text::columnar_layout& contents) noexcept override { + auto refit_target = retrieve(state, parent); + auto fid = retrieve(state, parent); + auto fat = dcon::fatten(state.world, fid); + auto sid = fat.get_factory_location().get_province().get_state_membership(); + auto n = fat.get_factory_location().get_province().get_nation_from_province_ownership(); + auto type = fat.get_building_type(); + + // no double upgrade + bool is_not_upgrading = true; + for(auto p : state.world.state_instance_get_state_building_construction(sid)) { + if(p.get_type() == type) + is_not_upgrading = false; + } + + text::add_line(state, contents, "production_refit_factory_tooltip"); + + text::add_line_break_to_layout(state, contents); + + bool is_civ = state.world.nation_get_is_civilized(state.local_player_nation); + text::add_line_with_condition(state, contents, "factory_upgrade_condition_1", is_civ); + + bool state_is_not_colonial = !state.world.province_get_is_colonial(state.world.state_instance_get_capital(sid)); + text::add_line_with_condition(state, contents, "factory_upgrade_condition_2", state_is_not_colonial); + + bool is_activated = state.world.nation_get_active_building(n, refit_target) == true || state.world.factory_type_get_is_available_from_start(refit_target); + text::add_line_with_condition(state, contents, "factory_upgrade_condition_3", is_activated); + + // For refit factories must match in output good or inputs. + bool type_compatibility = true; + if(refit_target) { + if(type == refit_target) { + type_compatibility = false; + } + + auto output_1 = state.world.factory_type_get_output(type); + auto output_2 = state.world.factory_type_get_output(refit_target); + auto inputs_1 = state.world.factory_type_get_inputs(type); + auto inputs_2 = state.world.factory_type_get_inputs(refit_target); + auto inputs_match = true; + + for(uint32_t i = 0; i < economy::commodity_set::set_size; ++i) { + auto input_1 = inputs_1.commodity_type[i]; + auto input_2 = inputs_2.commodity_type[i]; + + if(input_1 != input_2) { + inputs_match = false; + break; + } + } + if(output_1 != output_2 && !inputs_match) { + type_compatibility = false; + } + } + text::add_line_with_condition(state, contents, "factory_refit_condition_1", type_compatibility); + + + auto rules = state.world.nation_get_combined_issue_rules(state.local_player_nation); + text::add_line_with_condition(state, contents, "factory_upgrade_condition_8", (rules & issue_rule::expand_factory) != 0); + + text::add_line_with_condition(state, contents, "factory_upgrade_condition_9", is_not_upgrading); + text::add_line_with_condition(state, contents, "factory_upgrade_condition_10", fat.get_level() < 255); + } +}; + +class factory_refit_type_listbox_entry : public listbox_row_element_base { + std::unique_ptr make_child(sys::state& state, std::string_view name, dcon::gui_def_id id) noexcept override { + if(name == "factory_type_icon") { + return make_element_by_type(state, id); + } else if(name == "factory_type_select") { + return make_element_by_type(state, id); + } else { + return nullptr; + } + } + + message_result get(sys::state& state, Cyto::Any& payload) noexcept override { + if(payload.holds_type()) { + auto output = state.world.factory_type_get_output(content); + payload.emplace(output); + return message_result::consumed; + } + + return listbox_row_element_base::get(state, payload); + } +}; + +class factory_refit_type_listbox : public listbox_element_base { +protected: + std::string_view get_row_element_name() override { + return "factory_refit_type_item"; + } + +public: + void on_update(sys::state& state) noexcept override { + row_contents.clear(); + + const dcon::factory_id fid = retrieve(state, parent); + auto type = state.world.factory_get_building_type(fid); + + auto is_land = true; + + for(auto ftid : state.world.in_factory_type) { + if(type == ftid) { + continue; + } + row_contents.push_back(ftid); + } + + update(state); + } + +}; + +class factory_refit_window : public window_element_base { +public: + std::unique_ptr make_child(sys::state& state, std::string_view name, dcon::gui_def_id id) noexcept override { + if(name == "factory_refit_type_list") { + return make_element_by_type(state, id); + } else { + return nullptr; + } + } +}; + + +} diff --git a/src/gui/topbar_subwindows/production_subwindows/gui_projects_window.hpp b/src/gui/topbar_subwindows/production_subwindows/gui_projects_window.hpp index 5754554b9..f065093cc 100644 --- a/src/gui/topbar_subwindows/production_subwindows/gui_projects_window.hpp +++ b/src/gui/topbar_subwindows/production_subwindows/gui_projects_window.hpp @@ -169,9 +169,10 @@ class production_project_info : public listbox_row_element_base Date: Thu, 9 Jan 2025 18:11:37 +0200 Subject: [PATCH 2/6] Factory refit v2 --- assets/localisation/en-US/alice.csv | 5 +- src/economy/economy.cpp | 89 +++++++++++++++++-- src/economy/economy.hpp | 2 + src/gamestate/commands.cpp | 12 ++- .../gui_factory_refit_window.hpp | 50 +++++------ 5 files changed, 117 insertions(+), 41 deletions(-) diff --git a/assets/localisation/en-US/alice.csv b/assets/localisation/en-US/alice.csv index aac7c28c1..9ac415119 100644 --- a/assets/localisation/en-US/alice.csv +++ b/assets/localisation/en-US/alice.csv @@ -1087,8 +1087,9 @@ CANCEL_WARSUBSIDIES_WE_ACCEPT_LOG;$ACTOR$ stopped giving subsidies to us; CANCEL_WARSUBSIDIES_OTHER_ACCEPT_LOG;$ACTOR$ stopped giving subsidies to $RECIPIENT$; CANCEL_WARSUBSIDIES_DESC;Cancel monetary help to this country; CANCEL_WARSUBSIDIES_BUTTON;Cancel Subsidies; -production_refit_factory_tooltip;Refit factory into different line of production -factory_refit_condition_1;Old and new types match in input or output goods +production_refit_factory_tooltip;Refit ?Y$what$?! into ?Y$name$?! +factory_refit_cost;$what$: ?Y$val$?! (?Y$value$?!) +factory_refit_cost_total;Total: ?Y$value$?! alice_budget_scaled_16;Diplomatic interest: ?G$value$ alice_budget_scaled_17;Gold production: ?G$value$ alice_budget_scaled_net;Net revenue: ?Y$value$ diff --git a/src/economy/economy.cpp b/src/economy/economy.cpp index dca290787..fd9871e74 100644 --- a/src/economy/economy.cpp +++ b/src/economy/economy.cpp @@ -3190,6 +3190,82 @@ float global_factory_construction_time_modifier(sys::state& state) { return 0.1f; } +economy::commodity_set calculate_factory_refit_goods_cost(sys::state& state, dcon::nation_id n, dcon::state_instance_id sid, dcon::factory_type_id from, dcon::factory_type_id to) { + auto& from_cost = state.world.factory_type_get_construction_costs(from); + auto& to_cost = state.world.factory_type_get_construction_costs(to); + + auto level = 1; + + auto d = state.world.state_instance_get_definition(sid); + auto o = state.world.state_instance_get_nation_from_state_ownership(sid); + for(auto p : state.world.state_definition_get_abstract_state_membership(d)) { + if(p.get_province().get_nation_from_province_ownership() == o) { + for(auto f : p.get_province().get_factory_location()) { + if(f.get_factory().get_building_type() == from) { + level = f.get_factory().get_level(); + } + } + } + } + + // Refit cost = (to_cost) - (from_cost) + (0.1f * to_cost) + float refit_mod = 1.0f + state.defines.alice_factory_refit_cost_modifier; + + economy::commodity_set res; + + // First take 110% of to_cost as a baseline + if(!(n == state.local_player_nation && state.cheat_data.instant_industry)) { + for(uint32_t j = 0; j < commodity_set::set_size; ++j) { + if(to_cost.commodity_type[j]) { + res.commodity_type[j] = to_cost.commodity_type[j]; + res.commodity_amounts[j] = to_cost.commodity_amounts[j] * refit_mod * level; + } else { + break; + } + } + } + + // Substract from_cost to represent refit discount + if(!(n == state.local_player_nation && state.cheat_data.instant_industry)) { + for(uint32_t i = 0; i < commodity_set::set_size; ++i) { + if(!from_cost.commodity_type[i]) { + break; + } + + auto from_amount = from_cost.commodity_amounts[i] * level; + auto from_commodity = from_cost.commodity_type[i]; + + for(uint32_t j = 0; i < commodity_set::set_size; ++j) { + if(!res.commodity_type[j]) { + break; + } + + if(res.commodity_type[j] == from_commodity) { + res.commodity_amounts[j] = std::max(res.commodity_amounts[j] - from_amount, 0.f); + } + } + } + } + + return res; +} +float calculate_factory_refit_money_cost(sys::state& state, dcon::nation_id n, dcon::state_instance_id sid, dcon::factory_type_id from, dcon::factory_type_id to) { + auto goods_cost = calculate_factory_refit_goods_cost(state, n, sid, from, to); + + float admin_eff = state.world.nation_get_administrative_efficiency(n); + float admin_cost_factor = 2.0f - admin_eff; + float factory_mod = state.world.nation_get_modifier_values(n, sys::national_mod_offsets::factory_cost) + 1.0f; + + auto total = 0.0f; + for(uint32_t i = 0; i < economy::commodity_set::set_size; i++) { + if(goods_cost.commodity_type[i]) { + total += economy::price(state, sid, goods_cost.commodity_type[i]) * goods_cost.commodity_amounts[i] * factory_mod * admin_cost_factor; + } + } + + return total; +} + void populate_construction_consumption(sys::state& state) { uint32_t total_commodities = state.world.commodity_size(); @@ -3391,7 +3467,7 @@ void populate_construction_consumption(sys::state& state) { if(owner && !c.get_is_pop_project()) { float& base_budget = current_budget.get(owner); float budget_limit = total_budget.get(owner) / float(std::max(1, going_constructions.get(owner))); - auto& base_cost = c.get_type().get_construction_costs(); + auto base_cost = (c.get_refit_target() ? calculate_factory_refit_goods_cost(state, owner, c.get_state(), c.get_type(), c.get_refit_target()) : c.get_type().get_construction_costs()); auto& current_purchased = c.get_purchased_goods(); float construction_time = @@ -3407,7 +3483,6 @@ void populate_construction_consumption(sys::state& state) { float admin_eff = state.world.nation_get_administrative_efficiency(owner); float admin_cost_factor = 2.0f - admin_eff; - float refit_discount = (c.get_refit_target()) ? state.defines.alice_factory_refit_cost_modifier : 1.0f; for(uint32_t i = 0; i < commodity_set::set_size; ++i) { auto cid = base_cost.commodity_type[i]; @@ -3418,7 +3493,7 @@ void populate_construction_consumption(sys::state& state) { auto can_purchase_budget = std::max(budget_limit, base_budget) / (price(state, market, cid) + 0.001f); auto can_purchase_construction = base_cost.commodity_amounts[i] * admin_cost_factor - * factory_mod * refit_discount + * factory_mod / construction_time; auto can_purchase = std::min(can_purchase_budget, can_purchase_construction); @@ -8634,7 +8709,7 @@ void resolve_constructions(sys::state& state) { for(auto c : state.world.in_state_building_construction) { auto n = state.world.state_building_construction_get_nation(c); auto type = state.world.state_building_construction_get_type(c); - auto& base_cost = state.world.factory_type_get_construction_costs(type); + auto base_cost = (c.get_refit_target()) ? calculate_factory_refit_goods_cost(state, n, c.get_state(), c.get_type(), c.get_refit_target()) : state.world.factory_type_get_construction_costs(type); auto& current_purchased = state.world.state_building_construction_get_purchased_goods(c); if(!state.world.state_building_construction_get_is_pop_project(c)) { @@ -8642,13 +8717,12 @@ void resolve_constructions(sys::state& state) { float admin_cost_factor = 2.0f - admin_eff; float factory_mod = state.world.nation_get_modifier_values(n, sys::national_mod_offsets::factory_cost) + 1.0f; - float refit_discount = (c.get_refit_target()) ? state.defines.alice_factory_refit_cost_modifier : 1.0f; bool all_finished = true; if(!(n == state.local_player_nation && state.cheat_data.instant_industry)) { for(uint32_t j = 0; j < commodity_set::set_size && all_finished; ++j) { if(base_cost.commodity_type[j]) { - if(current_purchased.commodity_amounts[j] < base_cost.commodity_amounts[j] * factory_mod * admin_cost_factor * refit_discount) { + if(current_purchased.commodity_amounts[j] < base_cost.commodity_amounts[j] * factory_mod * admin_cost_factor) { all_finished = false; } } else { @@ -8669,13 +8743,12 @@ void resolve_constructions(sys::state& state) { } else { float factory_mod = (state.world.nation_get_modifier_values(n, sys::national_mod_offsets::factory_cost) + 1.0f) * std::max(0.1f, state.world.nation_get_modifier_values(n, sys::national_mod_offsets::factory_owner_cost)); - float refit_discount = (c.get_refit_target()) ? state.defines.alice_factory_refit_cost_modifier : 1.0f; bool all_finished = true; if(!(n == state.local_player_nation && state.cheat_data.instant_industry)) { for(uint32_t j = 0; j < commodity_set::set_size && all_finished; ++j) { if(base_cost.commodity_type[j]) { - if(current_purchased.commodity_amounts[j] < base_cost.commodity_amounts[j] * factory_mod * refit_discount) { + if(current_purchased.commodity_amounts[j] < base_cost.commodity_amounts[j] * factory_mod) { all_finished = false; } } else { diff --git a/src/economy/economy.hpp b/src/economy/economy.hpp index 5124206ff..20760537e 100644 --- a/src/economy/economy.hpp +++ b/src/economy/economy.hpp @@ -546,6 +546,8 @@ struct upgraded_factory { dcon::factory_type_id target_type; }; +economy::commodity_set calculate_factory_refit_goods_cost(sys::state& state, dcon::nation_id n, dcon::state_instance_id sid, dcon::factory_type_id from, dcon::factory_type_id to); +float calculate_factory_refit_money_cost(sys::state& state, dcon::nation_id n, dcon::state_instance_id sid, dcon::factory_type_id from, dcon::factory_type_id to); bool state_contains_constructed_factory(sys::state& state, dcon::state_instance_id si, dcon::factory_type_id ft); bool state_contains_factory(sys::state& state, dcon::state_instance_id s, dcon::factory_type_id ft); int32_t state_factory_count(sys::state& state, dcon::state_instance_id sid, dcon::nation_id n); diff --git a/src/gamestate/commands.cpp b/src/gamestate/commands.cpp index bf0d7ef07..8a84a6506 100644 --- a/src/gamestate/commands.cpp +++ b/src/gamestate/commands.cpp @@ -554,12 +554,19 @@ bool can_begin_factory_building_construction(sys::state& state, dcon::nation_id return false; } - // For refit factories must match in output good or inputs. if(refit_target) { if(type == refit_target) { return false; } + if(owner != source) { + return false; + } + + // Refit target must be unlocked and available + if(state.world.nation_get_active_building(source, refit_target) == false && !state.world.factory_type_get_is_available_from_start(refit_target)) + return false; + // Check if this factory is already being refit bool has_dup = false; economy::for_each_upgraded_factory(state, location, [&](economy::upgraded_factory const& nf) { has_dup = has_dup || nf.type == type; }); @@ -568,6 +575,8 @@ bool can_begin_factory_building_construction(sys::state& state, dcon::nation_id // We deliberately allow for duplicates to existing factories as this scenario is handled when construction is finished + // For refit factories must match in output good or inputs. + /* auto output_1 = state.world.factory_type_get_output(type); auto output_2 = state.world.factory_type_get_output(refit_target); auto inputs_1 = state.world.factory_type_get_inputs(type); @@ -586,6 +595,7 @@ bool can_begin_factory_building_construction(sys::state& state, dcon::nation_id if(output_1 != output_2 && !inputs_match) { return false; } + */ } if(state.world.nation_get_is_civilized(source) == false) diff --git a/src/gui/topbar_subwindows/production_subwindows/gui_factory_refit_window.hpp b/src/gui/topbar_subwindows/production_subwindows/gui_factory_refit_window.hpp index 9fa9c995c..de7d4d462 100644 --- a/src/gui/topbar_subwindows/production_subwindows/gui_factory_refit_window.hpp +++ b/src/gui/topbar_subwindows/production_subwindows/gui_factory_refit_window.hpp @@ -150,8 +150,27 @@ class factory_refit_type_listbox_entry_label : public button_element_base { is_not_upgrading = false; } - text::add_line(state, contents, "production_refit_factory_tooltip"); + text::add_line(state, contents, "production_refit_factory_tooltip", text::variable_type::what, state.world.factory_type_get_name(type), + text::variable_type::name, state.world.factory_type_get_name(refit_target)); + + text::add_line_break_to_layout(state, contents); + + auto refit_cost = economy::calculate_factory_refit_goods_cost(state, state.local_player_nation, fat.get_factory_location().get_province().get_state_membership(), type, refit_target); + auto total = 0.f; + for(uint32_t i = 0; i < economy::commodity_set::set_size; ++i) { + if(refit_cost.commodity_type[i] && refit_cost.commodity_amounts[i] > 0) { + float admin_eff = state.world.nation_get_administrative_efficiency(n); + float admin_cost_factor = 2.0f - admin_eff; + float factory_mod = state.world.nation_get_modifier_values(n, sys::national_mod_offsets::factory_cost) + 1.0f; + auto price = economy::price(state, sid, refit_cost.commodity_type[i]) * refit_cost.commodity_amounts[i] * factory_mod * admin_cost_factor; + total += price; + + text::add_line(state, contents, "factory_refit_cost", text::variable_type::what, state.world.commodity_get_name(refit_cost.commodity_type[i]), text::variable_type::val, text::fp_three_places{ refit_cost.commodity_amounts[i] }, text::variable_type::value, text::format_money(price)); + } + } + text::add_line(state, contents, "factory_refit_cost_total", text::variable_type::value, text::format_money(total)); + text::add_line_break_to_layout(state, contents); bool is_civ = state.world.nation_get_is_civilized(state.local_player_nation); @@ -163,35 +182,6 @@ class factory_refit_type_listbox_entry_label : public button_element_base { bool is_activated = state.world.nation_get_active_building(n, refit_target) == true || state.world.factory_type_get_is_available_from_start(refit_target); text::add_line_with_condition(state, contents, "factory_upgrade_condition_3", is_activated); - // For refit factories must match in output good or inputs. - bool type_compatibility = true; - if(refit_target) { - if(type == refit_target) { - type_compatibility = false; - } - - auto output_1 = state.world.factory_type_get_output(type); - auto output_2 = state.world.factory_type_get_output(refit_target); - auto inputs_1 = state.world.factory_type_get_inputs(type); - auto inputs_2 = state.world.factory_type_get_inputs(refit_target); - auto inputs_match = true; - - for(uint32_t i = 0; i < economy::commodity_set::set_size; ++i) { - auto input_1 = inputs_1.commodity_type[i]; - auto input_2 = inputs_2.commodity_type[i]; - - if(input_1 != input_2) { - inputs_match = false; - break; - } - } - if(output_1 != output_2 && !inputs_match) { - type_compatibility = false; - } - } - text::add_line_with_condition(state, contents, "factory_refit_condition_1", type_compatibility); - - auto rules = state.world.nation_get_combined_issue_rules(state.local_player_nation); text::add_line_with_condition(state, contents, "factory_upgrade_condition_8", (rules & issue_rule::expand_factory) != 0); From b70b9a17492d4b0696517a234c1610ff9439ebf2 Mon Sep 17 00:00:00 2001 From: Paul Nakonechnyy Date: Thu, 9 Jan 2025 18:53:57 +0200 Subject: [PATCH 3/6] Fix war parser --- src/parsing/parsers_declarations.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/parsing/parsers_declarations.cpp b/src/parsing/parsers_declarations.cpp index 36710f461..a868301e2 100644 --- a/src/parsing/parsers_declarations.cpp +++ b/src/parsing/parsers_declarations.cpp @@ -3300,6 +3300,19 @@ void war_history_file::finish(war_history_context& context) { auto rel = context.outer_context.state.world.force_create_war_participant(new_war, n); context.outer_context.state.world.war_participant_set_is_attacker(rel, false); } + + // release puppet if subject declares on overlord or vice versa + for(auto p : context.outer_context.state.world.war_get_war_participant(new_war)) { + auto ol_rel = context.outer_context.state.world.nation_get_overlord_as_subject(p.get_nation()); + auto ol = context.outer_context.state.world.overlord_get_ruler(ol_rel); + + for(auto p2 : context.outer_context.state.world.war_get_war_participant(new_war)) { + if(p2.get_nation() == ol && p.get_is_attacker() != p2.get_is_attacker()) { + nations::release_vassal(context.outer_context.state, ol_rel); + } + } + } + for(auto& wg : context.wargoals) { auto new_wg = fatten(context.outer_context.state.world, context.outer_context.state.world.create_wargoal()); new_wg.set_added_by(wg.actor_); From 2a04cf389f3361f18efcc4e6c2baf2571c9ba9d8 Mon Sep 17 00:00:00 2001 From: Paul Nakonechnyy Date: Thu, 9 Jan 2025 19:02:57 +0200 Subject: [PATCH 4/6] Revert Kat's "fix" --- src/military/military.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/military/military.cpp b/src/military/military.cpp index dedb66773..a4af4163c 100644 --- a/src/military/military.cpp +++ b/src/military/military.cpp @@ -7293,14 +7293,17 @@ void recover_org(sys::state& state) { auto tech_nation = in_nation ? in_nation : ar.get_controller_from_army_rebel_control().get_ruler_from_rebellion_within(); auto leader = ar.get_general_from_army_leadership(); + + // Morale (Organization Regain): increases a unit's organization by 0.01 * discipline for each % of morale. auto regen_mod = tech_nation.get_modifier_values(sys::national_mod_offsets::org_regain) + leader.get_personality().get_morale() + leader.get_background().get_morale() + 1.0f - + leader.get_prestige() * state.defines.leader_prestige_to_max_org_factor; + + leader.get_prestige() * state.defines.leader_prestige_to_morale_factor; auto spending_level = (in_nation ? in_nation.get_effective_land_spending() : 1.0f); auto modified_regen = regen_mod * spending_level / 150.f; - auto max_org = 0.25f + 0.75f * spending_level; for(auto reg : ar.get_army_membership()) { auto c_org = reg.get_regiment().get_org(); + // Unfulfilled supply doesn't lower max org as it makes half the game unplayable + auto max_org = std::max(c_org, 0.25f + 0.75f * spending_level); reg.get_regiment().set_org(std::min(c_org + modified_regen, max_org)); } } @@ -7322,9 +7325,10 @@ void recover_org(sys::state& state) { float over_size_penalty = oversize_amount > 1.0f ? 2.0f - oversize_amount : 1.0f; auto spending_level = in_nation.get_effective_naval_spending() * over_size_penalty; auto modified_regen = regen_mod * spending_level / 150.0f; - auto max_org = 0.25f + 0.75f * spending_level; for(auto reg : ar.get_navy_membership()) { auto c_org = reg.get_ship().get_org(); + // Unfulfilled supply doesn't lower max org as it makes half the game unplayable + auto max_org = std::max(c_org, 0.25f + 0.75f * spending_level); reg.get_ship().set_org(std::min(c_org + modified_regen, max_org)); } } From 68313552050e59ecd455f9f0148b52b8bc7a98eb Mon Sep 17 00:00:00 2001 From: Paul Nakonechnyy Date: Thu, 9 Jan 2025 19:02:57 +0200 Subject: [PATCH 5/6] Revert Kat's "fix" --- src/military/military.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/military/military.cpp b/src/military/military.cpp index dedb66773..5b3af2126 100644 --- a/src/military/military.cpp +++ b/src/military/military.cpp @@ -7293,14 +7293,18 @@ void recover_org(sys::state& state) { auto tech_nation = in_nation ? in_nation : ar.get_controller_from_army_rebel_control().get_ruler_from_rebellion_within(); auto leader = ar.get_general_from_army_leadership(); + + // Morale (Organization Regain): increases a unit's organization by 0.01 * discipline for each % of morale. + // Max org is applied in battle auto regen_mod = tech_nation.get_modifier_values(sys::national_mod_offsets::org_regain) + leader.get_personality().get_morale() + leader.get_background().get_morale() + 1.0f - + leader.get_prestige() * state.defines.leader_prestige_to_max_org_factor; + + leader.get_prestige() * state.defines.leader_prestige_to_morale_factor; auto spending_level = (in_nation ? in_nation.get_effective_land_spending() : 1.0f); auto modified_regen = regen_mod * spending_level / 150.f; - auto max_org = 0.25f + 0.75f * spending_level; for(auto reg : ar.get_army_membership()) { auto c_org = reg.get_regiment().get_org(); + // Unfulfilled supply doesn't lower max org as it makes half the game unplayable + auto max_org = std::max(c_org, 0.25f + 0.75f * spending_level); reg.get_regiment().set_org(std::min(c_org + modified_regen, max_org)); } } @@ -7322,9 +7326,10 @@ void recover_org(sys::state& state) { float over_size_penalty = oversize_amount > 1.0f ? 2.0f - oversize_amount : 1.0f; auto spending_level = in_nation.get_effective_naval_spending() * over_size_penalty; auto modified_regen = regen_mod * spending_level / 150.0f; - auto max_org = 0.25f + 0.75f * spending_level; for(auto reg : ar.get_navy_membership()) { auto c_org = reg.get_ship().get_org(); + // Unfulfilled supply doesn't lower max org as it makes half the game unplayable + auto max_org = std::max(c_org, 0.25f + 0.75f * spending_level); reg.get_ship().set_org(std::min(c_org + modified_regen, max_org)); } } From e9e0313d85c74a9268b3f0cf9d76163b85715c44 Mon Sep 17 00:00:00 2001 From: Paul Nakonechnyy Date: Fri, 10 Jan 2025 00:50:55 +0200 Subject: [PATCH 6/6] Minor UI fixes --- assets/localisation/en-US/alice.csv | 3 ++- src/gui/topbar_subwindows/gui_production_window.hpp | 6 +++--- .../production_subwindows/gui_factory_refit_window.hpp | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/assets/localisation/en-US/alice.csv b/assets/localisation/en-US/alice.csv index 9ac415119..573e3fe24 100644 --- a/assets/localisation/en-US/alice.csv +++ b/assets/localisation/en-US/alice.csv @@ -1087,7 +1087,8 @@ CANCEL_WARSUBSIDIES_WE_ACCEPT_LOG;$ACTOR$ stopped giving subsidies to us; CANCEL_WARSUBSIDIES_OTHER_ACCEPT_LOG;$ACTOR$ stopped giving subsidies to $RECIPIENT$; CANCEL_WARSUBSIDIES_DESC;Cancel monetary help to this country; CANCEL_WARSUBSIDIES_BUTTON;Cancel Subsidies; -production_refit_factory_tooltip;Refit ?Y$what$?! into ?Y$name$?! +production_refit_factory_tooltip_1;Refit ?Y$what$?! to a different production line +production_refit_factory_tooltip_2;Refit ?Y$what$?! into ?Y$name$?! factory_refit_cost;$what$: ?Y$val$?! (?Y$value$?!) factory_refit_cost_total;Total: ?Y$value$?! alice_budget_scaled_16;Diplomatic interest: ?G$value$ diff --git a/src/gui/topbar_subwindows/gui_production_window.hpp b/src/gui/topbar_subwindows/gui_production_window.hpp index 36f7e42dc..688078696 100644 --- a/src/gui/topbar_subwindows/gui_production_window.hpp +++ b/src/gui/topbar_subwindows/gui_production_window.hpp @@ -570,9 +570,9 @@ class factory_upgrade_progress_bar : public progress_bar { float factory_mod = state.world.nation_get_modifier_values(p.get_nation(), sys::national_mod_offsets::factory_cost) + 1.0f; float pop_factory_mod = std::max(0.1f, state.world.nation_get_modifier_values(p.get_nation(), sys::national_mod_offsets::factory_owner_cost)); float admin_cost_factor = (p.get_is_pop_project() ? pop_factory_mod : (2.0f - admin_eff)) * factory_mod; - float refit_discount = (p.get_refit_target()) ? state.defines.alice_factory_refit_cost_modifier : 1.0f; + auto owner = state.world.state_instance_get_nation_from_state_ownership(si); - auto& goods = state.world.factory_type_get_construction_costs(nf.type); + auto goods = economy::calculate_factory_refit_goods_cost(state, owner, si, nf.type, nf.target_type); auto& cgoods = p.get_purchased_goods(); for(uint32_t i = 0; i < economy::commodity_set::set_size; ++i) { @@ -586,7 +586,7 @@ class factory_upgrade_progress_bar : public progress_bar { text::add_to_layout_box(state, contents, box, std::string_view{ ": " }); text::add_to_layout_box(state, contents, box, text::fp_one_place{ cgoods.commodity_amounts[i] }); text::add_to_layout_box(state, contents, box, std::string_view{ " / " }); - text::add_to_layout_box(state, contents, box, text::fp_one_place{ goods.commodity_amounts[i] * admin_cost_factor * refit_discount }); + text::add_to_layout_box(state, contents, box, text::fp_one_place{ goods.commodity_amounts[i] * admin_cost_factor }); text::close_layout_box(contents, box); } } diff --git a/src/gui/topbar_subwindows/production_subwindows/gui_factory_refit_window.hpp b/src/gui/topbar_subwindows/production_subwindows/gui_factory_refit_window.hpp index de7d4d462..c42f4db97 100644 --- a/src/gui/topbar_subwindows/production_subwindows/gui_factory_refit_window.hpp +++ b/src/gui/topbar_subwindows/production_subwindows/gui_factory_refit_window.hpp @@ -13,7 +13,7 @@ class factory_refit_button : public button_element_base { auto fat = dcon::fatten(state.world, fid); const dcon::state_instance_id sid = retrieve(state, parent); const dcon::nation_id n = retrieve(state, parent); - auto type = retrieve(state, parent); + auto type = state.world.factory_get_building_type(fid); // no double upgrade bool is_not_upgrading = true; @@ -73,7 +73,7 @@ class factory_refit_button : public button_element_base { auto fat = dcon::fatten(state.world, fid); const dcon::state_instance_id sid = retrieve(state, parent); const dcon::nation_id n = retrieve(state, parent); - auto type = retrieve(state, parent); + auto type = state.world.factory_get_building_type(fid); // no double upgrade bool is_not_upgrading = true; @@ -82,7 +82,7 @@ class factory_refit_button : public button_element_base { is_not_upgrading = false; } - text::add_line(state, contents, "production_refit_factory_tooltip"); + text::add_line(state, contents, "production_refit_factory_tooltip_1", text::variable_type::what, state.world.factory_type_get_name(type)); text::add_line_break_to_layout(state, contents); @@ -150,7 +150,7 @@ class factory_refit_type_listbox_entry_label : public button_element_base { is_not_upgrading = false; } - text::add_line(state, contents, "production_refit_factory_tooltip", text::variable_type::what, state.world.factory_type_get_name(type), + text::add_line(state, contents, "production_refit_factory_tooltip_2", text::variable_type::what, state.world.factory_type_get_name(type), text::variable_type::name, state.world.factory_type_get_name(refit_target)); text::add_line_break_to_layout(state, contents);