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 return true;
411}
412
413bool pop_is_compatible_with_rebel_type(sys::state& state, dcon::pop_id p, dcon::rebel_type_id t) {
414 auto fac = fatten(state.world, t);
415 auto pop = fatten(state.world, p);
416
417 if(fac.get_independence() != 0 || fac.get_defection() != 0) {
418 if(fac.get_independence() == uint8_t(culture::rebel_independence::pan_nationalist) ||
419 fac.get_defection() == uint8_t(culture::rebel_defection::pan_nationalist)) {
420 if(pop.get_is_primary_or_accepted_culture())
421 return true;
422 } else {
423 if(pop.get_is_primary_or_accepted_culture())
424 return false;
425 }
426 for(auto core : pop.get_province_from_pop_location().get_core()) {
427 if(core.get_identity().get_primary_culture() == pop.get_culture())
428 return true;
429 }
430 return false;
431 }
432 //Discriminated pops focus on independence rather than political issues
433 else {
434 if(!pop.get_is_primary_or_accepted_culture()) {
435 return false;
436 }
437 }
438 if(fac.get_ideology() && fac.get_ideology() != pop.get_dominant_ideology()) {
439 return false;
440 }
441 return true;
442}
443
445 state.world.for_each_pop([&](dcon::pop_id p) {
446 auto owner = nations::owner_of_pop(state, p);
447 // pops not in a nation can't be in a rebel faction
448 if(!owner)
449 return;
450
451 auto mil = pop_demographics::get_militancy(state, p);
452 auto existing_faction = state.world.pop_get_rebel_faction_from_pop_rebellion_membership(p);
453
454 // -Pops with define : MIL_TO_JOIN_REBEL will join a rebel_faction
455 if(mil >= state.defines.mil_to_join_rebel) {
456 if(existing_faction && !pop_is_compatible_with_rebel_faction(state, p, existing_faction)) {
458 } else {
459 auto prov = state.world.pop_get_province_from_pop_location(p);
460 /*
461 - A pop in a province sieged or controlled by rebels will join that faction, if the pop is compatible with the
462 faction.
463 */
464
465 auto occupying_faction = state.world.province_get_rebel_faction_from_province_rebel_control(prov);
466 if(occupying_faction && pop_is_compatible_with_rebel_faction(state, p, occupying_faction)) {
467 assert(!bool(state.world.province_get_nation_from_province_control(prov)));
468 add_pop_to_rebel_faction(state, p, occupying_faction);
469 } else {
470 /*
471 - Otherwise take all the compatible and possible rebel types. Determine the spawn chance for each of them, by
472 taking the *product* of the modifiers. The pop then joins the type with the greatest chance (that's right, it
473 isn't really a *chance* at all). If that type has a defection type, it joins the faction with the national
474 identity most compatible with it and that type (pan-nationalist go to the union tag, everyone else uses the
475 logic I outline below)
476 */
477 float greatest_chance = 0.0f;
478 dcon::rebel_faction_id f;
479 for(auto rf : state.world.nation_get_rebellion_within(owner)) {
480 if(pop_is_compatible_with_rebel_faction(state, p, rf.get_rebels())) {
481 auto chance = rf.get_rebels().get_type().get_spawn_chance();
483 trigger::to_generic(owner), trigger::to_generic(rf.get_rebels().id));
484 if(eval > greatest_chance) {
485 f = rf.get_rebels();
486 greatest_chance = eval;
487 }
488 }
489 }
490
491 dcon::rebel_faction_id temp = state.world.create_rebel_faction();
492 dcon::national_identity_id ind_tag = [&]() {
493 auto prov = state.world.pop_get_province_from_pop_location(p);
494 for(auto core : state.world.province_get_core(prov)) {
495 if(!core.get_identity().get_is_not_releasable() && core.get_identity().get_primary_culture() == state.world.pop_get_culture(p))
496 return core.get_identity().id;
497 }
498 return dcon::national_identity_id{};
499 }();
500
501 dcon::rebel_type_id max_type;
502
503 state.world.for_each_rebel_type([&](dcon::rebel_type_id rt) {
504 if(pop_is_compatible_with_rebel_type(state, p, rt)) {
505 state.world.rebel_faction_set_type(temp, rt);
506 state.world.rebel_faction_set_defection_target(temp, dcon::national_identity_id{});
507 state.world.rebel_faction_set_primary_culture(temp, dcon::culture_id{});
508 state.world.rebel_faction_set_primary_culture_group(temp, dcon::culture_group_id{});
509 state.world.rebel_faction_set_religion(temp, dcon::religion_id{});
510
511 switch(culture::rebel_defection(state.world.rebel_type_get_defection(rt))) {
513 state.world.rebel_faction_set_primary_culture(temp, state.world.pop_get_culture(p));
514 state.world.rebel_faction_set_defection_target(temp, ind_tag);
515 if(!ind_tag)
516 return; // skip -- no defection possible
517 if(state.world.pop_get_is_primary_or_accepted_culture(p))
518 return; // skip -- can't defect
519 break;
521 state.world.rebel_faction_set_primary_culture_group(temp,
522 state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p)));
523 state.world.rebel_faction_set_defection_target(temp, ind_tag);
524 if(!ind_tag)
525 return; // skip -- no defection possible
526 if(state.world.pop_get_is_primary_or_accepted_culture(p))
527 return; // skip -- can't defect
528 break;
530 state.world.rebel_faction_set_religion(temp, state.world.pop_get_religion(p));
531 state.world.rebel_faction_set_defection_target(temp, ind_tag);
532 if(!ind_tag)
533 return; // skip -- no defection possible
534 if(state.world.pop_get_is_primary_or_accepted_culture(p))
535 return; // skip -- can't defect
536 break;
538 auto cg = state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p));
539 auto u = state.world.culture_group_get_identity_from_cultural_union_of(cg);
540 if(!u)
541 return; // skip -- no pan nationalist possible
542 state.world.rebel_faction_set_defection_target(temp, u);
543 break;
544 }
546 state.world.rebel_faction_set_defection_target(temp, ind_tag);
547 if(!ind_tag)
548 return; // skip -- no defection possible
549 if(state.world.pop_get_is_primary_or_accepted_culture(p))
550 return; // skip -- can't defect
551 break;
552 default:
553 break;
554 }
555
556 switch(culture::rebel_independence(state.world.rebel_type_get_independence(rt))) {
558 state.world.rebel_faction_set_primary_culture(temp, state.world.pop_get_culture(p));
559 state.world.rebel_faction_set_defection_target(temp, ind_tag);
560 if(!ind_tag)
561 return; // skip -- no defection possible
562 if(state.world.pop_get_is_primary_or_accepted_culture(p))
563 return; // skip -- can't defect
564 break;
566 state.world.rebel_faction_set_primary_culture_group(temp,
567 state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p)));
568 state.world.rebel_faction_set_defection_target(temp, ind_tag);
569 if(!ind_tag)
570 return; // skip -- no defection possible
571 if(state.world.pop_get_is_primary_or_accepted_culture(p))
572 return; // skip -- can't defect
573 break;
575 state.world.rebel_faction_set_religion(temp, state.world.pop_get_religion(p));
576 state.world.rebel_faction_set_defection_target(temp, ind_tag);
577 if(!ind_tag)
578 return; // skip -- no defection possible
579 if(state.world.pop_get_is_primary_or_accepted_culture(p))
580 return; // skip -- can't defect
581 break;
583 auto cg = state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p));
584 auto u = state.world.culture_group_get_identity_from_cultural_union_of(cg);
585 if(!u)
586 return; // skip -- no pan nationalist possible
587 if(state.world.pop_get_is_primary_or_accepted_culture(p))
588 return; // skip -- can't defect
589 state.world.rebel_faction_set_defection_target(temp, u);
590 break;
591 }
593 state.world.rebel_faction_set_defection_target(temp, ind_tag);
594 if(!ind_tag)
595 return; // skip -- no defection possible
596 if(state.world.pop_get_is_primary_or_accepted_culture(p))
597 return; // skip -- can't defect
598 break;
600 state.world.rebel_faction_set_defection_target(temp, ind_tag);
601 if(!ind_tag)
602 return; // skip -- no defection possible
603 if(state.world.pop_get_is_primary_or_accepted_culture(p))
604 return; // skip -- can't defect
605 break;
606 default:
607 break;
608 }
609
610 auto chance = state.world.rebel_type_get_spawn_chance(rt);
613 if(eval > greatest_chance) {
614 f = temp;
615 max_type = rt;
616 greatest_chance = eval;
617 }
618 }
619 });
620
621 if(f != temp) {
622 state.world.delete_rebel_faction(temp);
623 } else {
624 auto rt = max_type;
625 state.world.rebel_faction_set_type(temp, rt);
626 state.world.rebel_faction_set_defection_target(temp, dcon::national_identity_id{});
627 state.world.rebel_faction_set_primary_culture(temp, dcon::culture_id{});
628 state.world.rebel_faction_set_primary_culture_group(temp, dcon::culture_group_id{});
629 state.world.rebel_faction_set_religion(temp, dcon::religion_id{});
630
631 switch(culture::rebel_defection(state.world.rebel_type_get_defection(rt))) {
633 state.world.rebel_faction_set_primary_culture(temp, state.world.pop_get_culture(p));
634 state.world.rebel_faction_set_defection_target(temp, ind_tag);
635 break;
637 state.world.rebel_faction_set_primary_culture_group(temp,
638 state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p)));
639 state.world.rebel_faction_set_defection_target(temp, ind_tag);
640 break;
642 state.world.rebel_faction_set_religion(temp, state.world.pop_get_religion(p));
643 state.world.rebel_faction_set_defection_target(temp, ind_tag);
644 break;
646 auto cg = state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p));
647 auto u = state.world.culture_group_get_identity_from_cultural_union_of(cg);
648 state.world.rebel_faction_set_defection_target(temp, u);
649 break;
650 }
652 state.world.rebel_faction_set_defection_target(temp, ind_tag);
653 break;
654 default:
655 break;
656 }
657
658 switch(culture::rebel_independence(state.world.rebel_type_get_independence(rt))) {
660 state.world.rebel_faction_set_primary_culture(temp, state.world.pop_get_culture(p));
661 state.world.rebel_faction_set_defection_target(temp, ind_tag);
662 break;
664 state.world.rebel_faction_set_primary_culture_group(temp,
665 state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p)));
666 state.world.rebel_faction_set_defection_target(temp, ind_tag);
667 break;
669 state.world.rebel_faction_set_religion(temp, state.world.pop_get_religion(p));
670 state.world.rebel_faction_set_defection_target(temp, ind_tag);
671 break;
673 auto cg = state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p));
674 auto u = state.world.culture_group_get_identity_from_cultural_union_of(cg);
675 state.world.rebel_faction_set_defection_target(temp, u);
676 break;
677 }
679 state.world.rebel_faction_set_defection_target(temp, ind_tag);
680 break;
682 state.world.rebel_faction_set_defection_target(temp, ind_tag);
683 break;
684 default:
685 break;
686 }
687
688 if(state.world.rebel_type_get_culture_restriction(rt) && !state.world.rebel_faction_get_primary_culture(temp)) {
689 state.world.rebel_faction_set_primary_culture(temp, state.world.pop_get_culture(p));
690 }
691 if(state.world.rebel_type_get_culture_group_restriction(rt) && !state.world.rebel_faction_get_primary_culture_group(temp)) {
692 state.world.rebel_faction_set_primary_culture_group(temp, state.world.culture_get_group_from_culture_group_membership(state.world.pop_get_culture(p)));
693 }
694
695 state.world.try_create_rebellion_within(temp, owner);
696 }
697
698 if(greatest_chance > 0) {
699 add_pop_to_rebel_faction(state, p, f);
700 }
701 }
702 }
703 } else { // less than: MIL_TO_JOIN_REBEL will join a rebel_faction -- leave faction
704 if(existing_faction) {
706 }
707 }
708 });
709}
710
711void delete_faction(sys::state& state, dcon::rebel_faction_id reb) {
712 auto control = state.world.rebel_faction_get_province_rebel_control(reb);
713 for(auto p : control) {
714 auto prov = p.get_province();
715 province::set_province_controller(state, prov, prov.get_nation_from_province_ownership());
716 }
717 state.world.delete_rebel_faction(reb);
718}
719
722 // IMPORTANT: we count down here so that we can delete as we go, compacting from the end
723 for(auto last = state.world.rebel_faction_size(); last-- > 0;) {
724 dcon::rebel_faction_id m{dcon::rebel_faction_id::value_base_t(last)};
725 if(!rebel_faction_is_valid(state, m)) {
726 for(auto members : state.world.rebel_faction_get_pop_rebellion_membership(m)) {
727 pop_demographics::set_militancy(state, members.get_pop().id, 0.0f);
728 }
729 delete_faction(state, m);
730 }
731 }
732}
733
734inline constexpr float org_gain_factor = 0.4f;
735
737 // update brigade count
738 state.world.for_each_rebel_faction([&](dcon::rebel_faction_id rf) {
739 int32_t total = 0;
740 for(auto pop : state.world.rebel_faction_get_pop_rebellion_membership(rf)) {
741 total += int32_t(state.world.pop_get_size(pop.get_pop()) / state.defines.pop_size_per_regiment);
742 }
743 state.world.rebel_faction_set_possible_regiments(rf, total);
744 });
745
746 // update org
747 state.world.for_each_rebel_faction([&](dcon::rebel_faction_id rf) {
748 /*
749 - 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
750 militancy is > define:MILITANCY_TO_AUTO_RISE and 5 if militancy is less than that but > define:MILITANCY_TO_JOIN_RISING) /
751 (1 + national-administration-spending-setting). Take this sum, multiply by 0.001 x (rebel organization from technology +
752 1) and divide by the number of regiments those pops could form. If positive, add this to the current organization of the
753 rebel faction (to a maximum of 1). This appears to be done daily.
754 */
755
756 float total_change = 0;
757 for(auto pop : state.world.rebel_faction_get_pop_rebellion_membership(rf)) {
758 auto mil_factor = [&]() {
759 auto m = pop_demographics::get_militancy(state, pop.get_pop());
760 if(m > state.defines.mil_to_autorise)
761 return 10.0f;
762 else if(m > state.defines.mil_to_join_rising)
763 return 5.0f;
764 return 1.0f;
765 }();
766 total_change += pop.get_pop().get_savings() * pop_demographics::get_literacy(state, pop.get_pop()) * mil_factor * org_gain_factor;
767 }
768 auto reg_count = state.world.rebel_faction_get_possible_regiments(rf);
769 auto within = state.world.rebel_faction_get_ruler_from_rebellion_within(rf);
770 auto rebel_org_mod = 1.0f + state.world.nation_get_rebel_org_modifier(within, state.world.rebel_faction_get_type(rf));
771
772 if(reg_count > 0) {
773 state.world.rebel_faction_set_organization(rf,
774 std::min(1.0f,
775 state.world.rebel_faction_get_organization(rf) +
776 total_change *
777 (1.0f + state.world.nation_get_rebel_org_modifier(within, state.world.rebel_faction_get_type(rf))) *
778 0.001f / (1.0f + state.world.nation_get_administrative_efficiency(within)) / float(reg_count)));
779 }
780 });
781}
782
783void get_hunting_targets(sys::state& state, dcon::nation_id n, std::vector<impl::prov_str>& rebel_provs) {
784 assert(rebel_provs.empty());
785 auto nat = dcon::fatten(state.world, n);
786 for(auto prov : nat.get_province_ownership()) {
787 if(prov.get_province().get_rebel_faction_from_province_rebel_control()
788 || military::rebel_army_in_province(state, prov.get_province()))
789 rebel_provs.push_back(impl::prov_str{ prov.get_province().id, ai::estimate_rebel_strength(state, prov.get_province()) });
790 }
791}
792
793void sort_hunting_targets(sys::state& state, impl::arm_str const& ar, std::vector<impl::prov_str>& rebel_provs) {
794 auto our_str = ar.str;
795 auto loc = state.world.army_get_location_from_army_location(ar.a);
796 std::sort(rebel_provs.begin(), rebel_provs.end(), [&](impl::prov_str const& a, impl::prov_str const& b) {
797 auto aa = 0.001f * -(our_str - a.str);
798 auto ab = 0.001f * -(our_str - b.str);
799 auto da = province::sorting_distance(state, a.p, loc) + aa;
800 auto db = province::sorting_distance(state, b.p, loc) + ab;
801 if(da != db)
802 return da < db;
803 else
804 return a.p.index() < b.p.index();
805 });
806}
807
809 for(auto rf : state.world.in_rebel_faction) {
810 auto const faction_owner = rf.get_ruler_from_rebellion_within();
811 // rebel hunting logic
812 if(faction_owner.get_is_player_controlled()) {
813 static std::vector<impl::arm_str> rebel_hunters;
814 rebel_hunters.clear();
815 for(auto ar : faction_owner.get_army_control()) {
816 auto loc = ar.get_army().get_location_from_army_location();
817 if(ar.get_army().get_is_rebel_hunter()
818 && !ar.get_army().get_battle_from_army_battle_participation()
819 && !ar.get_army().get_navy_from_army_transport()
820 && !ar.get_army().get_arrival_time()
821 && loc.get_nation_from_province_control() == faction_owner
822 ) {
823 rebel_hunters.push_back(impl::arm_str{ ar.get_army().id, ai::estimate_army_offensive_strength (state, ar.get_army()) });
824 }
825 }
826
827 static std::vector<impl::prov_str> rebel_provs;
828 rebel_provs.clear();
829 get_hunting_targets(state, faction_owner.id, rebel_provs);
830
831 while(rebel_provs.size() > 0 && rebel_hunters.size() > 0) {
832 auto rh = rebel_hunters[0];
833 sort_hunting_targets(state, rh, rebel_provs);
834
835 auto closest_prov = rebel_provs[0].p;
836 std::sort(rebel_hunters.begin(), rebel_hunters.end(), [&](impl::arm_str const& a, impl::arm_str const& b) {
837 auto pa = state.world.army_get_location_from_army_location(a.a);
838 auto pb = state.world.army_get_location_from_army_location(b.a);
839 auto as = 0.001f * std::max<float>(a.str, 1.f);
840 auto bs = 0.001f * std::max<float>(b.str, 1.f);
841 auto da = province::sorting_distance(state, pa, closest_prov) + as;
842 auto db = province::sorting_distance(state, pb, closest_prov) + bs;
843 if(da != db)
844 return da < db;
845 else
846 return a.a.index() < b.a.index();
847 });
848
849 for(uint32_t i = 0; i < rebel_hunters.size(); ++i) {
850 auto a = rebel_hunters[i].a;
851 if(state.world.army_get_location_from_army_location(a) == closest_prov) {
852 state.world.army_get_path(a).clear();
853 state.world.army_set_arrival_time(a, sys::date{});
854
855 rebel_hunters[i] = rebel_hunters.back();
856 rebel_hunters.pop_back();
857 break;
858 } 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) {
859 auto existing_path = state.world.army_get_path(a);
860 auto new_size = uint32_t(path.size());
861 existing_path.resize(new_size);
862 for(uint32_t j = 0; j < new_size; j++) {
863 existing_path.at(j) = path[j];
864 }
865 state.world.army_set_arrival_time(a, military::arrival_time_to(state, a, path.back()));
866 state.world.army_set_dig_in(a, 0);
867
868 rebel_hunters[i] = rebel_hunters.back();
869 rebel_hunters.pop_back();
870 break;
871 }
872 }
873 rebel_provs[0] = rebel_provs.back();
874 rebel_provs.pop_back();
875 }
876 }
877 }
878
879 for(const auto a : state.world.in_army) {
880 if(a.get_is_rebel_hunter()
881 && !a.get_battle_from_army_battle_participation()
882 && !a.get_navy_from_army_transport()
883 && !a.get_arrival_time()
884 && a.get_location_from_army_location() != a.get_ai_province()
885 && a.get_location_from_army_location().get_province_control().get_nation() == a.get_location_from_army_location().get_province_ownership().get_nation())
886 {
887 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) {
888 auto existing_path = state.world.army_get_path(a);
889 auto new_size = uint32_t(path.size());
890 existing_path.resize(new_size);
891 for(uint32_t j = 0; j < new_size; j++) {
892 existing_path.at(j) = path[j];
893 }
894 state.world.army_set_arrival_time(a, military::arrival_time_to(state, a, path.back()));
895 state.world.army_set_dig_in(a, 0);
896 } else {
897 state.world.army_set_ai_province(a, state.world.army_get_location_from_army_location(a));
898 }
899 }
900 }
901}
902
903inline constexpr float rebel_size_reduction = 0.20f;
904
906 static std::vector<dcon::army_id> new_armies;
907 new_armies.clear();
908
909 for(auto rf : state.world.in_rebel_faction) {
910 auto revolt_chance = get_faction_revolt_risk(state, rf);
911 auto rval = rng::get_random(state, uint32_t(rf.id.value));
912 float p_val = float(rval & 0xFFFF) / float(0x10000);
913 if(p_val < revolt_chance) {
914 auto const faction_owner = rf.get_ruler_from_rebellion_within();
915 auto const new_to_make = int32_t(float(get_faction_brigades_ready(state, rf)) * rebel_size_reduction);
916 auto counter = new_to_make;
917 if(new_to_make == 0)
918 continue;
919
920 /*
921 - When a rising happens, pops with at least define:MILITANCY_TO_JOIN_RISING will spawn faction-organization x
922 max-possible-supported-regiments, to a minimum of 1 (if any more regiments are possible).
923 */
924 for(auto pop : rf.get_pop_rebellion_membership()) {
925 if(counter == 0)
926 break;
927
928 if(pop_demographics::get_militancy(state, pop.get_pop()) >= state.defines.mil_to_join_rising) {
929 auto location = pop.get_pop().get_province_from_pop_location();
930
931 // this is the logic we would use if we were creating rebel regiments
932 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));
933 auto cregs = pop.get_pop().get_regiment_source();
934 auto used_count = int32_t(cregs.end() - cregs.begin());
935
936 if(used_count < max_count) {
937 auto pop_location = pop.get_pop().get_province_from_pop_location();
938
939 auto new_reg = military::create_new_regiment(state, dcon::nation_id{}, state.military_definitions.irregular);
940 auto a = [&]() {
941 for(auto ar : state.world.province_get_army_location(pop_location)) {
942 if(!(ar.get_army().get_battle_from_army_battle_participation()) && ar.get_army().get_controller_from_army_rebel_control() == rf)
943 return ar.get_army().id;
944 }
945 auto new_army = fatten(state.world, state.world.create_army());
946 new_army.set_controller_from_army_rebel_control(rf);
947 new_army.set_location_from_army_location(pop_location);
948 new_armies.push_back(new_army);
949 return new_army.id;
950 }();
951 state.world.try_create_army_membership(new_reg, a);
952 state.world.try_create_regiment_source(new_reg, pop.get_pop());
953
954 --counter;
955 }
956 }
957 }
958
959 /*
960 - Faction organization is reduced to 0 after an initial rising (for later contributory risings, it may instead be reduced by
961 a factor of (number-of-additional-regiments x 0.01 + 1))
962 */
963 rf.set_organization(0);
964 if(counter != new_to_make) {
966 [reb = rf.id](sys::state& state, text::layout_base& contents) {
967 auto rn = rebel_name(state, reb);
968 text::add_line(state, contents, "msg_revolt_1", text::variable_type::x, std::string_view{ rn });
969 ankerl::unordered_dense::map<int32_t, int32_t> provs;
970 for(auto ar : state.world.rebel_faction_get_army_rebel_control(reb)) {
971 if(ar.get_controller() == reb) {
972 auto p = ar.get_army().get_location_from_army_location();
973 provs[p.id.index()] += 1;
974 }
975 }
976 for(auto p : provs) {
977 auto box = text::open_layout_box(contents, 15);
978 text::add_to_layout_box(state, contents, box, dcon::province_id(dcon::province_id::value_base_t(p.first)));
979 text::add_to_layout_box(state, contents, box, std::string_view(" ("));
980 text::add_to_layout_box(state, contents, box, text::int_wholenum{ p.second });
981 text::add_to_layout_box(state, contents, box, std::string_view(")"));
982 text::close_layout_box(contents, box);
983 }
984 },
985 "msg_revolt_title",
986 rf.get_ruler_from_rebellion_within(), dcon::nation_id{}, dcon::nation_id{},
988 }
989 }
990 }
991
992 for(auto a : new_armies) {
993 if(!state.world.army_get_battle_from_army_battle_participation(a))
994 military::army_arrives_in_province(state, a, state.world.army_get_location_from_army_location(a), military::crossing_type::none);
995 }
996}
997
998bool sphere_member_has_ongoing_revolt(sys::state& state, dcon::nation_id n) {
999 auto nation_count = state.world.nation_size();
1000 for(uint32_t i = 0; i < nation_count; ++i) {
1001 dcon::nation_id m{dcon::nation_id::value_base_t(i)};
1002 if(state.world.nation_get_in_sphere_of(m) == n) {
1003 for(auto fac : state.world.nation_get_rebellion_within(m)) {
1004 if(get_faction_brigades_active(state, fac.get_rebels()) > 0)
1005 return true;
1006 }
1007 }
1008 }
1009 return false;
1010}
1011
1012int32_t get_faction_brigades_ready(sys::state& state, dcon::rebel_faction_id r) {
1013 return state.world.rebel_faction_get_possible_regiments(r) - get_faction_brigades_active(state, r);
1014}
1015
1016int32_t get_faction_brigades_active(sys::state& state, dcon::rebel_faction_id r) {
1017 int32_t total = 0;
1018 for(auto ar : state.world.rebel_faction_get_army_rebel_control(r)) {
1019 auto regs = ar.get_army().get_army_membership();
1020 total += int32_t(regs.end() - regs.begin());
1021 }
1022 return total;
1023}
1024
1025float get_faction_organization(sys::state& state, dcon::rebel_faction_id r) {
1026 return state.world.rebel_faction_get_organization(r);
1027}
1028
1029float get_faction_revolt_risk(sys::state& state, dcon::rebel_faction_id r) {
1030 /*
1031 - Rebels have a chance to rise once per month. The probability the rising will happen is: faction-organization x 0.05 + 0.02 +
1032 faction-organization x number-of-regiments-the-rising-could-form / 1v(number-of-regiments-controlled-by-nation x 20)
1033 */
1034 auto nation_brigades =
1035 std::max(1, int32_t(state.world.nation_get_active_regiments(state.world.rebel_faction_get_ruler_from_rebellion_within(r))));
1036
1037 auto num_brigades_ready = get_faction_brigades_ready(state, r);
1038 auto faction_org = state.world.rebel_faction_get_organization(r);
1039
1040 if(num_brigades_ready <= 0) {
1041 return 0.0f;
1042 }
1043
1044 float chance = std::clamp(
1045 faction_org * 0.05f + 0.02f + faction_org * float(num_brigades_ready) / (float(nation_brigades) * 20.0f), 0.0f, 1.0f);
1046 return chance;
1047}
1048
1050 province::for_each_land_province(state, [&](dcon::province_id p) {
1051 auto reb_controller = state.world.province_get_rebel_faction_from_province_rebel_control(p);
1052 auto owner = state.world.province_get_nation_from_province_ownership(p);
1053 if(reb_controller && owner) {
1054 assert(!bool(state.world.province_get_nation_from_province_control(p)));
1055 auto reb_type = state.world.rebel_faction_get_type(reb_controller);
1056 culture::rebel_defection def_type = culture::rebel_defection(reb_type.get_defection());
1057 if(def_type != culture::rebel_defection::none && state.world.province_get_last_control_change(p) + reb_type.get_defect_delay() * 31 <= state.current_date) {
1058 // defection time
1059
1060 dcon::nation_id defection_tag = [&]() {
1061 switch(def_type) {
1063 // prefer existing tag of same ideology
1064 for(auto c : state.world.province_get_core(p)) {
1065 auto holder = c.get_identity().get_nation_from_identity_holder();
1066 if(holder.get_ruling_party().get_ideology() == reb_type.get_ideology() && holder.get_owned_province_count() != 0) {
1067 return c.get_identity().get_nation_from_identity_holder().id;
1068 }
1069 }
1070 // otherwise pick a non-existent tag
1071 for(auto c : state.world.province_get_core(p)) {
1072 auto holder = c.get_identity().get_nation_from_identity_holder();
1073 if(!c.get_identity().get_is_not_releasable() && holder.get_owned_province_count() == 0) {
1074 auto t = c.get_identity().get_nation_from_identity_holder().id;
1076 politics::force_nation_ideology(state, t, reb_type.get_ideology());
1077 return t;
1078 }
1079 }
1080 break;
1081 }
1083 // prefer existing
1084 for(auto c : state.world.province_get_core(p)) {
1085 auto holder = c.get_identity().get_nation_from_identity_holder();
1086 if(holder.get_religion() == state.world.rebel_faction_get_religion(reb_controller) && holder.get_owned_province_count() != 0) {
1087 return c.get_identity().get_nation_from_identity_holder().id;
1088 }
1089 }
1090 // otherwise pick a non-existent tag
1091 for(auto c : state.world.province_get_core(p)) {
1092 auto holder = c.get_identity().get_nation_from_identity_holder();
1093 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) {
1094 auto t = c.get_identity().get_nation_from_identity_holder().id;
1096 return t;
1097 }
1098 }
1099 break;
1101 // prefer existing
1102 for(auto c : state.world.province_get_core(p)) {
1103 auto holder = c.get_identity().get_nation_from_identity_holder();
1104 if(c.get_identity().get_primary_culture() == state.world.rebel_faction_get_primary_culture(reb_controller) && holder.get_owned_province_count() != 0) {
1105 return c.get_identity().get_nation_from_identity_holder().id;
1106 }
1107 }
1108 // otherwise pick a non-existent tag
1109 for(auto c : state.world.province_get_core(p)) {
1110 auto holder = c.get_identity().get_nation_from_identity_holder();
1111 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) {
1112 auto t = c.get_identity().get_nation_from_identity_holder().id;
1114 return t;
1115 }
1116 }
1117 break;
1119 // prefer existing
1120 for(auto c : state.world.province_get_core(p)) {
1121 auto holder = c.get_identity().get_nation_from_identity_holder();
1122 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) {
1123 return c.get_identity().get_nation_from_identity_holder().id;
1124 }
1125 }
1126 // otherwise pick a non-existent tag
1127 for(auto c : state.world.province_get_core(p)) {
1128 auto holder = c.get_identity().get_nation_from_identity_holder();
1129 if(!c.get_identity().get_is_not_releasable()
1130 && c.get_identity().get_primary_culture().get_group_from_culture_group_membership() == state.world.rebel_faction_get_primary_culture_group(reb_controller)
1131 && holder.get_owned_province_count() == 0) {
1132
1133 auto t = c.get_identity().get_nation_from_identity_holder().id;
1135 return t;
1136 }
1137 }
1138
1139 // first: existing tag of culture; then existing tag of culture group;
1140 // then dead tag of culture, then dead tag of cg
1141 break;
1143 // union tag or nothing
1144 for(auto c : state.world.province_get_core(p)) {
1145 auto holder = c.get_identity().get_nation_from_identity_holder();
1146 if(c.get_identity() == state.world.rebel_faction_get_defection_target(reb_controller) && holder.get_owned_province_count() != 0) {
1147 return holder.id;
1148 }
1149 }
1150 break;
1151 default: // NONE and ANY
1152 return dcon::nation_id{};
1153 }
1154 return dcon::nation_id{};
1155 }();
1156
1157 if(defection_tag) {
1158 // TODO: if rebel armies were still a thing, you would probably want to delete the ones that did
1159 // the work of causing the province to defect or to send them back to the original country or something
1160 // but ...
1161 province::change_province_owner(state, p, defection_tag);
1162 }
1163 }
1164 }
1165 });
1166}
1167
1168float get_suppression_point_cost(sys::state& state, dcon::movement_id m) {
1169 /*
1170 Depends on whether define:POPULATION_SUPPRESSION_FACTOR is non zero. If it is zero, suppression costs their effective
1171 radicalism + 1. If it is non zero, then the cost is the greater of that value and the movements effective radicalism x the
1172 movement's support / define:POPULATION_SUPPRESSION_FACTOR
1173 */
1174 if(state.defines.population_suppression_factor > 0.0f) {
1175 return std::max(state.world.movement_get_radicalism(m) + 1.0f, state.world.movement_get_radicalism(m) *
1176 state.world.movement_get_pop_support(m) / state.defines.population_suppression_factor);
1177 } else {
1178 return state.world.movement_get_radicalism(m) + 1.0f;
1179 }
1180}
1181
1183 for(uint32_t i = state.world.rebel_faction_size(); i-- > 0;) {
1184 auto reb = dcon::rebel_faction_id{dcon::rebel_faction_id::value_base_t(i)};
1185 auto within = state.world.rebel_faction_get_ruler_from_rebellion_within(reb);
1186 auto is_active = get_faction_brigades_active(state, reb) > 0;
1187 auto enforce_trigger = state.world.rebel_faction_get_type(reb).get_demands_enforced_trigger();
1188 if(is_active && enforce_trigger &&
1189 trigger::evaluate(state, enforce_trigger, trigger::to_generic(within), trigger::to_generic(within),
1190 trigger::to_generic(reb))) {
1191 // rebel victory
1192
1193 /*
1194 The government type of the nation will change if the rebel type has an associated government (with the same logic for
1195 a government type change from a wargoal or other cause).
1196 */
1197
1198 auto old_gov = state.world.nation_get_government_type(within);
1199 assert(old_gov);
1200 auto new_gov = state.world.rebel_faction_get_type(reb).get_government_change(old_gov);
1201 if(new_gov) {
1202 politics::change_government_type(state, within, new_gov);
1203 }
1204 if(auto iid = state.world.rebel_faction_get_type(reb).get_ideology(); iid) {
1205 politics::force_nation_ideology(state, within, iid);
1206 }
1207 //The pops won, reset their militancy to avoid death spiraling
1208 for(auto members : state.world.rebel_faction_get_pop_rebellion_membership(reb)) {
1209 pop_demographics::set_militancy(state, members.get_pop().id, std::max(pop_demographics::get_militancy(state, members.get_pop()) - 8.0f, 0.0f));
1210 }
1211
1212 /*
1213 If the rebel type has "break alliances on win" then the nation loses all of its alliances, all of its non-substate
1214 vassals, all of its sphere members, and loses all of its influence and has its influence level set to neutral.
1215 */
1216
1217 if(state.world.rebel_faction_get_type(reb).get_break_alliance_on_win()) {
1219 }
1220
1221 /*
1222 The nation loses prestige equal to define:PRESTIGE_HIT_ON_BREAK_COUNTRY x (nation's current prestige + permanent
1223 prestige), which is multiplied by the nation's prestige modifier from technology + 1 as usual (!).
1224 */
1225 auto ploss = state.defines.prestige_hit_on_break_country * nations::prestige_score(state, within);
1226 nations::adjust_prestige(state, within, ploss);
1227
1228
1229 /*
1230 If it wins, it gets its `demands_enforced_effect` executed.
1231 */
1232
1233 if(auto k = state.world.rebel_faction_get_type(reb).get_demands_enforced_effect(); k)
1235 uint32_t(state.current_date.value), uint32_t(within.index() ^ (reb.index() << 4)));
1236
1238 [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) {
1239
1240 text::add_line(state, contents, "msg_rebels_win_1", text::variable_type::x, state.world.rebel_type_get_title(type));
1241 text::add_line(state, contents, "msg_rebels_win_2", text::variable_type::x, text::fp_one_place{ploss});
1242 if(new_gov) {
1243 text::add_line(state, contents, "msg_rebels_win_3", text::variable_type::x, state.world.government_type_get_name(new_gov));
1244 }
1245 if(auto iid = state.world.rebel_type_get_ideology(type); iid) {
1246 text::add_line(state, contents, "msg_rebels_win_4", text::variable_type::x, state.world.ideology_get_name(iid));
1247 }
1248 if(state.world.rebel_type_get_break_alliance_on_win(type)) {
1249 text::add_line(state, contents, "msg_rebels_win_5");
1250 }
1251 if(auto k = state.world.rebel_type_get_demands_enforced_effect(type); k) {
1252 text::add_line(state, contents, "msg_rebels_win_6");
1253 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)));
1254 }
1255 },
1256 "msg_rebels_win_title",
1257 within, dcon::nation_id{}, dcon::nation_id{},
1259 });
1260
1261 /*
1262 Any units for the faction that exist are destroyed (or transferred if it is one of the special rebel types).
1263 */
1264 delete_faction(state, reb);
1265 }
1266 }
1267}
1268
1269void trigger_revolt(sys::state& state, dcon::nation_id n, dcon::rebel_type_id t, dcon::ideology_id i, dcon::culture_id c,
1270 dcon::religion_id r) {
1271 // TODO
1272}
1273
1274std::string rebel_name(sys::state& state, dcon::rebel_faction_id reb) {
1276
1277 auto culture = state.world.rebel_faction_get_primary_culture(reb);
1278 auto religion = state.world.rebel_faction_get_religion(reb);
1279 auto defection_target = state.world.rebel_faction_get_defection_target(reb);
1280 auto in_nation = state.world.rebel_faction_get_ruler_from_rebellion_within(reb);
1281 auto rebel_adj = text::get_adjective(state, in_nation);
1282 auto adjective = defection_target.get_adjective();
1283
1286
1287 if(culture) {
1289 } else {
1290 text::add_to_substitution_map(sub, text::variable_type::culture, state.world.nation_get_primary_culture(in_nation).get_name());
1291 }
1292
1293 if(religion) {
1295 }
1296
1297 if(defection_target) {
1300 } else {
1301 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());
1302 }
1303
1304 return text::resolve_string_substitution(state, state.world.rebel_faction_get_type(reb).get_name(), sub);
1305}
1306
1307bool allow_in_area(sys::state& state, dcon::province_id p, dcon::rebel_faction_id reb) {
1308 auto rf = dcon::fatten(state.world, reb);
1309 auto prov = dcon::fatten(state.world, p);
1310 switch(culture::rebel_area(rf.get_type().get_area())) {
1312 return true;
1314 return prov.get_dominant_culture() == rf.get_primary_culture();
1316 return prov.get_dominant_culture().get_culture_group_membership().get_group() == rf.get_primary_culture_group();
1318 return prov.get_dominant_religion() == rf.get_religion();
1320 return prov.get_dominant_religion() == rf.get_defection_target().get_religion() && prov.get_province_ownership().get_nation() == rf.get_rebellion_within().get_ruler();
1322 return prov.get_dominant_culture() == rf.get_defection_target().get_primary_culture() && prov.get_province_ownership().get_nation() == rf.get_rebellion_within().get_ruler();
1324 return prov.get_province_ownership().get_nation() == rf.get_rebellion_within().get_ruler();
1325 default:
1326 break;
1327 }
1328 return false;
1329}
1330
1332 concurrency::parallel_for(uint32_t(0), state.world.army_rebel_control_size(), [&](uint32_t i) {
1333 auto ar_reb_control = dcon::army_rebel_control_id{ dcon::army_rebel_control_id::value_base_t(i) };
1334 if(!state.world.army_rebel_control_is_valid(ar_reb_control))
1335 return;
1336 auto arc = dcon::fatten(state.world, ar_reb_control);
1337 auto ar = arc.get_army();
1338 if(!ar.get_army_rebel_control().get_controller()) /* Not a rebel army */
1339 return;
1340 if(ar.get_arrival_time() != sys::date{}) /* Do not interrupt travel */
1341 return;
1342 if(ar.get_battle_from_army_battle_participation()) /* In battle */
1343 return;
1344 if(ar.get_navy_from_army_transport()) /* Or in naval transport... */
1345 return;
1346
1347 auto type = arc.get_controller().get_type();
1348 auto area = arc.get_controller().get_type().get_area();
1349 auto location = ar.get_location_from_army_location();
1350 /* If on an unsieged province, siege it! */
1351 if(location.get_nation_from_province_control() && !location.get_rebel_faction_from_province_rebel_control()) {
1352 return;
1353 }
1354 dcon::province_fat_id best_prov = location;
1355 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);;
1356 for(const auto adj : location.get_province_adjacency()) {
1357 auto indx = adj.get_connected_provinces(0) != location.id ? 0 : 1;
1358 auto prov = adj.get_connected_provinces(indx);
1359 /* sea province */
1360 if(prov.id.index() >= state.province_definitions.first_sea_province.index())
1361 continue;
1362 /* impassable */
1363 if((adj.get_type() & province::border::impassible_bit) != 0)
1364 continue;
1365 if(allow_in_area(state, prov, arc.get_controller())) {
1366 //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()));
1367 float weight = float(rng::get_random(state, uint32_t(prov.id.index() * ar.id.index())) % 100);
1368 //if(prov.get_army_location().begin() != prov.get_army_location().end()) {
1369 // weight *= 0.01f;
1370 //}
1371 if(prov.get_rebel_faction_from_province_rebel_control()) {
1372 weight *= 0.0001f;
1373 }
1374 if(weight >= best_weight) {
1375 best_weight = weight;
1376 best_prov = prov;
1377 }
1378 }
1379 }
1380 if(best_prov != location) {
1381 ar.get_path().resize(1);
1382 ar.get_path()[0] = best_prov;
1383 ar.set_arrival_time(military::arrival_time_to(state, ar.id, best_prov));
1384 ar.set_dig_in(0);
1385 ar.set_is_rebel_hunter(false);
1386 }
1387 });
1388}
1389
1390} // namespace rebel
#define assert(condition)
Definition: debug.h:74
float estimate_army_offensive_strength(sys::state &state, dcon::army_id a)
Definition: ai.cpp:4459
float estimate_rebel_strength(sys::state &state, dcon::province_id p)
Definition: ai.cpp:5274
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:5404
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:3992
bool rebel_army_in_province(sys::state &state, dcon::province_id p)
Definition: military.cpp:7333
sys::date arrival_time_to(sys::state &state, dcon::army_id a, dcon::province_id p)
Definition: military.cpp:3889
dcon::regiment_id create_new_regiment(sys::state &state, dcon::nation_id n, dcon::unit_type_id t)
Definition: military.cpp:2047
void adjust_prestige(sys::state &state, dcon::nation_id n, float delta)
Definition: nations.cpp:1330
void destroy_diplomatic_relationships(sys::state &state, dcon::nation_id n)
Definition: nations.cpp:1348
float prestige_score(sys::state const &state, dcon::nation_id n)
Definition: nations.cpp:396
void create_nation_based_on_template(sys::state &state, dcon::nation_id n, dcon::nation_id base)
Definition: nations.cpp:1106
dcon::nation_id owner_of_pop(sys::state const &state, dcon::pop_id pop_ids)
Definition: nations.cpp:69
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:1673
bool is_overseas(sys::state const &state, dcon::province_id ids)
Definition: province.cpp:18
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:107
void change_province_owner(sys::state &state, dcon::province_id id, dcon::nation_id new_owner)
Definition: province.cpp:666
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:1049
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:1269
int32_t get_faction_brigades_active(sys::state &state, dcon::rebel_faction_id r)
Definition: rebels.cpp:1016
void delete_faction(sys::state &state, dcon::rebel_faction_id reb)
Definition: rebels.cpp:711
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:734
void rebel_risings_check(sys::state &state)
Definition: rebels.cpp:905
bool sphere_member_has_ongoing_revolt(sys::state &state, dcon::nation_id n)
Definition: rebels.cpp:998
float get_faction_organization(sys::state &state, dcon::rebel_faction_id r)
Definition: rebels.cpp:1025
constexpr float rebel_size_reduction
Definition: rebels.cpp:903
void update_movement_values(sys::state &state)
Definition: rebels.cpp:37
void update_armies(sys::state &state)
Definition: rebels.cpp:1331
void get_hunting_targets(sys::state &state, dcon::nation_id n, std::vector< impl::prov_str > &rebel_provs)
Definition: rebels.cpp:783
std::string rebel_name(sys::state &state, dcon::rebel_faction_id reb)
Definition: rebels.cpp:1274
void update_factions(sys::state &state)
Definition: rebels.cpp:720
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:1029
bool allow_in_area(sys::state &state, dcon::province_id p, dcon::rebel_faction_id reb)
Definition: rebels.cpp:1307
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:1168
void execute_rebel_victories(sys::state &state)
Definition: rebels.cpp:1182
void sort_hunting_targets(sys::state &state, impl::arm_str const &ar, std::vector< impl::prov_str > &rebel_provs)
Definition: rebels.cpp:793
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:736
int32_t get_faction_brigades_ready(sys::state &state, dcon::rebel_faction_id r)
Definition: rebels.cpp:1012
bool pop_is_compatible_with_rebel_type(sys::state &state, dcon::pop_id p, dcon::rebel_type_id t)
Definition: rebels.cpp:413
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:444
void rebel_hunting_check(sys::state &state)
Definition: rebels.cpp:808
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:2113
layout_box open_layout_box(layout_base &dest, int32_t indent)
Definition: text.cpp:1799
void add_line(sys::state &state, layout_base &dest, dcon::text_key txt, int32_t indent)
Definition: text.cpp:1899
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:794
void close_layout_box(columnar_layout &dest, layout_box &box)
Definition: text.cpp:1807
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:5865
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:5782
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