1+ #pragma once
2+
3+ #include < cstdint>
4+ #include < string_view>
5+
6+ #include " openvic-simulation/core/string/StringLiteral.hpp"
7+ #include " openvic-simulation/core/Typedefs.hpp"
8+ #include " openvic-simulation/dataloader/NodeTools.hpp"
9+ #include " openvic-simulation/types/OptionalBool.hpp"
10+ #include " openvic-simulation/utility/Logger.hpp"
11+
12+ namespace OpenVic {
13+ // TODO replace with strategy pattern?
14+ enum struct ApportionmentMethod : std::uint8_t {
15+ largest_share,
16+ dhont,
17+ sainte_laque
18+ };
19+ enum struct UpperHouseComposition : std::uint8_t {
20+ same_as_ruling_party,
21+ rich_only,
22+ state_vote,
23+ population_vote
24+ };
25+ enum struct CulturalVotingRight : std::uint8_t {
26+ primary_culture_voting,
27+ culture_voting,
28+ all_voting
29+ };
30+
31+ struct RuleSet {
32+ private:
33+ struct RuleDefinition {
34+ const std::string_view name;
35+ OptionalBool const & value;
36+
37+ template <string_literal Name>
38+ static constexpr RuleDefinition Define (OptionalBool const & value) {
39+ return RuleDefinition { { Name }, value };
40+ }
41+ };
42+
43+ #define DO_FOR_ALL_RULES (F ) \
44+ F (build_railway) \
45+ F (build_factory) \
46+ F (expand_factory) \
47+ F (open_factory) \
48+ F (destroy_factory) \
49+ F (pop_build_factory) \
50+ F (pop_expand_factory) \
51+ F (pop_open_factory) \
52+ F (can_subsidise) \
53+ F (factory_priority) \
54+ F (delete_factory_if_no_input) \
55+ F (build_factory_invest) \
56+ F (expand_factory_invest) \
57+ F (open_factory_invest) \
58+ F (build_railway_invest) \
59+ F (pop_build_factory_invest) \
60+ F (pop_expand_factory_invest) \
61+ F (pop_open_factory_invest) \
62+ F (can_invest_in_pop_projects) \
63+ F (allow_foreign_investment) \
64+ F (primary_culture_voting) \
65+ F (culture_voting) \
66+ F (all_voting) \
67+ F (slavery_allowed) \
68+ F (same_as_ruling_party) \
69+ F (rich_only) \
70+ F (state_vote) \
71+ F (population_vote) \
72+ F (largest_share) \
73+ F (dhont) \
74+ F (sainte_laque)
75+
76+ #define RULE_FIELDS (rule ) \
77+ OptionalBool rule : 2 = OptionalBool::UNSPECIFIED;
78+
79+ DO_FOR_ALL_RULES (RULE_FIELDS)
80+ #undef RULE_FIELDS
81+
82+ static NodeTools::node_callback_t expect_rule_set (NodeTools::callback_t <RuleSet&&> ruleset_callback) {
83+ return [ruleset_callback](ast::NodeCPtr root) mutable -> bool {
84+ RuleSet ruleset;
85+ using enum NodeTools::dictionary_entry_t ::expected_count_t ;
86+ bool ret = NodeTools::expect_dictionary_keys (
87+ " sainte_laque" , ZERO_OR_ONE, NodeTools::expect_bool (
88+ [&ruleset](const bool value) mutable -> bool {
89+ const bool ret = ruleset.sainte_laque == OptionalBool::UNSPECIFIED;
90+ ruleset.sainte_laque = value ? OptionalBool::TRUE : OptionalBool::FALSE ;
91+ return ret;
92+ }
93+ )
94+ // TODO add the rest using the DO_FOR_ALL_RULES macro and avoid duplicating inline lambda callbacks
95+ )(root);
96+ ret &= ruleset_callback (std::move (ruleset));
97+ return ret;
98+ };
99+ }
100+
101+ #undef DO_FOR_ALL_RULES
102+
103+ template <typename EnumType, size_t N>
104+ constexpr EnumType select_with_priority (const RuleDefinition (&rules)[N]) const {
105+ const std::string_view context_name = type_name<EnumType>();
106+
107+ // 1. Check for explicit TRUE flags (Highest Priority)
108+ for (size_t i = 0 ; i < N; ++i) {
109+ if (rules[i].value == OptionalBool::TRUE ) {
110+ if (!std::is_constant_evaluated ()) {
111+ // Log conflicts with lower-priority methods
112+ for (size_t j = i + 1 ; j < N; ++j) {
113+ if (OV_unlikely (rules[j].value == OptionalBool::TRUE )) {
114+ spdlog::warn_s (
115+ " Both {} and {} are enabled for {}. {} is picked." ,
116+ rules[i].name ,
117+ rules[j].name ,
118+ context_name,
119+ rules[i].name
120+ );
121+ }
122+ }
123+ }
124+ return EnumType (i);
125+ }
126+ }
127+
128+ // 2. Check for UNSPECIFIED (Defaulting logic)
129+ for (size_t i = 0 ; i < N; ++i) {
130+ if (rules[i].value == OptionalBool::UNSPECIFIED) {
131+ if (!std::is_constant_evaluated ()) {
132+ spdlog::warn_s (" No {} enabled. Picking {} (unspecified)." , context_name, rules[i].name );
133+ }
134+ return EnumType (i);
135+ }
136+ }
137+
138+ // 3. Absolute Fallback
139+ if (!std::is_constant_evaluated ()) {
140+ spdlog::error_s (" All {} options are disabled. Falling back to {}." , context_name, rules[0 ].name );
141+ }
142+ return EnumType (0 );
143+ }
144+
145+ #define DEF (Value ) RuleDefinition::Define<#Value>(Value)
146+
147+ #define RESOLVE_DEFAULT_FALSE (method_name, field ) \
148+ constexpr bool method_name () const { \
149+ const bool is_true = field == OptionalBool::TRUE ; \
150+ if (!std::is_constant_evaluated () && field == OptionalBool::UNSPECIFIED) { \
151+ spdlog::warn_s (" {} is not specified, returning {}." , #field, is_true); \
152+ } \
153+ return is_true; \
154+ }
155+
156+ public:
157+ // political
158+ constexpr ApportionmentMethod get_apportionment_method () const {
159+ const RuleDefinition rules[] {
160+ DEF (largest_share),
161+ DEF (dhont),
162+ DEF (sainte_laque)
163+ };
164+ return select_with_priority<ApportionmentMethod>(rules);
165+ }
166+ constexpr UpperHouseComposition get_upper_house_composition () const {
167+ const RuleDefinition rules[] {
168+ DEF (same_as_ruling_party),
169+ DEF (rich_only),
170+ DEF (state_vote),
171+ DEF (population_vote)
172+ };
173+ return select_with_priority<UpperHouseComposition>(rules);
174+ }
175+ constexpr CulturalVotingRight get_cultural_voting_rights () const {
176+ const RuleDefinition rules[] {
177+ DEF (primary_culture_voting),
178+ DEF (culture_voting),
179+ DEF (all_voting)
180+ };
181+ return select_with_priority<CulturalVotingRight>(rules);
182+ }
183+ #undef DEF
184+ RESOLVE_DEFAULT_FALSE (is_slavery_legal, slavery_allowed)
185+ // economic
186+ RESOLVE_DEFAULT_FALSE(may_build_infrastructure_domestically, build_railway)
187+ RESOLVE_DEFAULT_FALSE(may_build_factory_domestically, build_factory)
188+ RESOLVE_DEFAULT_FALSE(may_expand_factory_domestically, expand_factory)
189+ RESOLVE_DEFAULT_FALSE(may_open_factory_domestically, open_factory)
190+ constexpr bool may_close_factory_domestically() const {
191+ return may_open_factory_domestically ();
192+ }
193+ RESOLVE_DEFAULT_FALSE (may_destroy_factory_domestically, destroy_factory)
194+ RESOLVE_DEFAULT_FALSE(may_subsidise_factory_domestically, can_subsidise)
195+ RESOLVE_DEFAULT_FALSE(may_set_factory_priority_domestically, factory_priority)
196+
197+ RESOLVE_DEFAULT_FALSE(pop_may_build_factory_domestically, pop_build_factory)
198+ RESOLVE_DEFAULT_FALSE(pop_may_expand_factory_domestically, pop_expand_factory)
199+ RESOLVE_DEFAULT_FALSE(pop_may_open_factory_domestically, pop_open_factory)
200+ RESOLVE_DEFAULT_FALSE(may_automatically_delete_factory_if_no_input_domestically, delete_factory_if_no_input)
201+ RESOLVE_DEFAULT_FALSE(may_invest_in_pop_projects_domestically, can_invest_in_pop_projects)
202+
203+ RESOLVE_DEFAULT_FALSE(may_invest_in_building_factory_abroad, build_factory_invest)
204+ RESOLVE_DEFAULT_FALSE(may_invest_in_expanding_factory_abroad, expand_factory_invest)
205+ RESOLVE_DEFAULT_FALSE(may_invest_in_opening_factory_abroad, open_factory_invest)
206+ RESOLVE_DEFAULT_FALSE(pop_may_invest_in_building_factory_abroad, pop_build_factory_invest)
207+ RESOLVE_DEFAULT_FALSE(pop_may_invest_in_expanding_factory_abroad, pop_expand_factory_invest)
208+ RESOLVE_DEFAULT_FALSE(pop_may_invest_in_opening_factory_abroad, pop_open_factory_invest)
209+ RESOLVE_DEFAULT_FALSE(may_invest_in_expanding_infrastructure_abroad, build_railway_invest)
210+
211+ RESOLVE_DEFAULT_FALSE(foreigners_may_invest, allow_foreign_investment)
212+ #undef RESOLVE_DEFAULT_FALSE
213+ };
214+ }
0 commit comments