Project Alice
Loading...
Searching...
No Matches
rebels.cpp
Go to the documentation of this file.
1#include "rebels.hpp"
2#include "system_state.hpp"
3#include "triggers.hpp"
4#include "ai.hpp"
5#include "effects.hpp"
6#include "demographics.hpp"
7#include "gui_event.hpp"
8#include "politics.hpp"
10#include "prng.hpp"
11
12namespace rebel {
13
14dcon::movement_id get_movement_by_position(sys::state& state, dcon::nation_id n, dcon::issue_option_id o) {
15 for(auto m : state.world.nation_get_movement_within(n)) {
16 if(m.get_movement().get_associated_issue_option() == o)
17 return m.get_movement().id;
18 }
19 return dcon::movement_id{};
20}
21dcon::movement_id get_movement_by_independence(sys::state& state, dcon::nation_id n, dcon::national_identity_id i) {
22 for(auto m : state.world.nation_get_movement_within(n)) {
23 if(m.get_movement().get_associated_independence() == i)
24 return m.get_movement().id;
25 }
26 return dcon::movement_id{};
27}
28
29dcon::rebel_faction_id get_faction_by_type(sys::state& state, dcon::nation_id n, dcon::rebel_type_id r) {
30 for(auto f : state.world.nation_get_rebellion_within(n)) {
31 if(f.get_rebels().get_type() == r)
32 return f.get_rebels().id;
33 }
34 return dcon::rebel_faction_id{};
35}
36
37void update_movement_values(sys::state& state) { // simply updates cached values
38
39 state.world.execute_serial_over_movement([&](auto ids) { state.world.movement_set_pop_support(ids, ve::fp_vector()); });
40 state.world.for_each_pop([&](dcon::pop_id p) {
41 if(auto m = state.world.pop_get_movement_from_pop_movement_membership(p); m) {
42 auto i = state.world.movement_get_associated_issue_option(m);
43 state.world.movement_get_pop_support(m) += state.world.pop_get_size(p) * (i ? pop_demographics::get_demo(state, p, pop_demographics::to_key(state, i)) : 1.0f);
44 }
45 });
46
47 static ve::vectorizable_buffer<float, dcon::nation_id> nation_reform_count(uint32_t(1));
48 static uint32_t old_count = 1;
49
50 auto new_count = state.world.nation_size();
51 if(new_count > old_count) {
52 nation_reform_count = state.world.nation_make_vectorizable_float_buffer();
53 old_count = new_count;
54 }
55
56 state.world.execute_serial_over_nation([&](auto host_nations) {
57 nation_reform_count.set(host_nations, 0.0f);
58 });
59
60 for(auto preform : state.culture_definitions.political_issues) {
61 if(state.world.issue_get_is_next_step_only(preform)) {
62 auto base_reform = state.world.issue_get_options(preform)[0].index() + 1;
63
64 state.world.execute_serial_over_nation([&](auto host_nations) {
65 ve::fp_vector reform_totals = nation_reform_count.get(host_nations);
66 ve::tagged_vector<dcon::issue_option_id> creform = state.world.nation_get_issues(host_nations, preform);
67 reform_totals = reform_totals + ve::to_float(ve::int_vector(creform.to_original_values()) - base_reform);
68 nation_reform_count.set(host_nations, reform_totals);
69 });
70 }
71 }
72
73 state.world.execute_serial_over_movement([&](auto ids) {
74 /*
75 - Movements can accumulate radicalism, but only in civ nations. Internally we may represent radicalism as two values, a
76 radicalism value and transient radicalism. Every day the radicalism value is computed as: define:MOVEMENT_RADICALISM_BASE
77 + the movements current transient radicalism + number-of-political-reforms-passed-in-the-country-over-base x
78 define:MOVEMENT_RADICALISM_PASSED_REFORM_EFFECT + radicalism-of-the-nation's-primary-culture +
79 maximum-nationalism-value-in-any-province x define:MOVEMENT_RADICALISM_NATIONALISM_FACTOR +
80 define:POPULATION_MOVEMENT_RADICAL_FACTOR x movement-support / nation's-non-colonial-population.
81 */
82 auto host_nations = state.world.movement_get_nation_from_movement_within(ids);
83
84 auto host_max_nationalism = ve::apply(
85 [&](dcon::nation_id n, dcon::movement_id m) {
86 if(auto iid = state.world.movement_get_associated_independence(m); iid) {
87 float nmax = 0.0f;
88 for(auto c : state.world.national_identity_get_core(iid)) {
89 if(c.get_province().get_nation_from_province_ownership() == n) {
90 nmax = std::max(nmax, c.get_province().get_nationalism());
91 }
92 }
93 return nmax;
94 } else {
95 return 0.0f;
96 }
97 },
98 host_nations, ids);
99 auto host_pculture = state.world.nation_get_primary_culture(host_nations);
100 auto host_cradicalism = ve::to_float(state.world.culture_get_radicalism(host_pculture));
101
102 auto ref_effect = nation_reform_count.get(host_nations) * state.defines.movement_radicalism_passed_reform_effect;
103 auto nat_effect = host_max_nationalism * state.defines.movement_radicalism_nationalism_factor;
104 auto mov_support = state.world.movement_get_pop_support(ids);
105 auto non_c_pop = state.world.nation_get_non_colonial_population(host_nations);
106 auto support_effect = state.defines.population_movement_radical_factor * mov_support / non_c_pop;
107
108 auto new_radicalism = state.world.movement_get_transient_radicalism(ids) + state.defines.movement_radicalism_base
109 + ref_effect + host_cradicalism + nat_effect + support_effect;
110 state.world.movement_set_radicalism(ids, ve::max(0.0f, new_radicalism));
111 });
112}
113
114void add_pop_to_movement(sys::state& state, dcon::pop_id p, dcon::movement_id m) {
115 remove_pop_from_movement(state, p);
116 auto i = state.world.movement_get_associated_issue_option(m);
117 state.world.movement_get_pop_support(m) += state.world.pop_get_size(p) * (i ? pop_demographics::get_demo(state, p, pop_demographics::to_key(state, i)) : 1.0f);
118 state.world.try_create_pop_movement_membership(p, m);
119}
120void remove_pop_from_movement(sys::state& state, dcon::pop_id p) {
121 auto prior_movement = state.world.pop_get_movement_from_pop_movement_membership(p);
122 if(prior_movement) {
123 auto i = state.world.movement_get_associated_issue_option(prior_movement);
124 state.world.movement_get_pop_support(prior_movement) -= state.world.pop_get_size(p) * (i ? pop_demographics::get_demo(state, p, pop_demographics::to_key(state, i)) : 1.0f);
125 state.world.delete_pop_movement_membership(state.world.pop_get_pop_movement_membership(p));
126 }
127}
128
129void suppress_movement(sys::state& state, dcon::nation_id n, dcon::movement_id m) {
130 /*
131 - When a movement is suppressed:
132 Increase the transient radicalism of the movement by: define:SUPPRESSION_RADICALISM_HIT
133 Set the consciousness of all pops that were in the movement to 1 and remove them from it.
134 */
135 state.world.movement_get_transient_radicalism(m) += state.defines.suppression_radicalisation_hit;
136 for(auto p : state.world.movement_get_pop_movement_membership(m)) {
137 //auto old_con = pop_demographics::get_consciousness(state, p.get_pop());
138 pop_demographics::set_consciousness(state, p.get_pop().id, 1.0f);
139 }
140 state.world.movement_remove_all_pop_movement_membership(m);
141}
142
143void turn_movement_into_rebels(sys::state& state, dcon::movement_id m) {
144 /*
145 - When the radicalism value for a movement reaches 100, pops get removed from the movement and added to a rebel faction. Those
146 pops have their militancy increased to a minimum of define:MIL_ON_REB_MOVE. See below for determining which rebel faction the
147 pop joins.
148 */
149
150 for(auto p : state.world.movement_get_pop_movement_membership(m)) {
151 pop_demographics::set_militancy(state, p.get_pop().id, std::max(pop_demographics::get_militancy(state, p.get_pop()), state.defines.mil_on_reb_move));
152 }
153 // and then they will automatically be picked up by updating rebels
154
155 state.world.delete_movement(m);
156}
157
158bool issue_is_valid_for_movement(sys::state& state, dcon::nation_id nation_within, dcon::issue_option_id i) {
159 auto parent_issue = state.world.issue_option_get_parent_issue(i);
160 auto current_setting = state.world.nation_get_issues(nation_within, parent_issue);
161 if(i == current_setting)
162 return false;
163 if(state.world.issue_get_is_next_step_only(parent_issue)) {
164 if(i.index() != current_setting.id.index() - 1 && i.index() != current_setting.id.index() + 1)
165 return false;
166 }
167 auto allow = state.world.issue_option_get_allow(i);
168 if(allow && !trigger::evaluate(state, allow, trigger::to_generic(nation_within), trigger::to_generic(nation_within), 0))
169 return false;
170
171 return true;
172}
173
174bool movement_is_valid(sys::state& state, dcon::movement_id m) {
175 auto i = state.world.movement_get_associated_issue_option(m);
176
177 auto nation_within = state.world.movement_get_nation_from_movement_within(m);
178 if(i) {
179 return issue_is_valid_for_movement(state, nation_within, i);
180 }
181
182 auto t = state.world.movement_get_associated_independence(m);
183 if(t) {
184 for(auto p : state.world.nation_get_province_ownership(nation_within)) {
185 if(state.world.get_core_by_prov_tag_key(p.get_province(), t))
186 return true;
187 }
188 return false;
189 }
190
191 return false;
192}
193
195 state.world.for_each_pop([&](dcon::pop_id p) {
196 auto owner = nations::owner_of_pop(state, p);
197 // pops not in a nation can't be in a movement
198 if(!owner)
199 return;
200
201 // - Slave pops cannot belong to a movement
202 if(state.world.pop_get_poptype(p) == state.culture_definitions.slaves)
203 return;
204 // pops in rebel factions don't join movements
205 if(state.world.pop_get_rebel_faction_from_pop_rebellion_membership(p))
206 return;
207
208 auto pop_location = state.world.pop_get_province_from_pop_location(p);
209 // pops in colonial provinces don't join movements
210 if(state.world.province_get_is_colonial(pop_location))
211 return;
212
213 auto existing_movement = state.world.pop_get_movement_from_pop_movement_membership(p);
214 auto mil = pop_demographics::get_militancy(state, p);
215
216 // -Pops with define : MIL_TO_JOIN_REBEL or greater militancy cannot join a movement
217 if(mil >= state.defines.mil_to_join_rebel) {
218 remove_pop_from_movement(state, p);
219 return;
220 }
221 if(existing_movement) {
222 auto i =
223 state.world.movement_get_associated_issue_option(existing_movement);
224 if(i) {
225 auto support = pop_demographics::get_demo(state, p, pop_demographics::to_key(state, i));
226 if(support * 100.0f < state.defines.issue_movement_leave_limit) {
227 // If the pop's support of the issue for an issue-based movement drops below define:ISSUE_MOVEMENT_LEAVE_LIMIT
228 // the pop will leave the movement.
229 remove_pop_from_movement(state, p);
230 return;
231 }
232 } else if(mil < state.defines.nationalist_movement_mil_cap) {
233 // If the pop's militancy falls below define:NATIONALIST_MOVEMENT_MIL_CAP, the pop will leave an independence
234 // movement.
235 remove_pop_from_movement(state, p);
236 return;
237 }
238 // pop still remains in movement, no more work to do
239 return;
240 }
241
242 auto con = pop_demographics::get_consciousness(state, p);
243 auto lit = pop_demographics::get_literacy(state, p);
244
245 // a pop with a consciousness of at least 1.5 or a literacy of at least 0.25 may join a movement
246 if(con >= 1.5f || lit >= 0.25f) {
247 /*
248 - If there are one or more issues that the pop supports by at least define:ISSUE_MOVEMENT_JOIN_LIMIT, then the pop has
249 a chance to join an issue-based movement at probability: issue-support x 9 x define:MOVEMENT_LIT_FACTOR x pop-literacy
250 + issue-support x 9 x define:MOVEMENT_CON_FACTOR x pop-consciousness
251 */
252 dcon::issue_option_id max_option;
253 float max_support = 0;
254 state.world.for_each_issue_option([&](dcon::issue_option_id io) {
255 auto parent = state.world.issue_option_get_parent_issue(io);
256 auto co = state.world.nation_get_issues(owner, parent);
257 auto allow = state.world.issue_option_get_allow(io);
258 if(co != io && (state.world.issue_get_issue_type(parent) == uint8_t(culture::issue_type::social) || state.world.issue_get_issue_type(parent) == uint8_t(culture::issue_type::political))) { // filter out currently active issue
259 auto sup = pop_demographics::get_demo(state, p, pop_demographics::to_key(state, io));
260 if(sup * 100.0f >= state.defines.issue_movement_join_limit && sup > max_support) { // filter out -- above limit thersholds
261 /*
262 then the pop has a chance to join an issue-based movement at probability: issue-support x 9 x define:MOVEMENT_LIT_FACTOR x pop-literacy + issue-support x 9 x define:MOVEMENT_CON_FACTOR x pop-consciousness
263 */
264
265 // probability test
266 auto fp_prob = 9.0f * sup * (state.defines.movement_lit_factor * lit + state.defines.movement_con_factor * con);
267 auto rvalue = float(uint32_t(rng::get_random(state, (p.value << 3) ^ io.index()) & 0xFFFF)) / float(0x10000);
268 if(rvalue < fp_prob) {
269
270 // is this issue possible to get by law?
271 if(state.world.issue_get_is_next_step_only(parent) == false || co.id.index() + 1 == io.index() || co.id.index() - 1 == io.index()) {
272
273 max_option = io;
274 max_support = sup;
275 }
276 }
277 }
278 }
279 });
280 if(max_option) {
281 if(auto m = get_movement_by_position(state, owner, max_option); m) {
282 add_pop_to_movement(state, p, m);
283 } else if(issue_is_valid_for_movement(state, owner, max_option)) {
284 auto new_movement = fatten(state.world, state.world.create_movement());
285 new_movement.set_associated_issue_option(max_option);
286 state.world.try_create_movement_within(new_movement, owner);
287 add_pop_to_movement(state, p, new_movement);
288 }
289 } else if(!state.world.pop_get_is_primary_or_accepted_culture(p) && mil >= state.defines.nationalist_movement_mil_cap) {
290 /*
291 - If there are no valid issues, the pop has a militancy of at least define:NATIONALIST_MOVEMENT_MIL_CAP, does not
292 have the primary culture of the nation it is in, and does have the primary culture of some core in its province,
293 then it has a chance (20% ?) of joining an independence movement for such a core.
294 */
295 if(rng::reduce(uint32_t(rng::get_random(state, p.value)), 10) != 0) {
296 return; // exit out of considering this pop
297 }
298 auto pop_culture = state.world.pop_get_culture(p);
299 for(auto c : state.world.province_get_core(pop_location)) {
300 if(c.get_identity().get_primary_culture() == pop_culture) {
301 auto existing_mov = get_movement_by_independence(state, owner, c.get_identity());
302 if(existing_mov) {
303 state.world.try_create_pop_movement_membership(p, existing_mov);
304 } else {
305 auto new_mov = fatten(state.world, state.world.create_movement());
306 new_mov.set_associated_independence(c.get_identity());
307 state.world.try_create_movement_within(new_mov, owner);
308 state.world.try_create_pop_movement_membership(p, new_mov);
309 }
310 break;
311 }
312 }
313 }
314 }
315 });
316}
317
318void update_movements(sys::state& state) { // updates cached values and then possibly turns movements into rebels
320
321 // IMPORTANT: we count down here so that we can delete as we go, compacting from the end
322 for(auto last = state.world.movement_size(); last-- > 0;) {
323 dcon::movement_id m{dcon::movement_id::value_base_t(last)};
324 if(!movement_is_valid(state, m)) {
325 state.world.delete_movement(m);
326 }
327 }
328
330
331 for(auto last = state.world.movement_size(); last-- > 0;) {
332 dcon::movement_id m{dcon::movement_id::value_base_t(last)};
333 if(state.world.movement_get_radicalism(m) >= 100.0f) {
335 }
336 }
337}
338
339void remove_pop_from_rebel_faction(sys::state& state, dcon::pop_id p) {
340 if(auto m = state.world.pop_get_pop_rebellion_membership(p); m) {
341 auto fac = state.world.pop_rebellion_membership_get_rebel_faction(m);
342 state.world.rebel_faction_get_possible_regiments(fac) -=
343 int32_t(state.world.pop_get_size(p) / state.defines.pop_size_per_regiment);
344 state.world.delete_pop_rebellion_membership(m);
345 }
346}
347void add_pop_to_rebel_faction(sys::state& state, dcon::pop_id p, dcon::rebel_faction_id m) {
349 state.world.try_create_pop_rebellion_membership(p, m);
350 state.world.rebel_faction_get_possible_regiments(m) +=
351 int32_t(state.world.pop_get_size(p) / state.defines.pop_size_per_regiment);
352}
353
354bool rebel_faction_is_valid(sys::state& state, dcon::rebel_faction_id m) {
355 /*
356 - A rebel faction with no pops in it will disband
357 - Rebel factions whose independence country exists will disband (different for defection rebels?)
358 - Pan nationalists inside their union country will disband
359 - Any pops belonging to a rebel faction that disbands have their militancy reduced to 0
360 */
361
362 auto pops = state.world.rebel_faction_get_pop_rebellion_membership(m);
363 if(pops.begin() == pops.end())
364 return false;
365
366 auto type = fatten(state.world, state.world.rebel_faction_get_type(m));
367
368 auto tag = state.world.rebel_faction_get_defection_target(m);
369 auto within = fatten(state.world, m).get_rebellion_within().get_ruler();
370 if(within.get_identity_from_identity_holder() == tag)
371 return false;
372
373 return true;
374}
375
376bool pop_is_compatible_with_rebel_faction(sys::state& state, dcon::pop_id p, dcon::rebel_faction_id t) {
377 /*
378 - Faction compatibility: a pop will not join a faction that it is excluded from based on its culture, culture group, religion,
379 or ideology (here it is the dominant ideology of the pop that matters). There is also some logic for determining if a pop is
380 compatible with a national identity for independence. I don't think it is worth trying to imitate the logic of the base game
381 here. Instead I will go with: pop is not an accepted culture and either its primary culture is associated with that identity
382 *or* there is no core in the province associated with its primary identity.
383 */
384 auto type = fatten(state.world, state.world.rebel_faction_get_type(t));
385 auto fac = fatten(state.world, t);
386 auto pop = fatten(state.world, p);
387 if(type.get_independence() != 0 || type.get_defection() != 0) {
388 if(type.get_independence() == uint8_t(culture::rebel_independence::pan_nationalist) ||
389 type.get_defection() == uint8_t(culture::rebel_defection::pan_nationalist)) {
390 if(pop.get_is_primary_or_accepted_culture())
391 return true;
392 } else {
393 if(pop.get_is_primary_or_accepted_culture())
394 return false;
395 }
396 for(auto core : pop.get_province_from_pop_location().get_core()) {
397 if(core.get_identity().get_primary_culture() == pop.get_culture())
398 return true;
399 }
400 }
401 if(fac.get_primary_culture() && fac.get_primary_culture() != pop.get_culture())
402 return false;
403 if(fac.get_religion() && fac.get_religion() != pop.get_religion())
404 return false;
405 if(fac.get_primary_culture_group() &&
406 fac.get_primary_culture_group() != pop.get_culture().get_group_from_culture_group_membership())
407 return false;
408 if(fac.get_type().get_ideology() && fac.get_type().get_ideology_restriction() && fac.get_type().get_ideology() != pop.get_dominant_ideology())
409 return false;
410 if(fac.get_type().get_ideology() && pop.get_province_from_pop_location().get_is_colonial())
411 return false;
412 return true;
413}
414
415bool pop_is_compatible_with_rebel_type(sys::state& state, dcon::pop_id p, dcon::rebel_type_id t) {
416 auto fac = fatten(state.world, t);
417 auto pop = fatten(state.world, p);
418
419 if(fac.get_independence() != 0 || fac.get_defection() != 0) {
420 if(fac.get_independence() == uint8_t(culture::rebel_independence::pan_nationalist) ||
421 fac.get_defection() == uint8_t(culture::rebel_defection::pan_nationalist)) {
422 if(pop.get_is_primary_or_accepted_culture())
423 return true;
424 } else {
425 if(pop.get_is_primary_or_accepted_culture())
426 return false;
427 }
428 for(auto core : pop.get_province_from_pop_location().get_core()) {
429 if(core.get_identity().get_primary_culture() == pop.get_culture())
430 return true;
431 }
432 return false;
433 }
434 //Discriminated pops focus on independence rather than political issues
435 else {
436 if(!pop.get_is_primary_or_accepted_culture()) {
437 return false;
438 }
439 }
440 if(fac.get_ideology() && fac.get_ideology() != pop.get_dominant_ideology()) {
441 return false;
442 }
443 return true;
444}
445
447 state.world.for_each_pop([&](dcon::pop_id p) {
448 auto owner = nations::owner_of_pop(state, p);
449 // pops not in a nation can't be in a rebel faction
450 if(!owner)
451 return;
452
453 auto mil = pop_demographics::get_militancy(state, p);
454 auto existing_faction = state.world.pop_get_rebel_faction_from_pop_rebellion_membership(p);
455
456 // -Pops with define : MIL_TO_JOIN_REBEL will join a rebel_faction
457 if(mil >= state.defines.mil_to_join_rebel) {
458 if(existing_faction && !pop_is_compatible_with_rebel_faction(state, p, existing_faction)) {
460 } else {
461 auto prov = state.world.pop_get_province_from_pop_location(p);
462 /*
463 - A pop in a province sieged or controlled by rebels will join that faction, if the pop is compatible with the
464 faction.
465 */
466
467 auto occupying_faction = state.world.province_get_rebel_faction_from_province_rebel_control(prov);
468 if(occupying_faction && pop_is_compatible_with_rebel_faction(state, p, occupying_faction)) {
469 assert(!bool(state.world.province_get_nation_from_province_control(prov)));
470 add_pop_to_rebel_faction(state, p, occupying_faction);
471 } else {
472 /*
473 - Otherwise take all the compatible and possible rebel types. Determine the spawn chance for each of them, by
474 taking the *product* of the modifiers. The pop then joins the type with the greatest chance (that's right, it
475 isn't really a *chance* at all). If that type has a defection type, it joins the faction with the national
476 identity most compatible with it and that type (pan-nationalist go to the union tag, everyone else uses the
477 logic I outline below)
478 */
479 float greatest_chance = 0.0f;
480 dcon::rebel_faction_id f;
481 for(auto rf : state.world.nation_get_rebellion_within(owner)) {
482 if(pop_is_compatible_with_rebel_faction(state, p, rf.get_rebels())) {
483 auto chance = rf.get_rebels().get_type().get_spawn_chance();
485 trigger::to_generic(owner), trigger::to_generic(rf.get_rebels().id));
486 if(eval > greatest_chance) {
487 f = rf.get_rebels();
488 greatest_chance = eval;
489 }
490 }
491 }
492
493 dcon::rebel_faction_id temp = state.world.create_rebel_faction();
494 dcon::national_identity_id ind_tag = [&]() {
495 auto prov = state.world.pop_get_province_from_pop_location(p);
496 for(auto core : state.world.province_get_core(prov)) {
497 if(!core.get_identity().get_is_not_releasable() && core.get_identity().get_primary_culture() == state.world.pop_get_culture(p))
498 return core.get_identity().id;
499 }
500 return dcon::national_identity_id{};
501 }();
502
503 dcon::rebel_type_id max_type;
504
505 state.world.for_each_rebel_type([&](dcon::rebel_type_id rt) {
506 if(pop_is_compatible_with_rebel_type(state, p, rt)) {
507 state.world.rebel_faction_set_type(temp, rt);
508 state.world.rebel_faction_set_defection_target(temp, dcon::national_identity_id{});
509 state.world.rebel_faction_set_primary_culture(temp, dcon::culture_id{});
510 state.world.rebel_faction_set_primary_culture_group(temp, dcon::culture_group_id{});
511 state.world.rebel_faction_set_religion(temp, dcon::religion_id{});
512
513 switch(culture::rebel_defection(state.world.rebel_type_get_defection(rt))) {
515 state.world.rebel_faction_set_primary_culture(temp, state.world.pop_get_culture(p));
516 state.world.rebel_faction_set_defection_target(temp, ind_tag);
517 if(!ind_tag)
518 return; // skip -- no defection possible
519 if(state.world.pop_get_is_primary_or_accepted_culture(p))
520 return; // skip -- can't defect
521 break;
523 state.world.rebel_faction_set_primary_culture_group(temp,
524 state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p)));
525 state.world.rebel_faction_set_defection_target(temp, ind_tag);
526 if(!ind_tag)
527 return; // skip -- no defection possible
528 if(state.world.pop_get_is_primary_or_accepted_culture(p))
529 return; // skip -- can't defect
530 break;
532 state.world.rebel_faction_set_religion(temp, state.world.pop_get_religion(p));
533 state.world.rebel_faction_set_defection_target(temp, ind_tag);
534 if(!ind_tag)
535 return; // skip -- no defection possible
536 if(state.world.pop_get_is_primary_or_accepted_culture(p))
537 return; // skip -- can't defect
538 break;
540 auto cg = state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p));
541 auto u = state.world.culture_group_get_identity_from_cultural_union_of(cg);
542 if(!u)
543 return; // skip -- no pan nationalist possible
544 state.world.rebel_faction_set_defection_target(temp, u);
545 break;
546 }
548 state.world.rebel_faction_set_defection_target(temp, ind_tag);
549 if(!ind_tag)
550 return; // skip -- no defection possible
551 if(state.world.pop_get_is_primary_or_accepted_culture(p))
552 return; // skip -- can't defect
553 break;
554 default:
555 break;
556 }
557
558 switch(culture::rebel_independence(state.world.rebel_type_get_independence(rt))) {
560 state.world.rebel_faction_set_primary_culture(temp, state.world.pop_get_culture(p));
561 state.world.rebel_faction_set_defection_target(temp, ind_tag);
562 if(!ind_tag)
563 return; // skip -- no defection possible
564 if(state.world.pop_get_is_primary_or_accepted_culture(p))
565 return; // skip -- can't defect
566 break;
568 state.world.rebel_faction_set_primary_culture_group(temp,
569 state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p)));
570 state.world.rebel_faction_set_defection_target(temp, ind_tag);
571 if(!ind_tag)
572 return; // skip -- no defection possible
573 if(state.world.pop_get_is_primary_or_accepted_culture(p))
574 return; // skip -- can't defect
575 break;
577 state.world.rebel_faction_set_religion(temp, state.world.pop_get_religion(p));
578 state.world.rebel_faction_set_defection_target(temp, ind_tag);
579 if(!ind_tag)
580 return; // skip -- no defection possible
581 if(state.world.pop_get_is_primary_or_accepted_culture(p))
582 return; // skip -- can't defect
583 break;
585 auto cg = state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p));
586 auto u = state.world.culture_group_get_identity_from_cultural_union_of(cg);
587 if(!u)
588 return; // skip -- no pan nationalist possible
589 if(state.world.pop_get_is_primary_or_accepted_culture(p))
590 return; // skip -- can't defect
591 state.world.rebel_faction_set_defection_target(temp, u);
592 break;
593 }
595 state.world.rebel_faction_set_defection_target(temp, ind_tag);
596 if(!ind_tag)
597 return; // skip -- no defection possible
598 if(state.world.pop_get_is_primary_or_accepted_culture(p))
599 return; // skip -- can't defect
600 break;
602 state.world.rebel_faction_set_defection_target(temp, ind_tag);
603 if(!ind_tag)
604 return; // skip -- no defection possible
605 if(state.world.pop_get_is_primary_or_accepted_culture(p))
606 return; // skip -- can't defect
607 break;
608 default:
609 break;
610 }
611
612 auto chance = state.world.rebel_type_get_spawn_chance(rt);
615 if(eval > greatest_chance) {
616 f = temp;
617 max_type = rt;
618 greatest_chance = eval;
619 }
620 }
621 });
622
623 if(f != temp) {
624 state.world.delete_rebel_faction(temp);
625 } else {
626 auto rt = max_type;
627 state.world.rebel_faction_set_type(temp, rt);
628 state.world.rebel_faction_set_defection_target(temp, dcon::national_identity_id{});
629 state.world.rebel_faction_set_primary_culture(temp, dcon::culture_id{});
630 state.world.rebel_faction_set_primary_culture_group(temp, dcon::culture_group_id{});
631 state.world.rebel_faction_set_religion(temp, dcon::religion_id{});
632
633 switch(culture::rebel_defection(state.world.rebel_type_get_defection(rt))) {
635 state.world.rebel_faction_set_primary_culture(temp, state.world.pop_get_culture(p));
636 state.world.rebel_faction_set_defection_target(temp, ind_tag);
637 break;
639 state.world.rebel_faction_set_primary_culture_group(temp,
640 state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p)));
641 state.world.rebel_faction_set_defection_target(temp, ind_tag);
642 break;
644 state.world.rebel_faction_set_religion(temp, state.world.pop_get_religion(p));
645 state.world.rebel_faction_set_defection_target(temp, ind_tag);
646 break;
648 auto cg = state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p));
649 auto u = state.world.culture_group_get_identity_from_cultural_union_of(cg);
650 state.world.rebel_faction_set_defection_target(temp, u);
651 break;
652 }
654 state.world.rebel_faction_set_defection_target(temp, ind_tag);
655 break;
656 default:
657 break;
658 }
659
660 switch(culture::rebel_independence(state.world.rebel_type_get_independence(rt))) {
662 state.world.rebel_faction_set_primary_culture(temp, state.world.pop_get_culture(p));
663 state.world.rebel_faction_set_defection_target(temp, ind_tag);
664 break;
666 state.world.rebel_faction_set_primary_culture_group(temp,
667 state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p)));
668 state.world.rebel_faction_set_defection_target(temp, ind_tag);
669 break;
671 state.world.rebel_faction_set_religion(temp, state.world.pop_get_religion(p));
672 state.world.rebel_faction_set_defection_target(temp, ind_tag);
673 break;
675 auto cg = state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p));
676 auto u = state.world.culture_group_get_identity_from_cultural_union_of(cg);
677 state.world.rebel_faction_set_defection_target(temp, u);
678 break;
679 }
681 state.world.rebel_faction_set_defection_target(temp, ind_tag);
682 break;
684 state.world.rebel_faction_set_defection_target(temp, ind_tag);
685 break;
686 default:
687 break;
688 }
689
690 if(state.world.rebel_type_get_culture_restriction(rt) && !state.world.rebel_faction_get_primary_culture(temp)) {
691 state.world.rebel_faction_set_primary_culture(temp, state.world.pop_get_culture(p));
692 }
693 if(state.world.rebel_type_get_culture_group_restriction(rt) && !state.world.rebel_faction_get_primary_culture_group(temp)) {
694 state.world.rebel_faction_set_primary_culture_group(temp, state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p)));
695 }
696
697 state.world.try_create_rebellion_within(temp, owner);
698 }
699
700 if(greatest_chance > 0) {
701 add_pop_to_rebel_faction(state, p, f);
702 }
703 }
704 }
705 } else { // less than: MIL_TO_JOIN_REBEL will join a rebel_faction -- leave faction
706 if(existing_faction) {
708 }
709 }
710 });
711}
712
713void delete_faction(sys::state& state, dcon::rebel_faction_id reb) {
714 auto control = state.world.rebel_faction_get_province_rebel_control(reb);
715 for(auto p : control) {
716 auto prov = p.get_province();
717 province::set_province_controller(state, prov, prov.get_nation_from_province_ownership());
718 }
719 state.world.delete_rebel_faction(reb);
720}
721
724 // IMPORTANT: we count down here so that we can delete as we go, compacting from the end
725 for(auto last = state.world.rebel_faction_size(); last-- > 0;) {
726 dcon::rebel_faction_id m{dcon::rebel_faction_id::value_base_t(last)};
727 if(!rebel_faction_is_valid(state, m)) {
728 for(auto members : state.world.rebel_faction_get_pop_rebellion_membership(m)) {
729 pop_demographics::set_militancy(state, members.get_pop().id, 0.0f);
730 }
731 delete_faction(state, m);
732 }
733 }
734}
735
736inline constexpr float org_gain_factor = 0.4f;
737
739 // update brigade count
740 state.world.for_each_rebel_faction([&](dcon::rebel_faction_id rf) {
741 int32_t total = 0;
742 for(auto pop : state.world.rebel_faction_get_pop_rebellion_membership(rf)) {
743 total += int32_t(state.world.pop_get_size(pop.get_pop()) / state.defines.pop_size_per_regiment);
744 }
745 state.world.rebel_faction_set_possible_regiments(rf, total);
746 });
747
748 // update org
749 state.world.for_each_rebel_faction([&](dcon::rebel_faction_id rf) {
750 /*
751 - Sum for each pop belonging to the faction that has made money in the day: (pop-income + 0.02) x pop-literacy x (10 if
752 militancy is > define:MILITANCY_TO_AUTO_RISE and 5 if militancy is less than that but > define:MILITANCY_TO_JOIN_RISING) /
753 (1 + national-administration-spending-setting). Take this sum, multiply by 0.001 x (rebel organization from technology +
754 1) and divide by the number of regiments those pops could form. If positive, add this to the current organization of the
755 rebel faction (to a maximum of 1). This appears to be done daily.
756 */
757
758 float total_change = 0;
759 for(auto pop : state.world.rebel_faction_get_pop_rebellion_membership(rf)) {
760 auto mil_factor = [&]() {
761 auto m = pop_demographics::get_militancy(state, pop.get_pop());
762 if(m > state.defines.mil_to_autorise)
763 return 10.0f;
764 else if(m > state.defines.mil_to_join_rising)
765 return 5.0f;
766 return 1.0f;
767 }();
768 total_change += pop.get_pop().get_savings() * pop_demographics::get_literacy(state, pop.get_pop()) * mil_factor * org_gain_factor;
769 }
770 auto reg_count = state.world.rebel_faction_get_possible_regiments(rf);
771 auto within = state.world.rebel_faction_get_ruler_from_rebellion_within(rf);
772 auto rebel_org_mod = 1.0f + state.world.nation_get_rebel_org_modifier(within, state.world.rebel_faction_get_type(rf));
773
774 if(reg_count > 0) {
775 state.world.rebel_faction_set_organization(rf,
776 std::min(1.0f,
777 state.world.rebel_faction_get_organization(rf) +
778 total_change *
779 (1.0f + state.world.nation_get_rebel_org_modifier(within, state.world.rebel_faction_get_type(rf))) *
780 0.001f / (1.0f + state.world.nation_get_administrative_efficiency(within)) / float(reg_count)));
781 }
782 });
783}
784
785void get_hunting_targets(sys::state& state, dcon::nation_id n, std::vector<impl::prov_str>& rebel_provs) {
786 assert(rebel_provs.empty());
787 auto nat = dcon::fatten(state.world, n);
788 for(auto prov : nat.get_province_ownership()) {
789 if(prov.get_province().get_rebel_faction_from_province_rebel_control()
790 || military::rebel_army_in_province(state, prov.get_province()))
791 rebel_provs.push_back(impl::prov_str{ prov.get_province().id, ai::estimate_rebel_strength(state, prov.get_province()) });
792 }
793}
794
795void sort_hunting_targets(sys::state& state, impl::arm_str const& ar, std::vector<impl::prov_str>& rebel_provs) {
796 auto our_str = ar.str;
797 auto loc = state.world.army_get_location_from_army_location(ar.a);
798 std::sort(rebel_provs.begin(), rebel_provs.end(), [&](impl::prov_str const& a, impl::prov_str const& b) {
799 auto aa = 0.001f * -(our_str - a.str);
800 auto ab = 0.001f * -(our_str - b.str);
801 auto da = province::sorting_distance(state, a.p, loc) + aa;
802 auto db = province::sorting_distance(state, b.p, loc) + ab;
803 if(da != db)
804 return da < db;
805 else
806 return a.p.index() < b.p.index();
807 });
808}
809
811 for(auto rf : state.world.in_rebel_faction) {
812 auto const faction_owner = rf.get_ruler_from_rebellion_within();
813 // rebel hunting logic
814 if(faction_owner.get_is_player_controlled()) {
815 static std::vector<impl::arm_str> rebel_hunters;
816 rebel_hunters.clear();
817 for(auto ar : faction_owner.get_army_control()) {
818 auto loc = ar.get_army().get_location_from_army_location();
819 if(ar.get_army().get_is_rebel_hunter()
820 && !ar.get_army().get_battle_from_army_battle_participation()
821 && !ar.get_army().get_navy_from_army_transport()
822 && !ar.get_army().get_arrival_time()
823 && loc.get_nation_from_province_control() == faction_owner
824 ) {
825 rebel_hunters.push_back(impl::arm_str{ ar.get_army().id, ai::estimate_army_offensive_strength (state, ar.get_army()) });
826 }
827 }
828
829 static std::vector<impl::prov_str> rebel_provs;
830 rebel_provs.clear();
831 get_hunting_targets(state, faction_owner.id, rebel_provs);
832
833 while(rebel_provs.size() > 0 && rebel_hunters.size() > 0) {
834 auto rh = rebel_hunters[0];
835 sort_hunting_targets(state, rh, rebel_provs);
836
837 auto closest_prov = rebel_provs[0].p;
838 std::sort(rebel_hunters.begin(), rebel_hunters.end(), [&](impl::arm_str const& a, impl::arm_str const& b) {
839 auto pa = state.world.army_get_location_from_army_location(a.a);
840 auto pb = state.world.army_get_location_from_army_location(b.a);
841 auto as = 0.001f * std::max<float>(a.str, 1.f);
842 auto bs = 0.001f * std::max<float>(b.str, 1.f);
843 auto da = province::sorting_distance(state, pa, closest_prov) + as;
844 auto db = province::sorting_distance(state, pb, closest_prov) + bs;
845 if(da != db)
846 return da < db;
847 else
848 return a.a.index() < b.a.index();
849 });
850
851 for(uint32_t i = 0; i < rebel_hunters.size(); ++i) {
852 auto a = rebel_hunters[i].a;
853 if(state.world.army_get_location_from_army_location(a) == closest_prov) {
854 state.world.army_get_path(a).clear();
855 state.world.army_set_arrival_time(a, sys::date{});
856
857 rebel_hunters[i] = rebel_hunters.back();
858 rebel_hunters.pop_back();
859 break;
860 } else if(auto path = province::make_land_path(state, state.world.army_get_location_from_army_location(a), closest_prov, faction_owner, a); path.size() > 0) {
861 auto existing_path = state.world.army_get_path(a);
862 auto new_size = uint32_t(path.size());
863 existing_path.resize(new_size);
864 for(uint32_t j = 0; j < new_size; j++) {
865 existing_path.at(j) = path[j];
866 }
867 state.world.army_set_arrival_time(a, military::arrival_time_to(state, a, path.back()));
868 state.world.army_set_dig_in(a, 0);
869
870 rebel_hunters[i] = rebel_hunters.back();
871 rebel_hunters.pop_back();
872 break;
873 }
874 }
875 rebel_provs[0] = rebel_provs.back();
876 rebel_provs.pop_back();
877 }
878 }
879 }
880
881 for(const auto a : state.world.in_army) {
882 if(a.get_is_rebel_hunter()
883 && !a.get_battle_from_army_battle_participation()
884 && !a.get_navy_from_army_transport()
885 && !a.get_arrival_time()
886 && a.get_location_from_army_location() != a.get_ai_province()
887 && a.get_location_from_army_location().get_province_control().get_nation() == a.get_location_from_army_location().get_province_ownership().get_nation())
888 {
889 if(auto path = province::make_land_path(state, a.get_location_from_army_location(), a.get_ai_province(), a.get_army_control().get_controller(), a); path.size() > 0) {
890 auto existing_path = state.world.army_get_path(a);
891 auto new_size = uint32_t(path.size());
892 existing_path.resize(new_size);
893 for(uint32_t j = 0; j < new_size; j++) {
894 existing_path.at(j) = path[j];
895 }
896 state.world.army_set_arrival_time(a, military::arrival_time_to(state, a, path.back()));
897 state.world.army_set_dig_in(a, 0);
898 } else {
899 state.world.army_set_ai_province(a, state.world.army_get_location_from_army_location(a));
900 }
901 }
902 }
903}
904
905inline constexpr float rebel_size_reduction = 0.20f;
906
908 static std::vector<dcon::army_id> new_armies;
909 new_armies.clear();
910
911 for(auto rf : state.world.in_rebel_faction) {
912 auto revolt_chance = get_faction_revolt_risk(state, rf);
913 auto rval = rng::get_random(state, uint32_t(rf.id.value));
914 float p_val = float(rval & 0xFFFF) / float(0x10000);
915 if(p_val < revolt_chance) {
916 auto const faction_owner = rf.get_ruler_from_rebellion_within();
917 auto const new_to_make = int32_t(float(get_faction_brigades_ready(state, rf)) * rebel_size_reduction);
918 auto counter = new_to_make;
919 if(new_to_make == 0)
920 continue;
921
922 /*
923 - When a rising happens, pops with at least define:MILITANCY_TO_JOIN_RISING will spawn faction-organization x
924 max-possible-supported-regiments, to a minimum of 1 (if any more regiments are possible).
925 */
926 for(auto pop : rf.get_pop_rebellion_membership()) {
927 if(counter == 0)
928 break;
929
930 if(pop_demographics::get_militancy(state, pop.get_pop()) >= state.defines.mil_to_join_rising) {
931 auto location = pop.get_pop().get_province_from_pop_location();
932
933 // this is the logic we would use if we were creating rebel regiments
934 auto max_count = int32_t(state.world.pop_get_size(pop.get_pop()) * rebel_size_reduction / (province::is_overseas(state, pop.get_pop().get_province_from_pop_location()) ? (state.defines.pop_min_size_for_regiment_colony_multiplier * state.defines.pop_size_per_regiment) : state.defines.pop_size_per_regiment));
935 auto cregs = pop.get_pop().get_regiment_source();
936 auto used_count = int32_t(cregs.end() - cregs.begin());
937
938 if(used_count < max_count) {
939 auto pop_location = pop.get_pop().get_province_from_pop_location();
940
941 auto new_reg = military::create_new_regiment(state, dcon::nation_id{}, state.military_definitions.irregular);
942 auto a = [&]() {
943 for(auto ar : state.world.province_get_army_location(pop_location)) {
944 if(!(ar.get_army().get_battle_from_army_battle_participation()) && ar.get_army().get_controller_from_army_rebel_control() == rf)
945 return ar.get_army().id;
946 }
947 auto new_army = fatten(state.world, state.world.create_army());
948 new_army.set_controller_from_army_rebel_control(rf);
949 new_army.set_location_from_army_location(pop_location);
950 new_armies.push_back(new_army);
951 return new_army.id;
952 }();
953 state.world.try_create_army_membership(new_reg, a);
954 state.world.try_create_regiment_source(new_reg, pop.get_pop());
955
956 --counter;
957 }
958 }
959 }
960
961 /*
962 - Faction organization is reduced to 0 after an initial rising (for later contributory risings, it may instead be reduced by
963 a factor of (number-of-additional-regiments x 0.01 + 1))
964 */
965 rf.set_organization(0);
966 if(counter != new_to_make) {
968 [reb = rf.id](sys::state& state, text::layout_base& contents) {
969 auto rn = rebel_name(state, reb);
970 text::add_line(state, contents, "msg_revolt_1", text::variable_type::x, std::string_view{ rn });
971 ankerl::unordered_dense::map<int32_t, int32_t> provs;
972 for(auto ar : state.world.rebel_faction_get_army_rebel_control(reb)) {
973 if(ar.get_controller() == reb) {
974 auto p = ar.get_army().get_location_from_army_location();
975 provs[p.id.index()] += 1;
976 }
977 }
978 for(auto p : provs) {
979 auto box = text::open_layout_box(contents, 15);
980 text::add_to_layout_box(state, contents, box, dcon::province_id(dcon::province_id::value_base_t(p.first)));
981 text::add_to_layout_box(state, contents, box, std::string_view(" ("));
982 text::add_to_layout_box(state, contents, box, text::int_wholenum{ p.second });
983 text::add_to_layout_box(state, contents, box, std::string_view(")"));
984 text::close_layout_box(contents, box);
985 }
986 },
987 "msg_revolt_title",
988 rf.get_ruler_from_rebellion_within(), dcon::nation_id{}, dcon::nation_id{},
990 }
991 }
992 }
993
994 for(auto a : new_armies) {
995 if(!state.world.army_get_battle_from_army_battle_participation(a))
996 military::army_arrives_in_province(state, a, state.world.army_get_location_from_army_location(a), military::crossing_type::none);
997 }
998}
999
1000bool sphere_member_has_ongoing_revolt(sys::state& state, dcon::nation_id n) {
1001 auto nation_count = state.world.nation_size();
1002 for(uint32_t i = 0; i < nation_count; ++i) {
1003 dcon::nation_id m{dcon::nation_id::value_base_t(i)};
1004 if(state.world.nation_get_in_sphere_of(m) == n) {
1005 for(auto fac : state.world.nation_get_rebellion_within(m)) {
1006 if(get_faction_brigades_active(state, fac.get_rebels()) > 0)
1007 return true;
1008 }
1009 }
1010 }
1011 return false;
1012}
1013
1014int32_t get_faction_brigades_ready(sys::state& state, dcon::rebel_faction_id r) {
1015 return state.world.rebel_faction_get_possible_regiments(r) - get_faction_brigades_active(state, r);
1016}
1017
1018int32_t get_faction_brigades_active(sys::state& state, dcon::rebel_faction_id r) {
1019 int32_t total = 0;
1020 for(auto ar : state.world.rebel_faction_get_army_rebel_control(r)) {
1021 auto regs = ar.get_army().get_army_membership();
1022 total += int32_t(regs.end() - regs.begin());
1023 }
1024 return total;
1025}
1026
1027float get_faction_organization(sys::state& state, dcon::rebel_faction_id r) {
1028 return state.world.rebel_faction_get_organization(r);
1029}
1030
1031float get_faction_revolt_risk(sys::state& state, dcon::rebel_faction_id r) {
1032 /*
1033 - Rebels have a chance to rise once per month. The probability the rising will happen is: faction-organization x 0.05 + 0.02 +
1034 faction-organization x number-of-regiments-the-rising-could-form / 1v(number-of-regiments-controlled-by-nation x 20)
1035 */
1036 auto nation_brigades =
1037 std::max(1, int32_t(state.world.nation_get_active_regiments(state.world.rebel_faction_get_ruler_from_rebellion_within(r))));
1038
1039 auto num_brigades_ready = get_faction_brigades_ready(state, r);
1040 auto faction_org = state.world.rebel_faction_get_organization(r);
1041
1042 if(num_brigades_ready <= 0) {
1043 return 0.0f;
1044 }
1045
1046 float chance = std::clamp(
1047 faction_org * 0.05f + 0.02f + faction_org * float(num_brigades_ready) / (float(nation_brigades) * 20.0f), 0.0f, 1.0f);
1048 return chance;
1049}
1050
1052 province::for_each_land_province(state, [&](dcon::province_id p) {
1053 auto reb_controller = state.world.province_get_rebel_faction_from_province_rebel_control(p);
1054 auto owner = state.world.province_get_nation_from_province_ownership(p);
1055 if(reb_controller && owner) {
1056 assert(!bool(state.world.province_get_nation_from_province_control(p)));
1057 auto reb_type = state.world.rebel_faction_get_type(reb_controller);
1058 culture::rebel_defection def_type = culture::rebel_defection(reb_type.get_defection());
1059 if(def_type != culture::rebel_defection::none && state.world.province_get_last_control_change(p) + reb_type.get_defect_delay() * 31 <= state.current_date) {
1060 // defection time
1061
1062 dcon::nation_id defection_tag = [&]() {
1063 switch(def_type) {
1065 // prefer existing tag of same ideology
1066 for(auto c : state.world.province_get_core(p)) {
1067 auto holder = c.get_identity().get_nation_from_identity_holder();
1068 if(holder.get_ruling_party().get_ideology() == reb_type.get_ideology() && holder.get_owned_province_count() != 0) {
1069 return c.get_identity().get_nation_from_identity_holder().id;
1070 }
1071 }
1072 // otherwise pick a non-existent tag
1073 for(auto c : state.world.province_get_core(p)) {
1074 auto holder = c.get_identity().get_nation_from_identity_holder();
1075 if(!c.get_identity().get_is_not_releasable() && holder.get_owned_province_count() == 0) {
1076 auto t = c.get_identity().get_nation_from_identity_holder().id;
1078 politics::force_nation_ideology(state, t, reb_type.get_ideology());
1079 return t;
1080 }
1081 }
1082 break;
1083 }
1085 // prefer existing
1086 for(auto c : state.world.province_get_core(p)) {
1087 auto holder = c.get_identity().get_nation_from_identity_holder();
1088 if(holder.get_religion() == state.world.rebel_faction_get_religion(reb_controller) && holder.get_owned_province_count() != 0) {
1089 return c.get_identity().get_nation_from_identity_holder().id;
1090 }
1091 }
1092 // otherwise pick a non-existent tag
1093 for(auto c : state.world.province_get_core(p)) {
1094 auto holder = c.get_identity().get_nation_from_identity_holder();
1095 if(!c.get_identity().get_is_not_releasable() && c.get_identity().get_religion() == state.world.rebel_faction_get_religion(reb_controller) && holder.get_owned_province_count() == 0) {
1096 auto t = c.get_identity().get_nation_from_identity_holder().id;
1098 return t;
1099 }
1100 }
1101 break;
1103 // prefer existing
1104 for(auto c : state.world.province_get_core(p)) {
1105 auto holder = c.get_identity().get_nation_from_identity_holder();
1106 if(c.get_identity().get_primary_culture() == state.world.rebel_faction_get_primary_culture(reb_controller) && holder.get_owned_province_count() != 0) {
1107 return c.get_identity().get_nation_from_identity_holder().id;
1108 }
1109 }
1110 // otherwise pick a non-existent tag
1111 for(auto c : state.world.province_get_core(p)) {
1112 auto holder = c.get_identity().get_nation_from_identity_holder();
1113 if(!c.get_identity().get_is_not_releasable() && c.get_identity().get_primary_culture() == state.world.rebel_faction_get_primary_culture(reb_controller) && holder.get_owned_province_count() == 0) {
1114 auto t = c.get_identity().get_nation_from_identity_holder().id;
1116 return t;
1117 }
1118 }
1119 break;
1121 // prefer existing
1122 for(auto c : state.world.province_get_core(p)) {
1123 auto holder = c.get_identity().get_nation_from_identity_holder();
1124 if(c.get_identity().get_primary_culture().get_group_from_culture_group_membership() == state.world.rebel_faction_get_primary_culture_group(reb_controller) && holder.get_owned_province_count() != 0) {
1125 return c.get_identity().get_nation_from_identity_holder().id;
1126 }
1127 }
1128 // otherwise pick a non-existent tag
1129 for(auto c : state.world.province_get_core(p)) {
1130 auto holder = c.get_identity().get_nation_from_identity_holder();
1131 if(!c.get_identity().get_is_not_releasable()
1132 && c.get_identity().get_primary_culture().get_group_from_culture_group_membership() == state.world.rebel_faction_get_primary_culture_group(reb_controller)
1133 && holder.get_owned_province_count() == 0) {
1134
1135 auto t = c.get_identity().get_nation_from_identity_holder().id;
1137 return t;
1138 }
1139 }
1140
1141 // first: existing tag of culture; then existing tag of culture group;
1142 // then dead tag of culture, then dead tag of cg
1143 break;
1145 // union tag or nothing
1146 for(auto c : state.world.province_get_core(p)) {
1147 auto holder = c.get_identity().get_nation_from_identity_holder();
1148 if(c.get_identity() == state.world.rebel_faction_get_defection_target(reb_controller) && holder.get_owned_province_count() != 0) {
1149 return holder.id;
1150 }
1151 }
1152 break;
1153 default: // NONE and ANY
1154 return dcon::nation_id{};
1155 }
1156 return dcon::nation_id{};
1157 }();
1158
1159 if(defection_tag) {
1160 // TODO: if rebel armies were still a thing, you would probably want to delete the ones that did
1161 // the work of causing the province to defect or to send them back to the original country or something
1162 // but ...
1163 province::change_province_owner(state, p, defection_tag);
1164 }
1165 }
1166 }
1167 });
1168}
1169
1170float get_suppression_point_cost(sys::state& state, dcon::movement_id m) {
1171 /*
1172 Depends on whether define:POPULATION_SUPPRESSION_FACTOR is non zero. If it is zero, suppression costs their effective
1173 radicalism + 1. If it is non zero, then the cost is the greater of that value and the movements effective radicalism x the
1174 movement's support / define:POPULATION_SUPPRESSION_FACTOR
1175 */
1176 if(state.defines.population_suppression_factor > 0.0f) {
1177 return std::max(state.world.movement_get_radicalism(m) + 1.0f, state.world.movement_get_radicalism(m) *
1178 state.world.movement_get_pop_support(m) / state.defines.population_suppression_factor);
1179 } else {
1180 return state.world.movement_get_radicalism(m) + 1.0f;
1181 }
1182}
1183
1185 for(uint32_t i = state.world.rebel_faction_size(); i-- > 0;) {
1186 auto reb = dcon::rebel_faction_id{dcon::rebel_faction_id::value_base_t(i)};
1187 auto within = state.world.rebel_faction_get_ruler_from_rebellion_within(reb);
1188 auto is_active = get_faction_brigades_active(state, reb) > 0;
1189 auto enforce_trigger = state.world.rebel_faction_get_type(reb).get_demands_enforced_trigger();
1190 if(is_active && enforce_trigger &&
1191 trigger::evaluate(state, enforce_trigger, trigger::to_generic(within), trigger::to_generic(within),
1192 trigger::to_generic(reb))) {
1193 // rebel victory
1194
1195 /*
1196 The government type of the nation will change if the rebel type has an associated government (with the same logic for
1197 a government type change from a wargoal or other cause).
1198 */
1199
1200 auto old_gov = state.world.nation_get_government_type(within);
1201 assert(old_gov);
1202 auto new_gov = state.world.rebel_faction_get_type(reb).get_government_change(old_gov);
1203 if(new_gov) {
1204 politics::change_government_type(state, within, new_gov);
1205 }
1206 if(auto iid = state.world.rebel_faction_get_type(reb).get_ideology(); iid) {
1207 politics::force_nation_ideology(state, within, iid);
1208 }
1209 //The pops won, reset their militancy to avoid death spiraling
1210 for(auto members : state.world.rebel_faction_get_pop_rebellion_membership(reb)) {
1211 pop_demographics::set_militancy(state, members.get_pop().id, std::max(pop_demographics::get_militancy(state, members.get_pop()) - 8.0f, 0.0f));
1212 }
1213
1214 /*
1215 If the rebel type has "break alliances on win" then the nation loses all of its alliances, all of its non-substate
1216 vassals, all of its sphere members, and loses all of its influence and has its influence level set to neutral.
1217 */
1218
1219 if(state.world.rebel_faction_get_type(reb).get_break_alliance_on_win()) {
1221 }
1222
1223 /*
1224 The nation loses prestige equal to define:PRESTIGE_HIT_ON_BREAK_COUNTRY x (nation's current prestige + permanent
1225 prestige), which is multiplied by the nation's prestige modifier from technology + 1 as usual (!).
1226 */
1227 auto ploss = state.defines.prestige_hit_on_break_country * nations::prestige_score(state, within);
1228 nations::adjust_prestige(state, within, ploss);
1229
1230
1231 /*
1232 If it wins, it gets its `demands_enforced_effect` executed.
1233 */
1234
1235 if(auto k = state.world.rebel_faction_get_type(reb).get_demands_enforced_effect(); k)
1237 uint32_t(state.current_date.value), uint32_t(within.index() ^ (reb.index() << 4)));
1238
1240 [type = state.world.rebel_faction_get_type(reb).id, ploss, new_gov, within, when = state.current_date, i = reb.index()](sys::state& state, text::layout_base& contents) {
1241
1242 text::add_line(state, contents, "msg_rebels_win_1", text::variable_type::x, state.world.rebel_type_get_title(type));
1243 text::add_line(state, contents, "msg_rebels_win_2", text::variable_type::x, text::fp_one_place{ploss});
1244 if(new_gov) {
1245 text::add_line(state, contents, "msg_rebels_win_3", text::variable_type::x, state.world.government_type_get_name(new_gov));
1246 }
1247 if(auto iid = state.world.rebel_type_get_ideology(type); iid) {
1248 text::add_line(state, contents, "msg_rebels_win_4", text::variable_type::x, state.world.ideology_get_name(iid));
1249 }
1250 if(state.world.rebel_type_get_break_alliance_on_win(type)) {
1251 text::add_line(state, contents, "msg_rebels_win_5");
1252 }
1253 if(auto k = state.world.rebel_type_get_demands_enforced_effect(type); k) {
1254 text::add_line(state, contents, "msg_rebels_win_6");
1255 ui::effect_description(state, contents, k, trigger::to_generic(within), trigger::to_generic(within), -1, uint32_t(when.value), uint32_t(within.index() ^ (i << 4)));
1256 }
1257 },
1258 "msg_rebels_win_title",
1259 within, dcon::nation_id{}, dcon::nation_id{},
1261 });
1262
1263 /*
1264 Any units for the faction that exist are destroyed (or transferred if it is one of the special rebel types).
1265 */
1266 delete_faction(state, reb);
1267 }
1268 }
1269}
1270
1271void trigger_revolt(sys::state& state, dcon::nation_id n, dcon::rebel_type_id t, dcon::ideology_id i, dcon::culture_id c,
1272 dcon::religion_id r) {
1273 // TODO
1274}
1275
1276std::string rebel_name(sys::state& state, dcon::rebel_faction_id reb) {
1278
1279 auto culture = state.world.rebel_faction_get_primary_culture(reb);
1280 auto religion = state.world.rebel_faction_get_religion(reb);
1281 auto defection_target = state.world.rebel_faction_get_defection_target(reb);
1282 auto in_nation = state.world.rebel_faction_get_ruler_from_rebellion_within(reb);
1283 auto rebel_adj = text::get_adjective(state, in_nation);
1284 auto adjective = defection_target.get_adjective();
1285
1288
1289 if(culture) {
1291 } else {
1292 text::add_to_substitution_map(sub, text::variable_type::culture, state.world.nation_get_primary_culture(in_nation).get_name());
1293 }
1294
1295 if(religion) {
1297 }
1298
1299 if(defection_target) {
1302 } else {
1303 text::add_to_substitution_map(sub, text::variable_type::union_adj, state.world.nation_get_primary_culture(in_nation).get_group_from_culture_group_membership().get_identity_from_cultural_union_of().get_adjective());
1304 }
1305
1306 return text::resolve_string_substitution(state, state.world.rebel_faction_get_type(reb).get_name(), sub);
1307}
1308
1309bool allow_in_area(sys::state& state, dcon::province_id p, dcon::rebel_faction_id reb) {
1310 auto rf = dcon::fatten(state.world, reb);
1311 auto prov = dcon::fatten(state.world, p);
1312 switch(culture::rebel_area(rf.get_type().get_area())) {
1314 return true;
1316 return prov.get_dominant_culture() == rf.get_primary_culture();
1318 return prov.get_dominant_culture().get_culture_group_membership().get_group() == rf.get_primary_culture_group();
1320 return prov.get_dominant_religion() == rf.get_religion();
1322 return prov.get_dominant_religion() == rf.get_defection_target().get_religion() && prov.get_province_ownership().get_nation() == rf.get_rebellion_within().get_ruler();
1324 return prov.get_dominant_culture() == rf.get_defection_target().get_primary_culture() && prov.get_province_ownership().get_nation() == rf.get_rebellion_within().get_ruler();
1326 return prov.get_province_ownership().get_nation() == rf.get_rebellion_within().get_ruler();
1327 default:
1328 break;
1329 }
1330 return false;
1331}
1332
1334 concurrency::parallel_for(uint32_t(0), state.world.army_rebel_control_size(), [&](uint32_t i) {
1335 auto ar_reb_control = dcon::army_rebel_control_id{ dcon::army_rebel_control_id::value_base_t(i) };
1336 if(!state.world.army_rebel_control_is_valid(ar_reb_control))
1337 return;
1338 auto arc = dcon::fatten(state.world, ar_reb_control);
1339 auto ar = arc.get_army();
1340 if(!ar.get_army_rebel_control().get_controller()) /* Not a rebel army */
1341 return;
1342 if(ar.get_arrival_time() != sys::date{}) /* Do not interrupt travel */
1343 return;
1344 if(ar.get_battle_from_army_battle_participation()) /* In battle */
1345 return;
1346 if(ar.get_navy_from_army_transport()) /* Or in naval transport... */
1347 return;
1348
1349 auto type = arc.get_controller().get_type();
1350 auto area = arc.get_controller().get_type().get_area();
1351 auto location = ar.get_location_from_army_location();
1352 /* If on an unsieged province, siege it! */
1353 if(location.get_nation_from_province_control() && !location.get_rebel_faction_from_province_rebel_control()) {
1354 return;
1355 }
1356 dcon::province_fat_id best_prov = location;
1357 float best_weight = 0.f;// trigger::evaluate_multiplicative_modifier(state, type.get_movement_evaluation(), trigger::to_generic(best_prov), trigger::to_generic(best_prov), 0);;
1358 for(const auto adj : location.get_province_adjacency()) {
1359 auto indx = adj.get_connected_provinces(0) != location.id ? 0 : 1;
1360 auto prov = adj.get_connected_provinces(indx);
1361 /* sea province */
1362 if(prov.id.index() >= state.province_definitions.first_sea_province.index())
1363 continue;
1364 /* impassable */
1365 if((adj.get_type() & province::border::impassible_bit) != 0)
1366 continue;
1367 if(allow_in_area(state, prov, arc.get_controller())) {
1368 //float weight = trigger::evaluate_multiplicative_modifier(state, type.get_movement_evaluation(), trigger::to_generic(prov), trigger::to_generic(prov), trigger::to_generic(arc.get_controller()));
1369 float weight = float(rng::get_random(state, uint32_t(prov.id.index() * ar.id.index())) % 100);
1370 //if(prov.get_army_location().begin() != prov.get_army_location().end()) {
1371 // weight *= 0.01f;
1372 //}
1373 if(prov.get_rebel_faction_from_province_rebel_control()) {
1374 weight *= 0.0001f;
1375 }
1376 if(weight >= best_weight) {
1377 best_weight = weight;
1378 best_prov = prov;
1379 }
1380 }
1381 }
1382 if(best_prov != location) {
1383 ar.get_path().resize(1);
1384 ar.get_path()[0] = best_prov;
1385 ar.set_arrival_time(military::arrival_time_to(state, ar.id, best_prov));
1386 ar.set_dig_in(0);
1387 ar.set_is_rebel_hunter(false);
1388 }
1389 });
1390}
1391
1392} // namespace rebel
#define assert(condition)
Definition: debug.h:74
float estimate_army_offensive_strength(sys::state &state, dcon::army_id a)
Definition: ai.cpp:5164
float estimate_rebel_strength(sys::state &state, dcon::province_id p)
Definition: ai.cpp:6066
rebel_defection
Definition: culture.hpp:180
rebel_independence
Definition: culture.hpp:181
pop_satisfaction_wrapper_fat fatten(data_container const &c, pop_satisfaction_wrapper_id id) noexcept
void execute(sys::state &state, dcon::effect_key key, int32_t primary, int32_t this_slot, int32_t from_slot, uint32_t r_lo, uint32_t r_hi)
Definition: effects.cpp:5413
void army_arrives_in_province(sys::state &state, dcon::army_id a, dcon::province_id p, crossing_type crossing, dcon::land_battle_id from)
Definition: military.cpp:4387
bool rebel_army_in_province(sys::state &state, dcon::province_id p)
Definition: military.cpp:7775
sys::date arrival_time_to(sys::state &state, dcon::army_id a, dcon::province_id p)
Definition: military.cpp:4284
dcon::regiment_id create_new_regiment(sys::state &state, dcon::nation_id n, dcon::unit_type_id t)
Definition: military.cpp:2306
void adjust_prestige(sys::state &state, dcon::nation_id n, float delta)
Definition: nations.cpp:1900
void destroy_diplomatic_relationships(sys::state &state, dcon::nation_id n)
Definition: nations.cpp:1918
float prestige_score(sys::state const &state, dcon::nation_id n)
Definition: nations.cpp:961
void create_nation_based_on_template(sys::state &state, dcon::nation_id n, dcon::nation_id base)
Definition: nations.cpp:1672
dcon::nation_id owner_of_pop(sys::state const &state, dcon::pop_id pop_ids)
Definition: nations.cpp:87
void post(sys::state &state, message &&m)
void force_nation_ideology(sys::state &state, dcon::nation_id n, dcon::ideology_id id)
Definition: politics.cpp:422
void change_government_type(sys::state &state, dcon::nation_id n, dcon::government_type_id new_type)
Definition: politics.cpp:441
void set_militancy(sys::state &state, dcon::pop_id p, float v)
void set_consciousness(sys::state &state, dcon::pop_id p, float v)
dcon::pop_demographics_key to_key(sys::state const &state, dcon::ideology_id v)
float get_literacy(sys::state const &state, dcon::pop_id p)
float get_consciousness(sys::state const &state, dcon::pop_id p)
float get_militancy(sys::state const &state, dcon::pop_id p)
float get_demo(sys::state const &state, dcon::pop_id p, dcon::pop_demographics_key k)
std::vector< dcon::province_id > make_land_path(sys::state &state, dcon::province_id start, dcon::province_id end, dcon::nation_id nation_as, dcon::army_id a)
Definition: province.cpp:1863
bool is_overseas(sys::state const &state, dcon::province_id ids)
Definition: province.cpp:19
void for_each_land_province(sys::state &state, F const &func)
void set_province_controller(sys::state &state, dcon::province_id p, dcon::nation_id n)
Definition: province.cpp:157
void change_province_owner(sys::state &state, dcon::province_id id, dcon::nation_id new_owner)
Definition: province.cpp:716
Definition: rebels.cpp:12
dcon::rebel_faction_id get_faction_by_type(sys::state &state, dcon::nation_id n, dcon::rebel_type_id r)
Definition: rebels.cpp:29
bool pop_is_compatible_with_rebel_faction(sys::state &state, dcon::pop_id p, dcon::rebel_faction_id t)
Definition: rebels.cpp:376
void execute_province_defections(sys::state &state)
Definition: rebels.cpp:1051
void add_pop_to_rebel_faction(sys::state &state, dcon::pop_id p, dcon::rebel_faction_id m)
Definition: rebels.cpp:347
void trigger_revolt(sys::state &state, dcon::nation_id n, dcon::rebel_type_id t, dcon::ideology_id i, dcon::culture_id c, dcon::religion_id r)
Definition: rebels.cpp:1271
int32_t get_faction_brigades_active(sys::state &state, dcon::rebel_faction_id r)
Definition: rebels.cpp:1018
void delete_faction(sys::state &state, dcon::rebel_faction_id reb)
Definition: rebels.cpp:713
bool issue_is_valid_for_movement(sys::state &state, dcon::nation_id nation_within, dcon::issue_option_id i)
Definition: rebels.cpp:158
bool movement_is_valid(sys::state &state, dcon::movement_id m)
Definition: rebels.cpp:174
constexpr float org_gain_factor
Definition: rebels.cpp:736
void rebel_risings_check(sys::state &state)
Definition: rebels.cpp:907
bool sphere_member_has_ongoing_revolt(sys::state &state, dcon::nation_id n)
Definition: rebels.cpp:1000
float get_faction_organization(sys::state &state, dcon::rebel_faction_id r)
Definition: rebels.cpp:1027
constexpr float rebel_size_reduction
Definition: rebels.cpp:905
void update_movement_values(sys::state &state)
Definition: rebels.cpp:37
void update_armies(sys::state &state)
Definition: rebels.cpp:1333
void get_hunting_targets(sys::state &state, dcon::nation_id n, std::vector< impl::prov_str > &rebel_provs)
Definition: rebels.cpp:785
std::string rebel_name(sys::state &state, dcon::rebel_faction_id reb)
Definition: rebels.cpp:1276
void update_factions(sys::state &state)
Definition: rebels.cpp:722
void suppress_movement(sys::state &state, dcon::nation_id n, dcon::movement_id m)
Definition: rebels.cpp:129
void remove_pop_from_movement(sys::state &state, dcon::pop_id p)
Definition: rebels.cpp:120
dcon::movement_id get_movement_by_independence(sys::state &state, dcon::nation_id n, dcon::national_identity_id i)
Definition: rebels.cpp:21
float get_faction_revolt_risk(sys::state &state, dcon::rebel_faction_id r)
Definition: rebels.cpp:1031
bool allow_in_area(sys::state &state, dcon::province_id p, dcon::rebel_faction_id reb)
Definition: rebels.cpp:1309
void remove_pop_from_rebel_faction(sys::state &state, dcon::pop_id p)
Definition: rebels.cpp:339
float get_suppression_point_cost(sys::state &state, dcon::movement_id m)
Definition: rebels.cpp:1170
void execute_rebel_victories(sys::state &state)
Definition: rebels.cpp:1184
void sort_hunting_targets(sys::state &state, impl::arm_str const &ar, std::vector< impl::prov_str > &rebel_provs)
Definition: rebels.cpp:795
void add_pop_to_movement(sys::state &state, dcon::pop_id p, dcon::movement_id m)
Definition: rebels.cpp:114
void daily_update_rebel_organization(sys::state &state)
Definition: rebels.cpp:738
int32_t get_faction_brigades_ready(sys::state &state, dcon::rebel_faction_id r)
Definition: rebels.cpp:1014
bool pop_is_compatible_with_rebel_type(sys::state &state, dcon::pop_id p, dcon::rebel_type_id t)
Definition: rebels.cpp:415
void update_movements(sys::state &state)
Definition: rebels.cpp:318
void update_pop_movement_membership(sys::state &state)
Definition: rebels.cpp:194
bool rebel_faction_is_valid(sys::state &state, dcon::rebel_faction_id m)
Definition: rebels.cpp:354
void turn_movement_into_rebels(sys::state &state, dcon::movement_id m)
Definition: rebels.cpp:143
dcon::movement_id get_movement_by_position(sys::state &state, dcon::nation_id n, dcon::issue_option_id o)
Definition: rebels.cpp:14
void update_pop_rebel_membership(sys::state &state)
Definition: rebels.cpp:446
void rebel_hunting_check(sys::state &state)
Definition: rebels.cpp:810
uint32_t reduce(uint32_t value_in, uint32_t upper_bound)
Definition: prng.cpp:46
uint64_t get_random(sys::state const &state, uint32_t value_in)
Definition: prng.cpp:8
void add_to_layout_box(sys::state &state, layout_base &dest, layout_box &box, embedded_flag ico)
Definition: text.cpp:1165
std::string resolve_string_substitution(sys::state &state, dcon::text_key source_text, substitution_map const &mp)
Definition: text.cpp:2137
layout_box open_layout_box(layout_base &dest, int32_t indent)
Definition: text.cpp:1823
void add_line(sys::state &state, layout_base &dest, dcon::text_key txt, int32_t indent)
Definition: text.cpp:1923
void add_to_substitution_map(substitution_map &mp, variable_type key, substitution value)
Definition: text.cpp:1068
dcon::text_key get_adjective(sys::state &state, dcon::nation_id id)
Definition: text.cpp:890
ankerl::unordered_dense::map< uint32_t, substitution > substitution_map
Definition: text.hpp:797
void close_layout_box(columnar_layout &dest, layout_box &box)
Definition: text.cpp:1831
int32_t to_generic(dcon::province_id v)
Definition: triggers.hpp:12
bool evaluate(sys::state &state, dcon::trigger_key key, int32_t primary, int32_t this_slot, int32_t from_slot)
Definition: triggers.cpp:5895
float evaluate_multiplicative_modifier(sys::state &state, dcon::value_modifier_key modifier, int32_t primary, int32_t this_slot, int32_t from_slot)
Definition: triggers.cpp:5812
void effect_description(sys::state &state, text::layout_base &layout, dcon::effect_key k, int32_t primary_slot, int32_t this_slot, int32_t from_slot, uint32_t r_lo, uint32_t r_hi)
float to_float(int32_t a)
uint uint32_t
uchar uint8_t
dcon::army_id a
Definition: rebels.hpp:37
Holds important data about the game world, state, and other data regarding windowing,...