Project Alice
Loading...
Searching...
No Matches
military.cpp
Go to the documentation of this file.
1#include "military.hpp"
3#include "dcon_generated.hpp"
4#include "prng.hpp"
5#include "effects.hpp"
6#include "events.hpp"
7#include "ai.hpp"
8#include "demographics.hpp"
9#include "politics.hpp"
11#include "rebels.hpp"
12#include "triggers.hpp"
13#include "container_types.hpp"
14#include "math_fns.hpp"
15
16namespace military {
17
18template auto province_is_blockaded<ve::tagged_vector<dcon::province_id>>(sys::state const&, ve::tagged_vector<dcon::province_id>);
19template auto province_is_under_siege<ve::tagged_vector<dcon::province_id>>(sys::state const&, ve::tagged_vector<dcon::province_id>);
20template auto battle_is_ongoing_in_province<ve::tagged_vector<dcon::province_id>>(sys::state const&, ve::tagged_vector<dcon::province_id>);
21
22constexpr inline float org_dam_mul = 0.18f;
23constexpr inline float str_dam_mul = 0.14f;
24
25int32_t total_regiments(sys::state& state, dcon::nation_id n) {
26 return state.world.nation_get_active_regiments(n);
27}
28int32_t total_ships(sys::state& state, dcon::nation_id n) {
29 int32_t total = 0;
30 for(auto v : state.world.nation_get_navy_control(n)) {
31 auto srange = v.get_navy().get_navy_membership();
32 total += int32_t(srange.end() - srange.begin());
33 }
34 return total;
35}
36
37bool is_infantry_better(sys::state& state, dcon::nation_id n, dcon::unit_type_id best, dcon::unit_type_id given) {
38 if(!best) {
39 return true;
40 }
41 auto curbeststats = state.world.nation_get_unit_stats(n, best);
42 auto& curbestdef = (curbeststats.discipline_or_evasion) ? curbeststats : state.military_definitions.unit_base_definitions[best];
43
44 auto stats = state.world.nation_get_unit_stats(n, given);
45 auto& def = (stats.discipline_or_evasion) ? stats : state.military_definitions.unit_base_definitions[given];
46
47 if(def.defence_or_hull > curbestdef.defence_or_hull) {
48 return true;
49 } else if(def.defence_or_hull == curbestdef.defence_or_hull && def.default_organisation > curbestdef.default_organisation) {
50 return true;
51 } else if(def.defence_or_hull == curbestdef.defence_or_hull && def.default_organisation == curbestdef.default_organisation && def.attack_or_gun_power > curbestdef.attack_or_gun_power) {
52 return true;
53 }
54 return false;
55}
56
57bool is_artillery_better(sys::state& state, dcon::nation_id n, dcon::unit_type_id best, dcon::unit_type_id given) {
58 if(!best) {
59 return true;
60 }
61 auto& curbestdef = state.military_definitions.unit_base_definitions[best];
62 auto& def = state.military_definitions.unit_base_definitions[given];
63
64 if(def.support > curbestdef.support) {
65 return true;
66 } else if(def.support == curbestdef.support && def.discipline_or_evasion > curbestdef.discipline_or_evasion) {
67 return true;
68 } else if(def.support == curbestdef.support && def.discipline_or_evasion == curbestdef.discipline_or_evasion && def.siege_or_torpedo_attack > curbestdef.siege_or_torpedo_attack) {
69 return true;
70 }
71 return false;
72}
73
74bool is_cavalry_better(sys::state& state, dcon::nation_id n, dcon::unit_type_id best, dcon::unit_type_id given) {
75 if(!best) {
76 return true;
77 }
78 auto curbeststats = state.world.nation_get_unit_stats(n, best);
79 auto& basedef = state.military_definitions.unit_base_definitions[best];
80 auto& curbestdef = (curbeststats.discipline_or_evasion) ? curbeststats : basedef;
81
82 auto stats = state.world.nation_get_unit_stats(n, given);
83 auto& newbasedef = state.military_definitions.unit_base_definitions[given];
84 auto& def = (stats.discipline_or_evasion) ? stats : newbasedef;
85
86 if(def.reconnaissance_or_fire_range > curbestdef.reconnaissance_or_fire_range) {
87 return true;
88 } else if(def.reconnaissance_or_fire_range == curbestdef.reconnaissance_or_fire_range && def.attack_or_gun_power > curbestdef.attack_or_gun_power) {
89 return true;
90 } else if(def.reconnaissance_or_fire_range == curbestdef.reconnaissance_or_fire_range && def.attack_or_gun_power == curbestdef.attack_or_gun_power && basedef.maneuver > newbasedef.maneuver) {
91 return true;
92 }
93 return false;
94}
95
96dcon::unit_type_id get_best_infantry(sys::state& state, dcon::nation_id n, bool primary_culture) {
97 dcon::unit_type_id curbest;
98
99 auto ndef = state.world.nation_get_identity_from_identity_holder(n);
100 auto nationname = text::produce_simple_string(state, state.world.national_identity_get_name(ndef));
101
102 for(uint32_t i = 2; i < state.military_definitions.unit_base_definitions.size(); ++i) {
103 dcon::unit_type_id uid = dcon::unit_type_id{ dcon::unit_type_id::value_base_t(i) };
104 auto& def = state.military_definitions.unit_base_definitions[uid];
105
106 if(def.type != military::unit_type::infantry) {
107 continue;
108 }
109
110 auto available = state.world.nation_get_active_unit(n, uid) || def.active;
111
112 if(!available) {
113 continue;
114 }
115
116 if(def.primary_culture && !primary_culture) {
117 continue;
118 }
119
120 if(is_infantry_better(state, n, curbest, uid)) {
121 curbest = uid;
122 }
123 }
124#ifndef NDEBUG
125 state.console_log(nationname + " infantry type: " + std::to_string(curbest.index()));
126#endif
127 return curbest;
128}
129
130dcon::unit_type_id get_best_artillery(sys::state& state, dcon::nation_id n, bool primary_culture) {
131 dcon::unit_type_id curbest;
132
133 for(uint32_t i = 2; i < state.military_definitions.unit_base_definitions.size(); ++i) {
134 dcon::unit_type_id uid = dcon::unit_type_id{ dcon::unit_type_id::value_base_t(i) };
135 auto def = state.military_definitions.unit_base_definitions[uid];
136
137 if(def.type != military::unit_type::support) {
138 continue;
139 }
140
141 auto available = state.world.nation_get_active_unit(n, uid) || def.active;
142
143 if(!available) {
144 continue;
145 }
146
147 if(def.primary_culture && !primary_culture) {
148 continue;
149 }
150
151 if(is_artillery_better(state, n, curbest, uid)) {
152 curbest = uid;
153 }
154 }
155 return curbest;
156}
157
158dcon::unit_type_id get_best_cavalry(sys::state& state, dcon::nation_id n, bool primary_culture) {
159 dcon::unit_type_id curbest;
160 for(uint32_t i = 2; i < state.military_definitions.unit_base_definitions.size(); ++i) {
161 dcon::unit_type_id uid = dcon::unit_type_id{ dcon::unit_type_id::value_base_t(i) };
162
163 auto def = state.military_definitions.unit_base_definitions[uid];
164
165 if(def.type != military::unit_type::cavalry) {
166 continue;
167 }
168
169 auto available = state.world.nation_get_active_unit(n, uid) || def.active;
170
171 if(!available) {
172 continue;
173 }
174
175 if(def.primary_culture && !primary_culture) {
176 continue;
177 }
178
179 if(is_cavalry_better(state, n, curbest, uid)) {
180 curbest = uid;
181 }
182 }
183 return curbest;
184}
185
187 for(uint32_t i = 0; i < state.military_definitions.unit_base_definitions.size(); ++i) {
188 dcon::unit_type_id uid = dcon::unit_type_id{dcon::unit_type_id::value_base_t(i)};
189 state.world.for_each_nation([&](dcon::nation_id nid) {
190 state.world.nation_get_unit_stats(nid, uid) = state.military_definitions.unit_base_definitions[uid];
191 });
192 }
193}
194
196 assert(state.military_definitions.base_army_unit.index() < 2);
197 assert(state.military_definitions.base_naval_unit.index() < 2);
198 assert(state.military_definitions.base_army_unit.index() != -1);
199 assert(state.military_definitions.base_naval_unit.index() != -1);
200 for(uint32_t i = 2; i < state.military_definitions.unit_base_definitions.size(); ++i) {
201 dcon::unit_type_id uid = dcon::unit_type_id{dcon::unit_type_id::value_base_t(i)};
202 auto base_id = state.military_definitions.unit_base_definitions[uid].is_land
203 ? state.military_definitions.base_army_unit
204 : state.military_definitions.base_naval_unit;
205 state.world.for_each_nation([&](dcon::nation_id nid) {
206 auto& base_stats = state.world.nation_get_unit_stats(nid, base_id);
207 auto& current_stats = state.world.nation_get_unit_stats(nid, uid);
208 current_stats += base_stats;
209 });
210 }
211}
212
214 for(auto wg : state.world.in_wargoal) {
215 if(wg.get_war_from_wargoals_attached()) {
216 auto s = wg.get_associated_state();
217 if(s) {
218 bool found_state = false;
219 for(auto si : wg.get_target_nation().get_state_ownership()) {
220 if(si.get_state().get_definition() == s) {
221 found_state = true;
222 break;
223 }
224 }
225 if(!found_state) {
226 state.world.delete_wargoal(wg);
227 }
228 }
229 }
230 }
231}
232
234 state.world.for_each_nation([&](dcon::nation_id n) {
235 auto w = state.world.nation_get_war_participant(n);
236 if(w.begin() != w.end()) {
237 state.world.nation_set_is_at_war(n, true);
238 }
239 });
243}
244
245bool can_use_cb_against(sys::state& state, dcon::nation_id from, dcon::nation_id target) {
246 auto other_cbs = state.world.nation_get_available_cbs(from);
247 for(auto& cb : other_cbs) {
248 if(cb.target == target && cb_conditions_satisfied(state, from, target, cb.cb_type))
249 return true;
250 }
251 for(auto cb : state.world.in_cb_type) {
252 if((cb.get_type_bits() & military::cb_flag::always) != 0) {
253 if(cb_conditions_satisfied(state, from, target, cb))
254 return true;
255 }
256 }
257 return false;
258}
259
260bool can_add_always_cb_to_war(sys::state& state, dcon::nation_id actor, dcon::nation_id target, dcon::cb_type_id cb, dcon::war_id w) {
261
262 auto can_use = state.world.cb_type_get_can_use(cb);
263 if(can_use && !trigger::evaluate(state, can_use, trigger::to_generic(target), trigger::to_generic(actor), trigger::to_generic(target))) {
264 return false;
265 }
266
267 auto allowed_countries = state.world.cb_type_get_allowed_countries(cb);
268 auto allowed_states = state.world.cb_type_get_allowed_states(cb);
269
270 bool for_all_state = (state.world.cb_type_get_type_bits(cb) & military::cb_flag::all_allowed_states) != 0;
271
272 if(!allowed_countries && allowed_states) {
273 if(for_all_state) {
274 for(auto wg : state.world.war_get_wargoals_attached(w)) {
275 if(wg.get_wargoal().get_added_by() == actor
276 && wg.get_wargoal().get_type() == cb
277 && wg.get_wargoal().get_target_nation() == target) {
278
279 return false;
280 }
281 }
282 }
283
284 bool any_allowed = false;
285 for(auto si : state.world.nation_get_state_ownership(target)) {
286 if(trigger::evaluate(state, allowed_states, trigger::to_generic(si.get_state().id), trigger::to_generic(actor),
287 trigger::to_generic(actor))) {
288
289 any_allowed = true;
290
291 for(auto wg : state.world.war_get_wargoals_attached(w)) {
292 if(wg.get_wargoal().get_added_by() == actor
293 && wg.get_wargoal().get_type() == cb
294 && wg.get_wargoal().get_associated_state() == si.get_state().get_definition()
295 && wg.get_wargoal().get_target_nation() == target) {
296
297 any_allowed = false;
298 break;
299 }
300 }
301 if(any_allowed)
302 break;
303 }
304 }
305 if(!any_allowed)
306 return false;
307 }
308
309 auto allowed_substates = state.world.cb_type_get_allowed_substate_regions(cb);
310 if(allowed_substates) {
311 bool any_allowed = [&]() {
312 for(auto v : state.world.nation_get_overlord_as_ruler(target)) {
313 if(v.get_subject().get_is_substate()) {
314 for(auto si : state.world.nation_get_state_ownership(target)) {
315 if(trigger::evaluate(state, allowed_substates, trigger::to_generic(si.get_state().id), trigger::to_generic(actor), trigger::to_generic(actor))) {
316 return true;
317 }
318 }
319 }
320 }
321 return false;
322 }();
323 if(!any_allowed)
324 return false;
325 }
326
327 if(allowed_countries) {
328 bool any_allowed = [&]() {
329 for(auto n : state.world.in_nation) {
330 if(n != actor) {
331 if(trigger::evaluate(state, allowed_countries, trigger::to_generic(target), trigger::to_generic(actor), trigger::to_generic(n.id))) {
332 if(allowed_states) { // check whether any state within the target is valid for free / liberate
333
334 bool found_dup = false;
335
336 if(for_all_state) {
337 for(auto wg : state.world.war_get_wargoals_attached(w)) {
338 if(wg.get_wargoal().get_added_by() == actor
339 && wg.get_wargoal().get_type() == cb
340 && (wg.get_wargoal().get_associated_tag() == n.get_identity_from_identity_holder()
341 || wg.get_wargoal().get_secondary_nation() == n)
342 && wg.get_wargoal().get_target_nation() == target) {
343
344 found_dup = true;
345 }
346 }
347 }
348
349 if(!found_dup) {
350 for(auto si : state.world.nation_get_state_ownership(target)) {
351 if(trigger::evaluate(state, allowed_states, trigger::to_generic(si.get_state().id), trigger::to_generic(actor), trigger::to_generic(n.id))) {
352
353
354 for(auto wg : state.world.war_get_wargoals_attached(w)) {
355 if(wg.get_wargoal().get_added_by() == actor
356 && wg.get_wargoal().get_type() == cb
357 && (wg.get_wargoal().get_associated_tag() == n.get_identity_from_identity_holder()
358 || wg.get_wargoal().get_secondary_nation() == n)
359 && wg.get_wargoal().get_associated_state() == si.get_state().get_definition()
360 && wg.get_wargoal().get_target_nation() == target) {
361
362 found_dup = true;
363 break;
364 }
365 }
366 }
367 }
368 }
369
370 if(!found_dup)
371 return true;
372
373 } else { // no allowed states trigger -> nation is automatically a valid target
374 bool found_dup = false;
375
376 for(auto wg : state.world.war_get_wargoals_attached(w)) {
377 if(wg.get_wargoal().get_added_by() == actor
378 && wg.get_wargoal().get_type() == cb
379 && (wg.get_wargoal().get_associated_tag() == n.get_identity_from_identity_holder()
380 || wg.get_wargoal().get_secondary_nation() == n)
381 && wg.get_wargoal().get_target_nation() == target) {
382
383 found_dup = true;
384 }
385 }
386
387 if(!found_dup) {
388 return true;
389 }
390 }
391 }
392 }
393 }
394 return false;
395 }();
396 if(!any_allowed)
397 return false;
398 }
399
400 return true;
401
402}
403
404bool cb_conditions_satisfied(sys::state& state, dcon::nation_id actor, dcon::nation_id target, dcon::cb_type_id cb) {
405 auto can_use = state.world.cb_type_get_can_use(cb);
406 if(can_use && !trigger::evaluate(state, can_use, trigger::to_generic(target), trigger::to_generic(actor), trigger::to_generic(target))) {
407 return false;
408 }
409
410 auto allowed_countries = state.world.cb_type_get_allowed_countries(cb);
411 auto allowed_states = state.world.cb_type_get_allowed_states(cb);
412
413 if(!allowed_countries && allowed_states) {
414 bool any_allowed = false;
415 for(auto si : state.world.nation_get_state_ownership(target)) {
416 if(trigger::evaluate(state, allowed_states, trigger::to_generic(si.get_state().id), trigger::to_generic(actor), trigger::to_generic(actor))) {
417 any_allowed = true;
418 break;
419 }
420 }
421 if(!any_allowed)
422 return false;
423 }
424
425 auto allowed_substates = state.world.cb_type_get_allowed_substate_regions(cb);
426 if(allowed_substates) {
427 bool any_allowed = [&]() {
428 for(auto v : state.world.nation_get_overlord_as_ruler(target)) {
429 if(v.get_subject().get_is_substate()) {
430 for(auto si : state.world.nation_get_state_ownership(target)) {
431 if(trigger::evaluate(state, allowed_substates, trigger::to_generic(si.get_state().id), trigger::to_generic(actor),
432 trigger::to_generic(actor))) {
433 return true;
434 }
435 }
436 }
437 }
438 return false;
439 }();
440 if(!any_allowed)
441 return false;
442 }
443
444 if(allowed_countries) {
445 bool any_allowed = [&]() {
446 for(auto n : state.world.in_nation) {
447 if(n != actor) {
448 if(trigger::evaluate(state, allowed_countries, trigger::to_generic(target), trigger::to_generic(actor), trigger::to_generic(n.id))) {
449 if(allowed_states) { // check whether any state within the target is valid for free / liberate
450 for(auto si : state.world.nation_get_state_ownership(target)) {
451 if(trigger::evaluate(state, allowed_states, trigger::to_generic(si.get_state().id), trigger::to_generic(actor),
452 trigger::to_generic(n.id))) {
453 return true;
454 }
455 }
456 } else { // no allowed states trigger -> nation is automatically a valid target
457 return true;
458 }
459 }
460 }
461 }
462 return false;
463 }();
464 if(!any_allowed)
465 return false;
466 }
467
468 return true;
469}
470
471bool cb_instance_conditions_satisfied(sys::state& state, dcon::nation_id actor, dcon::nation_id target, dcon::cb_type_id cb,
472 dcon::state_definition_id st, dcon::national_identity_id tag, dcon::nation_id secondary) {
473
474 auto can_use = state.world.cb_type_get_can_use(cb);
475 if(can_use && !trigger::evaluate(state, can_use, trigger::to_generic(target), trigger::to_generic(actor), trigger::to_generic(target))) {
476 return false;
477 }
478
479 auto allowed_countries = state.world.cb_type_get_allowed_countries(cb);
480 auto allowed_states = state.world.cb_type_get_allowed_states(cb);
481
482 if(!allowed_countries && allowed_states) {
483 bool any_allowed = false;
484 if(cb_requires_selection_of_a_state(state, cb)) {
485 for(auto si : state.world.nation_get_state_ownership(target)) {
486 if(si.get_state().get_definition() == st &&
487 trigger::evaluate(state, allowed_states, trigger::to_generic(si.get_state().id), trigger::to_generic(actor),
488 trigger::to_generic(actor))) {
489 any_allowed = true;
490 break;
491 }
492 }
493 } else {
494 for(auto si : state.world.nation_get_state_ownership(target)) {
495 if(trigger::evaluate(state, allowed_states, trigger::to_generic(si.get_state().id), trigger::to_generic(actor),
496 trigger::to_generic(actor))) {
497 any_allowed = true;
498 break;
499 }
500 }
501 }
502 if(!any_allowed)
503 return false;
504 }
505
506 auto allowed_substates = state.world.cb_type_get_allowed_substate_regions(cb);
507 if(allowed_substates) {
508 bool any_allowed = [&]() {
509 for(auto v : state.world.nation_get_overlord_as_ruler(target)) {
510 if(v.get_subject().get_is_substate()) {
511 if(cb_requires_selection_of_a_state(state, cb)) {
512 for(auto si : state.world.nation_get_state_ownership(target)) {
513 if(si.get_state().get_definition() == st &&
514 trigger::evaluate(state, allowed_substates, trigger::to_generic(si.get_state().id), trigger::to_generic(actor),
515 trigger::to_generic(actor))) {
516 return true;
517 }
518 }
519 } else {
520 for(auto si : state.world.nation_get_state_ownership(target)) {
521 if(trigger::evaluate(state, allowed_substates, trigger::to_generic(si.get_state().id), trigger::to_generic(actor),
522 trigger::to_generic(actor))) {
523 return true;
524 }
525 }
526 }
527 }
528 }
529 return false;
530 }();
531 if(!any_allowed)
532 return false;
533 }
534
535 if(allowed_countries) {
536 auto secondary_nation = secondary ? secondary : state.world.national_identity_get_nation_from_identity_holder(tag);
537
538 if(secondary_nation != actor && trigger::evaluate(state, allowed_countries, trigger::to_generic(target), trigger::to_generic(actor), trigger::to_generic(secondary_nation))) {
539 bool validity = false;
540 if(allowed_states) { // check whether any state within the target is valid for free / liberate
541 if((state.world.cb_type_get_type_bits(cb) & cb_flag::all_allowed_states) != 0) {
542 for(auto si : state.world.nation_get_state_ownership(target)) {
543 if(trigger::evaluate(state, allowed_states, trigger::to_generic(si.get_state().id), trigger::to_generic(actor),
544 trigger::to_generic(secondary_nation))) {
545 validity = true;
546 break;
547 }
548 }
549 } else {
550 for(auto si : state.world.nation_get_state_ownership(target)) {
551 if(si.get_state().get_definition() == st &&
552 trigger::evaluate(state, allowed_states, trigger::to_generic(si.get_state().id), trigger::to_generic(actor),
553 trigger::to_generic(secondary_nation))) {
554 validity = true;
555 break;
556 }
557 }
558 }
559 } else { // no allowed states trigger -> nation is automatically a valid target
560 validity = true;
561 }
562 if(!validity)
563 return false;
564 } else {
565 return false;
566 }
567 }
568
569 return true;
570}
571
572bool province_is_blockaded(sys::state const& state, dcon::province_id ids) {
573 return state.world.province_get_is_blockaded(ids);
574}
575
576bool compute_blockade_status(sys::state& state, dcon::province_id p) {
577 auto controller = state.world.province_get_nation_from_province_control(p);
578 auto owner = state.world.province_get_nation_from_province_ownership(p);
579 if(!owner)
580 return false;
581 if(controller != owner)
582 return false;
583
584 auto port_to = state.world.province_get_port_to(p);
585 if(!port_to)
586 return false;
587
588 for(auto n : state.world.province_get_navy_location(port_to)) {
589 if(n.get_navy().get_is_retreating() == false && !n.get_navy().get_battle_from_navy_battle_participation()) {
590 if(military::are_at_war(state, owner, n.get_navy().get_controller_from_navy_control()))
591 return true;
592 }
593 }
594 return false;
595}
596
598 province::for_each_land_province(state, [&](dcon::province_id p) {
599 state.world.province_set_is_blockaded(p, compute_blockade_status(state, p));
600 });
601}
602
603bool province_is_under_siege(sys::state const& state, dcon::province_id ids) {
604 return state.world.province_get_siege_progress(ids) > 0.0f;
605}
606
607float recruited_pop_fraction(sys::state const& state, dcon::nation_id n) {
608 auto current = float(state.world.nation_get_active_regiments(n));
609 auto maximum = float(state.world.nation_get_recruitable_regiments(n));
610 return maximum > 0.0f ? current / maximum : 1.0f;
611}
612
613bool state_has_naval_base(sys::state const& state, dcon::state_instance_id si) {
614 auto owner = state.world.state_instance_get_nation_from_state_ownership(si);
615 auto def = state.world.state_instance_get_definition(si);
616 for(auto p : state.world.state_definition_get_abstract_state_membership(def)) {
617 if(p.get_province().get_nation_from_province_ownership() == owner) {
618 if(p.get_province().get_building_level(uint8_t(economy::province_building_type::naval_base)) > 0)
619 return true;
620 }
621 }
622 return false;
623}
624
625uint32_t state_naval_base_level(sys::state const& state, dcon::state_instance_id si) {
626 uint32_t level = 0;
627 auto owner = state.world.state_instance_get_nation_from_state_ownership(si);
628 auto def = state.world.state_instance_get_definition(si);
629 for(auto p : state.world.state_definition_get_abstract_state_membership(def)) {
630 if(p.get_province().get_nation_from_province_ownership() == owner) {
631 level += p.get_province().get_building_level(uint8_t(economy::province_building_type::naval_base));
632 }
633 }
634 return level;
635}
636
637uint32_t state_railroad_level(sys::state const& state, dcon::state_instance_id si) {
638 uint32_t level = 0;
639 auto owner = state.world.state_instance_get_nation_from_state_ownership(si);
640 auto def = state.world.state_instance_get_definition(si);
641 for(auto p : state.world.state_definition_get_abstract_state_membership(def)) {
642 if(p.get_province().get_nation_from_province_ownership() == owner) {
643 level += p.get_province().get_building_level(uint8_t(economy::province_building_type::railroad));
644 }
645 }
646 return level;
647}
648
649bool are_at_war(sys::state const& state, dcon::nation_id a, dcon::nation_id b) {
650 if(!state.world.nation_get_is_at_war(a) || !state.world.nation_get_is_at_war(b))
651 return false;
652 for(auto wa : state.world.nation_get_war_participant(a)) {
653 auto is_attacker = wa.get_is_attacker();
654 for(auto o : wa.get_war().get_war_participant()) {
655 if(o.get_nation() == b)
656 return o.get_is_attacker() != is_attacker;
657 }
658 }
659 return false;
660}
661
662bool are_allied_in_war(sys::state const& state, dcon::nation_id a, dcon::nation_id b) {
663 for(auto wa : state.world.nation_get_war_participant(a)) {
664 auto is_attacker = wa.get_is_attacker();
665 for(auto o : wa.get_war().get_war_participant()) {
666 if(o.get_nation() == b)
667 return o.get_is_attacker() == is_attacker;
668 }
669 }
670 return false;
671}
672
673bool are_in_common_war(sys::state const& state, dcon::nation_id a, dcon::nation_id b) {
674 for(auto wa : state.world.nation_get_war_participant(a)) {
675 for(auto o : wa.get_war().get_war_participant()) {
676 if(o.get_nation() == b)
677 return true;
678 }
679 }
680 return false;
681}
682
683void remove_from_common_allied_wars(sys::state& state, dcon::nation_id a, dcon::nation_id b) {
684 std::vector<dcon::war_id> wars;
685 for(auto wa : state.world.nation_get_war_participant(a)) {
686 for(auto o : wa.get_war().get_war_participant()) {
687 if(o.get_nation() == b && o.get_is_attacker() == wa.get_is_attacker()) {
688 wars.push_back(wa.get_war());
689 }
690 }
691 }
692 for(const auto w : wars) {
693 //military::remove_from_war(state, w, a, false);
694 military::remove_from_war(state, w, b, false);
695 }
696}
697
699 dcon::war_id w;
701};
702
703participation internal_find_war_between(sys::state const& state, dcon::nation_id a, dcon::nation_id b) {
704 for(auto wa : state.world.nation_get_war_participant(a)) {
705 auto is_attacker = wa.get_is_attacker();
706 for(auto o : wa.get_war().get_war_participant()) {
707 if(o.get_nation() == b) {
708 if(o.get_is_attacker() != is_attacker)
710 else
711 return participation{};
712 }
713 }
714 }
715 return participation{};
716}
717
718dcon::war_id find_war_between(sys::state const& state, dcon::nation_id a, dcon::nation_id b) {
719 return internal_find_war_between(state, a, b).w;
720}
721
722bool joining_war_does_not_violate_constraints(sys::state const& state, dcon::nation_id a, dcon::war_id w, bool as_attacker) {
723 auto target_war_participants = state.world.war_get_war_participant(w);
724 for(auto wa : state.world.nation_get_war_participant(a)) {
725 for(auto other_participants : wa.get_war().get_war_participant()) {
726 if(other_participants.get_is_attacker() ==
727 wa.get_is_attacker()) { // case: ally on same side -- must not be on opposite site
728 for(auto tp : target_war_participants) {
729 if(tp.get_nation() == other_participants.get_nation() && tp.get_is_attacker() != as_attacker)
730 return false;
731 }
732 } else { // case opponent -- must not be in new war at all
733 for(auto tp : target_war_participants) {
734 if(tp.get_nation() == other_participants.get_nation())
735 return false;
736 }
737 }
738 }
739 }
740 return true;
741}
742
743bool is_civil_war(sys::state const& state, dcon::war_id w) {
744 for(auto wg : state.world.war_get_wargoals_attached(w)) {
745 if((wg.get_wargoal().get_type().get_type_bits() & cb_flag::is_civil_war) != 0)
746 return true;
747 }
748 return false;
749}
750
751bool is_defender_wargoal(sys::state const& state, dcon::war_id w, dcon::wargoal_id wg) {
752 auto from = state.world.wargoal_get_added_by(wg);
753 for(auto p : state.world.war_get_war_participant(w)) {
754 if(p.get_nation() == from)
755 return !p.get_is_attacker();
756 }
757 return false;
758}
759
760bool defenders_have_non_status_quo_wargoal(sys::state const& state, dcon::war_id w) {
761 for(auto wg : state.world.war_get_wargoals_attached(w)) {
762 if((wg.get_wargoal().get_type().get_type_bits() & cb_flag::po_status_quo) == 0 && is_defender_wargoal(state, w, wg.get_wargoal()))
763 return true;
764 }
765 return false;
766}
767
768bool defenders_have_status_quo_wargoal(sys::state const& state, dcon::war_id w) {
769 for(auto wg : state.world.war_get_wargoals_attached(w)) {
770 if((wg.get_wargoal().get_type().get_type_bits() & cb_flag::po_status_quo) != 0 && is_defender_wargoal(state, w, wg.get_wargoal()))
771 return true;
772 }
773 return false;
774}
775bool attackers_have_status_quo_wargoal(sys::state const& state, dcon::war_id w) {
776 for(auto wg : state.world.war_get_wargoals_attached(w)) {
777 if((wg.get_wargoal().get_type().get_type_bits() & cb_flag::po_status_quo) != 0 && !is_defender_wargoal(state, w, wg.get_wargoal()))
778 return true;
779 }
780 return false;
781}
782
783bool joining_as_attacker_would_break_truce(sys::state& state, dcon::nation_id a, dcon::war_id w) {
784 for(auto p : state.world.war_get_war_participant(w)) {
785 if(p.get_is_attacker() == false) {
786 if(has_truce_with(state, a, p.get_nation())) {
787 return true;
788 }
789 }
790 }
791 return false;
792}
793
794int32_t supply_limit_in_province(sys::state& state, dcon::nation_id n, dcon::province_id p) {
795 /*
796 (province-supply-limit-modifier + 1) x (2.5 if it is owned an controlled or 2 if it is just controlled, you are allied to the
797 controller, have military access with the controller, a rebel controls it, it is one of your core provinces, or you are
798 sieging it) x (technology-supply-limit-modifier + 1)
799 */
800 float modifier = 1.0f;
801 auto prov_controller = state.world.province_get_nation_from_province_control(p);
802 auto self_controlled = prov_controller == n;
803 if(state.world.province_get_nation_from_province_ownership(p) == n && self_controlled) {
804 modifier = 2.5f;
805 } else if(self_controlled ||
806 bool(state.world.province_get_rebel_faction_from_province_rebel_control(p))) { // TODO: check for sieging
807 modifier = 2.0f;
808 } else if(auto dip_rel = state.world.get_diplomatic_relation_by_diplomatic_pair(prov_controller, n);
809 state.world.diplomatic_relation_get_are_allied(dip_rel)) {
810 modifier = 2.0f;
811 } else if(auto uni_rel = state.world.get_unilateral_relationship_by_unilateral_pair(prov_controller, n);
812 state.world.unilateral_relationship_get_military_access(uni_rel)) {
813 modifier = 2.0f;
814 } else if(bool(state.world.get_core_by_prov_tag_key(p, state.world.nation_get_identity_from_identity_holder(n)))) {
815 modifier = 2.0f;
816 } else if(state.world.province_get_siege_progress(p) > 0.0f) {
817 modifier = 2.0f;
818 }
819 auto base_supply_lim = (state.world.province_get_modifier_values(p, sys::provincial_mod_offsets::supply_limit) + 1.0f);
820 auto national_supply_lim = (state.world.nation_get_modifier_values(n, sys::national_mod_offsets::supply_limit) + 1.0f);
821 return int32_t(base_supply_lim * modifier * national_supply_lim);
822}
823int32_t regiments_created_from_province(sys::state& state, dcon::province_id p) {
824 int32_t total = 0;
825 for(auto pop : state.world.province_get_pop_location(p)) {
826 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers) {
827 auto regs = pop.get_pop().get_regiment_source();
828 total += int32_t(regs.end() - regs.begin());
829 }
830 }
831 return total;
832}
833int32_t mobilized_regiments_created_from_province(sys::state& state, dcon::province_id p) {
834 /*
835 Mobilized regiments come only from non-colonial provinces.
836 */
837 if(fatten(state.world, p).get_is_colonial())
838 return 0;
839
840 int32_t total = 0;
841 for(auto pop : state.world.province_get_pop_location(p)) {
842 if(pop.get_pop().get_poptype() != state.culture_definitions.soldiers &&
843 pop.get_pop().get_poptype().get_strata() == uint8_t(culture::pop_strata::poor)) {
844 auto regs = pop.get_pop().get_regiment_source();
845 total += int32_t(regs.end() - regs.begin());
846 }
847 }
848 return total;
849}
850
851int32_t regiments_possible_from_pop(sys::state& state, dcon::pop_id p) {
852 auto type = state.world.pop_get_poptype(p);
853 if(type == state.culture_definitions.soldiers) {
854 auto location = state.world.pop_get_province_from_pop_location(p);
855 if(state.world.province_get_is_colonial(location)) {
856 float divisor = state.defines.pop_size_per_regiment * state.defines.pop_min_size_for_regiment_colony_multiplier;
857 float minimum = state.defines.pop_min_size_for_regiment;
858
859 if(state.world.pop_get_size(p) >= minimum) {
860 return int32_t((state.world.pop_get_size(p) / divisor) + 1);
861 }
862 return 0;
863 } else if(!state.world.province_get_is_owner_core(location)) {
864 float divisor = state.defines.pop_size_per_regiment * state.defines.pop_min_size_for_regiment_noncore_multiplier;
865 float minimum = state.defines.pop_min_size_for_regiment;
866
867 if(state.world.pop_get_size(p) >= minimum) {
868 return int32_t((state.world.pop_get_size(p) / divisor) + 1);
869 }
870 return 0;
871 } else {
872 float divisor = state.defines.pop_size_per_regiment;
873 float minimum = state.defines.pop_min_size_for_regiment;
874
875 if(state.world.pop_get_size(p) >= minimum) {
876 return int32_t((state.world.pop_get_size(p) / divisor) + 1);
877 }
878 return 0;
879 }
880 } else { // mobilized
881 return int32_t(state.world.pop_get_size(p) * mobilization_size(state, nations::owner_of_pop(state, p)) / state.defines.pop_size_per_regiment);
882 }
883}
884
885int32_t regiments_max_possible_from_province(sys::state& state, dcon::province_id p) {
886 /*
887 - A soldier pop must be at least define:POP_MIN_SIZE_FOR_REGIMENT to support any regiments
888 - If it is at least that large, then it can support one regiment per define:POP_SIZE_PER_REGIMENT x
889 define:POP_MIN_SIZE_FOR_REGIMENT_COLONY_MULTIPLIER (if it is located in a colonial province) x
890 define:POP_MIN_SIZE_FOR_REGIMENT_NONCORE_MULTIPLIER (if it is non-colonial but uncored)
891 */
892 int32_t total = 0;
893 if(state.world.province_get_is_colonial(p)) {
894 float divisor = state.defines.pop_size_per_regiment * state.defines.pop_min_size_for_regiment_colony_multiplier;
895 float minimum = state.defines.pop_min_size_for_regiment;
896
897 for(auto pop : state.world.province_get_pop_location(p)) {
898 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers) {
899 if(pop.get_pop().get_size() >= minimum) {
900 total += int32_t((pop.get_pop().get_size() / divisor) + 1);
901 }
902 }
903 }
904 } else if(!state.world.province_get_is_owner_core(p)) {
905 float divisor = state.defines.pop_size_per_regiment * state.defines.pop_min_size_for_regiment_noncore_multiplier;
906 float minimum = state.defines.pop_min_size_for_regiment;
907
908 for(auto pop : state.world.province_get_pop_location(p)) {
909 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers) {
910 if(pop.get_pop().get_size() >= minimum) {
911 total += int32_t((pop.get_pop().get_size() / divisor) + 1);
912 }
913 }
914 }
915 } else {
916 float divisor = state.defines.pop_size_per_regiment;
917 float minimum = state.defines.pop_min_size_for_regiment;
918
919 for(auto pop : state.world.province_get_pop_location(p)) {
920 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers) {
921 if(pop.get_pop().get_size() >= minimum) {
922 total += int32_t((pop.get_pop().get_size() / divisor) + 1);
923 }
924 }
925 }
926 }
927 return total;
928}
929int32_t main_culture_regiments_created_from_province(sys::state& state, dcon::province_id p) {
930 int32_t total = 0;
931 for(auto pop : state.world.province_get_pop_location(p)) {
932 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers && pop.get_pop().get_is_primary_or_accepted_culture()) {
933 auto regs = pop.get_pop().get_regiment_source();
934 total += int32_t(regs.end() - regs.begin());
935 }
936 }
937 return total;
938}
940 /*
941 - A soldier pop must be at least define:POP_MIN_SIZE_FOR_REGIMENT to support any regiments
942 - If it is at least that large, then it can support one regiment per define:POP_SIZE_PER_REGIMENT x
943 define:POP_MIN_SIZE_FOR_REGIMENT_COLONY_MULTIPLIER (if it is located in a colonial province) x
944 define:POP_MIN_SIZE_FOR_REGIMENT_NONCORE_MULTIPLIER (if it is non-colonial but uncored)
945 */
946 int32_t total = 0;
947 if(state.world.province_get_is_colonial(p)) {
948 float divisor = state.defines.pop_size_per_regiment * state.defines.pop_min_size_for_regiment_colony_multiplier;
949 float minimum = state.defines.pop_min_size_for_regiment;
950
951 for(auto pop : state.world.province_get_pop_location(p)) {
952 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers &&
953 pop.get_pop().get_is_primary_or_accepted_culture()) {
954 if(pop.get_pop().get_size() >= minimum) {
955 total += int32_t((pop.get_pop().get_size() / divisor) + 1);
956 }
957 }
958 }
959 } else if(!state.world.province_get_is_owner_core(p)) {
960 float divisor = state.defines.pop_size_per_regiment * state.defines.pop_min_size_for_regiment_noncore_multiplier;
961 float minimum = state.defines.pop_min_size_for_regiment;
962
963 for(auto pop : state.world.province_get_pop_location(p)) {
964 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers &&
965 pop.get_pop().get_is_primary_or_accepted_culture()) {
966 if(pop.get_pop().get_size() >= minimum) {
967 total += int32_t(pop.get_pop().get_size() / divisor);
968 }
969 }
970 }
971 } else {
972 float divisor = state.defines.pop_size_per_regiment;
973 float minimum = state.defines.pop_min_size_for_regiment;
974
975 for(auto pop : state.world.province_get_pop_location(p)) {
976 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers &&
977 pop.get_pop().get_is_primary_or_accepted_culture()) {
978 if(pop.get_pop().get_size() >= minimum) {
979 total += int32_t(pop.get_pop().get_size() / divisor);
980 }
981 }
982 }
983 }
984 return total;
985}
986int32_t regiments_under_construction_in_province(sys::state& state, dcon::province_id p) {
987 int32_t total = 0;
988 for(auto pop : state.world.province_get_pop_location(p)) {
989 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers) {
990 auto regs = pop.get_pop().get_province_land_construction();
991 total += int32_t(regs.end() - regs.begin());
992 }
993 }
994 return total;
995}
997 int32_t total = 0;
998 for(auto pop : state.world.province_get_pop_location(p)) {
999 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers && pop.get_pop().get_is_primary_or_accepted_culture()) {
1000 auto regs = pop.get_pop().get_province_land_construction();
1001 total += int32_t(regs.end() - regs.begin());
1002 }
1003 }
1004 return total;
1005}
1006
1007// Calculates whether province can support more regiments
1008// Considers existing regiments and construction as well
1009dcon::pop_id find_available_soldier(sys::state& state, dcon::province_id p, dcon::culture_id pop_culture) {
1010 if(state.world.province_get_is_colonial(p)) {
1011 float divisor = state.defines.pop_size_per_regiment * state.defines.pop_min_size_for_regiment_colony_multiplier;
1012 float minimum = state.defines.pop_min_size_for_regiment;
1013
1014 for(auto pop : state.world.province_get_pop_location(p)) {
1015 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers) {
1016 if(pop.get_pop().get_size() >= minimum && pop.get_pop().get_culture() == pop_culture) {
1017 auto amount = int32_t((pop.get_pop().get_size() / divisor) + 1);
1018 auto regs = pop.get_pop().get_regiment_source();
1019 auto building = pop.get_pop().get_province_land_construction();
1020
1021 if(amount > ((regs.end() - regs.begin()) + (building.end() - building.begin()))) {
1022 return pop.get_pop().id;
1023 }
1024 }
1025 }
1026 }
1027 return dcon::pop_id{};
1028 } else if(!state.world.province_get_is_owner_core(p)) {
1029 float divisor = state.defines.pop_size_per_regiment * state.defines.pop_min_size_for_regiment_noncore_multiplier;
1030 float minimum = state.defines.pop_min_size_for_regiment;
1031
1032 for(auto pop : state.world.province_get_pop_location(p)) {
1033 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers) {
1034 if(pop.get_pop().get_size() >= minimum && pop.get_pop().get_culture() == pop_culture) {
1035 auto amount = int32_t((pop.get_pop().get_size() / divisor) + 1);
1036 auto regs = pop.get_pop().get_regiment_source();
1037 auto building = pop.get_pop().get_province_land_construction();
1038
1039 if(amount > ((regs.end() - regs.begin()) + (building.end() - building.begin()))) {
1040 return pop.get_pop().id;
1041 }
1042 }
1043 }
1044 }
1045 return dcon::pop_id{};
1046 } else {
1047 float divisor = state.defines.pop_size_per_regiment;
1048 float minimum = state.defines.pop_min_size_for_regiment;
1049
1050 for(auto pop : state.world.province_get_pop_location(p)) {
1051 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers) {
1052 if(pop.get_pop().get_size() >= minimum && pop.get_pop().get_culture() == pop_culture) {
1053 auto amount = int32_t((pop.get_pop().get_size() / divisor) + 1);
1054 auto regs = pop.get_pop().get_regiment_source();
1055 auto building = pop.get_pop().get_province_land_construction();
1056
1057 if(amount > ((regs.end() - regs.begin()) + (building.end() - building.begin()))) {
1058 return pop.get_pop().id;
1059 }
1060 }
1061 }
1062 }
1063 return dcon::pop_id{};
1064 }
1065}
1066
1067dcon::pop_id find_available_soldier(sys::state& state, dcon::province_id p, bool require_accepted) {
1068 if(state.world.province_get_is_colonial(p)) {
1069 float divisor = state.defines.pop_size_per_regiment * state.defines.pop_min_size_for_regiment_colony_multiplier;
1070 float minimum = state.defines.pop_min_size_for_regiment;
1071
1072 dcon::pop_id non_preferred;
1073 for(auto pop : state.world.province_get_pop_location(p)) {
1074 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers) {
1075 if(pop.get_pop().get_size() >= minimum) {
1076 auto amount = int32_t((pop.get_pop().get_size() / divisor) + 1);
1077 auto regs = pop.get_pop().get_regiment_source();
1078 auto building = pop.get_pop().get_province_land_construction();
1079
1080 if(amount > ((regs.end() - regs.begin()) + (building.end() - building.begin()))) {
1081 if(require_accepted == pop.get_pop().get_is_primary_or_accepted_culture())
1082 return pop.get_pop().id;
1083 else
1084 non_preferred = pop.get_pop().id;
1085 }
1086 }
1087 }
1088 }
1089 return non_preferred;
1090 } else if(!state.world.province_get_is_owner_core(p)) {
1091 float divisor = state.defines.pop_size_per_regiment * state.defines.pop_min_size_for_regiment_noncore_multiplier;
1092 float minimum = state.defines.pop_min_size_for_regiment;
1093
1094 dcon::pop_id non_preferred;
1095 for(auto pop : state.world.province_get_pop_location(p)) {
1096 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers) {
1097 if(pop.get_pop().get_size() >= minimum) {
1098 auto amount = int32_t((pop.get_pop().get_size() / divisor) + 1);
1099 auto regs = pop.get_pop().get_regiment_source();
1100 auto building = pop.get_pop().get_province_land_construction();
1101
1102 if(amount > ((regs.end() - regs.begin()) + (building.end() - building.begin()))) {
1103 if(require_accepted == pop.get_pop().get_is_primary_or_accepted_culture())
1104 return pop.get_pop().id;
1105 else
1106 non_preferred = pop.get_pop().id;
1107 }
1108 }
1109 }
1110 }
1111 return non_preferred;
1112 } else {
1113 float divisor = state.defines.pop_size_per_regiment;
1114 float minimum = state.defines.pop_min_size_for_regiment;
1115
1116 dcon::pop_id non_preferred;
1117 for(auto pop : state.world.province_get_pop_location(p)) {
1118 if(pop.get_pop().get_poptype() == state.culture_definitions.soldiers) {
1119 if(pop.get_pop().get_size() >= minimum) {
1120 auto amount = int32_t((pop.get_pop().get_size() / divisor) + 1);
1121 auto regs = pop.get_pop().get_regiment_source();
1122 auto building = pop.get_pop().get_province_land_construction();
1123
1124 if(amount > ((regs.end() - regs.begin()) + (building.end() - building.begin()))) {
1125 if(require_accepted == pop.get_pop().get_is_primary_or_accepted_culture())
1126 return pop.get_pop().id;
1127 else
1128 non_preferred = pop.get_pop().id;
1129 }
1130 }
1131 }
1132 }
1133 return non_preferred;
1134 }
1135}
1136
1137int32_t mobilized_regiments_possible_from_province(sys::state& state, dcon::province_id p) {
1138 /*
1139 Mobilized regiments come only from unoccupied, non-colonial provinces.
1140 */
1141 auto fp = fatten(state.world, p);
1142 if(fp.get_is_colonial() || fp.get_nation_from_province_control() != fp.get_nation_from_province_ownership())
1143 return 0;
1144
1145 int32_t total = 0;
1146 // Mobilization size = national-modifier-to-mobilization-size + technology-modifier-to-mobilization-size
1147 auto mobilization_size =
1148 std::max(0.0f, fp.get_nation_from_province_ownership().get_modifier_values(sys::national_mod_offsets::mobilization_size));
1149
1150 for(auto pop : state.world.province_get_pop_location(p)) {
1151 /*
1152 In those provinces, mobilized regiments come from non-soldier, non-slave, poor-strata pops with a culture that is either
1153 the primary culture of the nation or an accepted culture.
1154 */
1155 if(pop_eligible_for_mobilization(state, pop.get_pop())) {
1156 /*
1157 The number of regiments these pops can provide is determined by pop-size x mobilization-size /
1158 define:POP_SIZE_PER_REGIMENT.
1159 */
1160 total += int32_t(pop.get_pop().get_size() * mobilization_size / state.defines.pop_size_per_regiment);
1161 }
1162 }
1163 return total;
1164}
1165
1166int32_t mobilized_regiments_pop_limit(sys::state& state, dcon::nation_id n) {
1167 int32_t total = 0;
1168 for(auto p : state.world.nation_get_province_ownership(n)) {
1169 if(p.get_province().get_is_colonial() == false)
1170 total += mobilized_regiments_possible_from_province(state, p.get_province());
1171 }
1172 return total;
1173}
1174
1175void update_recruitable_regiments(sys::state& state, dcon::nation_id n) {
1176 state.world.nation_set_recruitable_regiments(n, uint16_t(0));
1177 for(auto p : state.world.nation_get_province_ownership(n)) {
1178 state.world.nation_get_recruitable_regiments(n) += uint16_t(regiments_max_possible_from_province(state, p.get_province()));
1179 }
1180}
1182 state.world.execute_serial_over_nation([&](auto ids) { state.world.nation_set_recruitable_regiments(ids, ve::int_vector(0)); });
1183 state.world.for_each_province([&](dcon::province_id p) {
1184 auto owner = state.world.province_get_nation_from_province_ownership(p);
1185 if(owner) {
1186 state.world.nation_get_recruitable_regiments(owner) += uint16_t(regiments_max_possible_from_province(state, p));
1187 }
1188 });
1189}
1191 state.world.execute_serial_over_nation([&](auto ids) { state.world.nation_set_active_regiments(ids, ve::int_vector(0)); });
1192 state.world.for_each_army([&](dcon::army_id a) {
1193 auto owner = state.world.army_get_controller_from_army_control(a);
1194 if(owner) {
1195 auto regs_range = state.world.army_get_army_membership(a);
1196 auto num_regs = regs_range.end() - regs_range.begin();
1197 state.world.nation_get_active_regiments(owner) += uint16_t(num_regs);
1198 }
1199 });
1200}
1201
1203 /*
1204 We also need to know the average land unit score, which we define here as (attack + defense + national land attack modifier +
1205 national land defense modifier) x discipline
1206 */
1207 auto const max = state.military_definitions.unit_base_definitions.size();
1208 state.world.for_each_nation([&](dcon::nation_id n) {
1209 float total = 0;
1210 float count = 0;
1211
1212 auto lo_mod = state.world.nation_get_modifier_values(n, sys::national_mod_offsets::land_attack_modifier);
1213 auto ld_mod = state.world.nation_get_modifier_values(n, sys::national_mod_offsets::land_defense_modifier);
1214
1215 for(uint32_t i = 2; i < max; ++i) {
1216 dcon::unit_type_id u{dcon::unit_type_id::value_base_t(i)};
1217 if((state.world.nation_get_active_unit(n, u) || state.military_definitions.unit_base_definitions[u].active) && state.military_definitions.unit_base_definitions[u].is_land) {
1218 auto& reg_stats = state.world.nation_get_unit_stats(n, u);
1219 total += ((reg_stats.defence_or_hull + ld_mod) + (reg_stats.attack_or_gun_power + lo_mod)) *
1220 state.military_definitions.unit_base_definitions[u].discipline_or_evasion;
1221 ++count;
1222 }
1223 }
1224 state.world.nation_set_averge_land_unit_score(n, total / count);
1225 });
1226}
1228 /*
1229 To that we add for each capital ship: (hull points + national-naval-defense-modifier) x (gun power +
1230 national-naval-attack-modifier) / 250
1231 */
1232 state.world.for_each_nation([&](dcon::nation_id n) {
1233 float total = 0;
1234 auto no_mod = state.world.nation_get_modifier_values(n, sys::national_mod_offsets::naval_attack_modifier);
1235 auto nd_mod = state.world.nation_get_modifier_values(n, sys::national_mod_offsets::naval_defense_modifier);
1236
1237 for(auto nv : state.world.nation_get_navy_control(n)) {
1238 for(auto shp : nv.get_navy().get_navy_membership()) {
1239 if(state.military_definitions.unit_base_definitions[shp.get_ship().get_type()].capital) {
1240 auto& ship_stats = state.world.nation_get_unit_stats(n, shp.get_ship().get_type());
1241 total += (ship_stats.defence_or_hull + nd_mod) * (ship_stats.attack_or_gun_power + no_mod);
1242 }
1243 }
1244 }
1245 state.world.nation_set_capital_ship_score(n, total / 250.0f);
1246 });
1247}
1248
1249int32_t naval_supply_points(sys::state& state, dcon::nation_id n) {
1250 return int32_t(state.world.nation_get_naval_supply_points(n));
1251}
1252int32_t naval_supply_points_used(sys::state& state, dcon::nation_id n) {
1253 return int32_t(state.world.nation_get_used_naval_supply_points(n));
1254}
1255
1256uint32_t naval_supply_from_naval_base(sys::state& state, dcon::province_id prov, dcon::nation_id nation) {
1257 uint32_t supply = uint32_t(state.defines.naval_base_supply_score_base * (std::pow(2, (dcon::fatten(state.world, prov).get_building_level(uint8_t(economy::province_building_type::naval_base)) -1))));
1258 if(dcon::fatten(state.world, prov).get_building_level(uint8_t(economy::province_building_type::naval_base)) != 0) {
1259 return supply;
1260 } else {
1261 for(auto c : dcon::fatten(state.world, prov).get_core()) {
1262 if(c.get_identity().get_nation_from_identity_holder().id == nation && province::has_access_to_province(state, nation, prov)) {
1263 return uint32_t(state.defines.naval_base_supply_score_empty);
1264 }
1265 }
1266 if(!province::has_access_to_province(state, nation, prov)) {
1267 return uint32_t(state.defines.naval_base_supply_score_empty * state.defines.naval_base_non_core_supply_score);
1268 } else {
1269 return uint32_t(state.defines.naval_base_supply_score_empty);
1270 }
1271 }
1272}
1273
1275 /*
1276 - naval supply score: you get define:NAVAL_BASE_SUPPLY_SCORE_BASE x (2 to the power of (its-level - 1)) for each naval base or
1277 define:NAVAL_BASE_SUPPLY_SCORE_EMPTY for each state without one, multiplied by define:NAVAL_BASE_NON_CORE_SUPPLY_SCORE if it
1278 is neither a core nor connected to the capital.
1279 */
1280 state.world.for_each_nation([&](dcon::nation_id n) {
1281 auto cap_region = state.world.province_get_connected_region_id(state.world.nation_get_capital(n));
1282 float total = 0;
1283 for(auto si : state.world.nation_get_state_ownership(n)) {
1284 auto d = state.world.state_instance_get_definition(si.get_state());
1285 bool saw_coastal = false;
1286 bool nb_was_core = false;
1287 bool coast_was_core = false;
1288 int32_t nb_level = 0;
1289 for(auto p : state.world.state_definition_get_abstract_state_membership(d)) {
1290 if(p.get_province().get_nation_from_province_ownership() == n) {
1291 if(p.get_province().get_is_coast()) {
1292 saw_coastal = true;
1293 coast_was_core = coast_was_core || p.get_province().get_is_owner_core();
1294 }
1295 nb_level = std::max(nb_level, int32_t(p.get_province().get_building_level(uint8_t(economy::province_building_type::naval_base))));
1296 if(nb_level > 0)
1297 nb_was_core = p.get_province().get_is_owner_core();
1298 }
1299 }
1300 bool connected = si.get_state().get_capital().get_connected_region_id() == cap_region;
1301
1302 if(saw_coastal) {
1303 if(nb_level > 0) {
1304 if(nb_was_core || connected)
1305 total += state.defines.naval_base_supply_score_base * float(1 << (nb_level - 1));
1306 else
1307 total += state.defines.naval_base_supply_score_base * float(1 << (nb_level - 1)) * state.defines.naval_base_non_core_supply_score;
1308 } else {
1309 if(coast_was_core || connected)
1310 total += state.defines.naval_base_supply_score_empty;
1311 else
1312 total += 1.0f;
1313 }
1314 }
1315 }
1316 state.world.nation_set_naval_supply_points(n, uint16_t(total));
1317 });
1318
1319 /*
1320 - ships consume naval base supply at their supply_consumption_score. Going over the naval supply score comes with various
1321 penalties (described elsewhere).
1322 */
1323 state.world.for_each_nation([&](dcon::nation_id n) {
1324 float total = 0;
1325 for(auto nv : state.world.nation_get_navy_control(n)) {
1326 for(auto shp : nv.get_navy().get_navy_membership()) {
1327 total += state.military_definitions.unit_base_definitions[shp.get_ship().get_type()].supply_consumption_score;
1328 }
1329 }
1330 state.world.nation_set_used_naval_supply_points(n, uint16_t(total));
1331 });
1332}
1333
1334float mobilization_size(sys::state const& state, dcon::nation_id n) {
1335 // Mobilization size = national-modifier-to-mobilization-size + technology-modifier-to-mobilization-size
1336 return state.world.nation_get_modifier_values(n, sys::national_mod_offsets::mobilization_size);
1337}
1338
1339ve::fp_vector ve_mobilization_size(sys::state const& state, ve::tagged_vector<dcon::nation_id> nations) {
1340 // Mobilization size = national-modifier-to-mobilization-size + technology-modifier-to-mobilization-size
1341 return state.world.nation_get_modifier_values(nations, sys::national_mod_offsets::mobilization_size);
1342}
1343
1344float mobilization_impact(sys::state const& state, dcon::nation_id n) {
1345 // Mobilization impact = 1 - mobilization-size x (national-mobilization-economy-impact-modifier +
1346 // technology-mobilization-impact-modifier), to a minimum of zero.
1347 return std::clamp(
1348 1.0f - mobilization_size(state, n)
1349 * state.world.nation_get_modifier_values(n, sys::national_mod_offsets::mobilization_impact),
1350 0.0f,
1351 1.0f
1352 );
1353}
1354
1355ve::fp_vector ve_mobilization_impact(sys::state const& state, ve::tagged_vector<dcon::nation_id> nations) {
1356 auto size = ve_mobilization_size(state, nations);
1357 auto impact = (1.f - size) * state.world.nation_get_modifier_values(nations, sys::national_mod_offsets::mobilization_impact);
1358 return ve::min(ve::max(impact, 0.f), 1.f);
1359}
1360
1362 for(auto n : state.world.in_nation) {
1363 if(state.current_date == n.get_reparations_until()) {
1364 for(auto urel : n.get_unilateral_relationship_as_source()) {
1365 urel.set_reparations(false);
1366 }
1367 }
1368 auto current_cbs = n.get_available_cbs();
1369 for(uint32_t i = current_cbs.size(); i-- > 0;) {
1370 if(current_cbs[i].expiration && current_cbs[i].expiration <= state.current_date) {
1371 current_cbs.remove_at(i);
1372 }
1373 }
1374
1375 // check for cancellation
1376 if(n.get_constructing_cb_type()) {
1377 /*
1378 CBs that become invalid (the nations involved no longer satisfy the conditions or enter into a war with each other)
1379 are canceled (and the player should be notified in this event).
1380 */
1381 auto target = n.get_constructing_cb_target();
1382 if(military::are_at_war(state, n, target) || state.world.nation_get_owned_province_count(target) == 0 ||
1383 !cb_conditions_satisfied(state, n, target, n.get_constructing_cb_type())) {
1384 if(n == state.local_player_nation) {
1386 [](sys::state& state, text::layout_base& contents) {
1387 text::add_line(state, contents, "msg_fab_canceled_1");
1388 },
1389 "msg_fab_canceled_title",
1390 n, dcon::nation_id{}, dcon::nation_id{},
1392 });
1393 }
1394
1395 n.set_constructing_cb_is_discovered(false);
1396 n.set_constructing_cb_progress(0.0f);
1397 n.set_constructing_cb_target(dcon::nation_id{});
1398 n.set_constructing_cb_type(dcon::cb_type_id{});
1399 n.set_constructing_cb_target_state(dcon::state_definition_id{});
1400 }
1401 }
1402
1403 if(n.get_constructing_cb_type() && !nations::is_involved_in_crisis(state, n)) {
1404 /*
1405 CB fabrication by a nation is paused while that nation is in a crisis (nor do events related to CB fabrication
1406 happen). CB fabrication is advanced by points equal to: define:CB_GENERATION_BASE_SPEED x cb-type-construction-speed x
1407 (national-cb-construction-speed-modifiers + technology-cb-construction-speed-modifier + 1).
1408 */
1409
1410 auto eff_speed = state.defines.cb_generation_base_speed * n.get_constructing_cb_type().get_construction_speed() * (n.get_modifier_values(sys::national_mod_offsets::cb_generation_speed_modifier) + 1.0f);
1411
1412 n.get_constructing_cb_progress() += std::max(eff_speed, 0.0f);
1413
1414 /*
1415 Each day, a fabricating CB has a define:CB_DETECTION_CHANCE_BASE out of 1000 chance to be detected. If discovered, the
1416 fabricating country gains the infamy for that war goal x the fraction of fabrication remaining. If discovered
1417 relations between the two nations are changed by define:ON_CB_DETECTED_RELATION_CHANGE. If discovered, any states with
1418 a flashpoint in the target nation will have their tension increase by define:TENSION_ON_CB_DISCOVERED
1419 */
1420 if(!n.get_constructing_cb_is_discovered() && eff_speed > 0.0f) {
1421 auto val = rng::get_random(state, uint32_t((n.id.index() << 3) + 5)) % 1000;
1422 if(val <= uint32_t(state.defines.cb_detection_chance_base)) {
1423 execute_cb_discovery(state, n);
1424 n.set_constructing_cb_is_discovered(true);
1425 }
1426 }
1427
1428 /*
1429 When fabrication progress reaches 100, the CB will remain valid for define:CREATED_CB_VALID_TIME months (so x30 days
1430 for us). Note that pending CBs have their target nation fixed, but all other parameters are flexible.
1431 */
1432 if(n.get_constructing_cb_progress() >= 100.0f) {
1433 add_cb(state, n, n.get_constructing_cb_type(), n.get_constructing_cb_target(), n.get_constructing_cb_target_state());
1434
1435 if(n == state.local_player_nation) {
1437 [t = n.get_constructing_cb_target(), c = n.get_constructing_cb_type()](sys::state& state, text::layout_base& contents) {
1438 text::add_line(state, contents, "msg_fab_finished_1", text::variable_type::x, state.world.cb_type_get_name(c), text::variable_type::y, t);
1439 },
1440 "msg_fab_finished_title",
1441 n, dcon::nation_id{}, dcon::nation_id{},
1443 });
1444 }
1445
1446 n.set_constructing_cb_is_discovered(false);
1447 n.set_constructing_cb_progress(0.0f);
1448 n.set_constructing_cb_target(dcon::nation_id{});
1449 n.set_constructing_cb_type(dcon::cb_type_id{});
1450 n.set_constructing_cb_target_state(dcon::state_definition_id{});
1451 }
1452 }
1453 }
1454}
1455
1456void add_cb(sys::state& state, dcon::nation_id n, dcon::cb_type_id cb, dcon::nation_id target, dcon::state_definition_id target_state = dcon::state_definition_id{}) {
1457 auto current_cbs = state.world.nation_get_available_cbs(n);
1458 current_cbs.push_back(military::available_cb{ state.current_date + int32_t(state.defines.created_cb_valid_time) * 30, target, cb, target_state });
1459}
1460
1461float cb_infamy_country_modifier(sys::state& state, dcon::nation_id target) {
1462 float total_population = 0.f;
1463 auto total_countries = 0;
1464
1465 auto target_civilized = state.world.nation_get_is_civilized(target);
1466
1467 for(auto n : state.world.in_nation) {
1468 /* Use correct reference group for average country population calculation.
1469 Civs are compared vs civs and uncivs vs uncivs as they have just too big a difference in averages.*/
1470 auto is_civilized = state.world.nation_get_is_civilized(n);
1471
1472 if(target_civilized != is_civilized) {
1473 continue;
1474 }
1475 auto n_pop = state.world.nation_get_demographics(n, demographics::total);
1476 total_population += n_pop;
1477 total_countries += (n_pop > 0) ? 1 : 0;
1478 }
1479
1480 float country_population = state.world.nation_get_demographics(target, demographics::total);
1481
1482 return std::clamp(math::sqrt(country_population / (total_population / total_countries)), 0.5f, 2.0f);
1483}
1484
1485float cb_infamy_state_modifier(sys::state& state, dcon::nation_id target, dcon::state_definition_id cb_state) {
1486 if(cb_state == dcon::state_definition_id{}) {
1487 // assert(false && "This shouldn't happen");
1488 return 2.0f; // Default cap for infamy price
1489 }
1490
1491 float total_population = 0.f;
1492 auto total_states = 0;
1493 float state_population = 0;
1494
1495 auto target_civilized = state.world.nation_get_is_civilized(target);
1496
1497 for(auto s : state.world.in_state_instance) {
1498 /* Use correct reference group for average state population calculation.
1499 Civs are compared vs civs and uncivs vs uncivs as they have just too big a difference in averages.*/
1500 auto n = state.world.state_instance_get_nation_from_state_ownership(s);
1501 auto is_civilized = state.world.nation_get_is_civilized(n);
1502
1503 if(target_civilized != is_civilized) {
1504 continue;
1505 }
1506
1507 auto s_pop = s.get_demographics(demographics::total);
1508 total_population += s_pop;
1509 total_states += (s_pop > 0) ? 1 : 0;
1510
1511 if(s.get_definition() == cb_state) {
1512 state_population = s_pop;
1513 }
1514 }
1515
1516 return std::clamp(math::sqrt(state_population / (total_population / total_states)), 0.5f, 2.0f);
1517}
1518
1519float cb_infamy(sys::state& state, dcon::cb_type_id t, dcon::nation_id target, dcon::state_definition_id cb_state) {
1520 float total = 0.0f;
1521 auto bits = state.world.cb_type_get_type_bits(t);
1522
1523 if((bits & cb_flag::po_remove_prestige) != 0) {
1524 total += state.defines.infamy_prestige;
1525 }
1526 if((bits & cb_flag::po_clear_union_sphere) != 0) {
1527 total += state.defines.infamy_clear_union_sphere * cb_infamy_country_modifier(state, target);
1528 }
1529 if((bits & cb_flag::po_gunboat) != 0) {
1530 total += state.defines.infamy_gunboat * cb_infamy_country_modifier(state, target);
1531 }
1532 if((bits & cb_flag::po_annex) != 0) {
1533 total += state.defines.infamy_annex * cb_infamy_country_modifier(state, target);
1534 }
1535 if((bits & cb_flag::po_make_puppet) != 0) {
1536 total += state.defines.infamy_make_puppet * cb_infamy_country_modifier(state, target);
1537 }
1538 if((bits & cb_flag::po_release_puppet) != 0) {
1539 total += state.defines.infamy_release_puppet * cb_infamy_country_modifier(state, target);
1540 }
1541 if((bits & cb_flag::po_status_quo) != 0) {
1542 total += state.defines.infamy_status_quo;
1543 }
1544 if((bits & cb_flag::po_install_communist_gov_type) != 0) {
1545 total += state.defines.infamy_install_communist_gov_type * cb_infamy_country_modifier(state, target);
1546 }
1547 if((bits & cb_flag::po_uninstall_communist_gov_type) != 0) {
1548 total += state.defines.infamy_uninstall_communist_gov_type * cb_infamy_country_modifier(state, target);
1549 }
1550 if((bits & cb_flag::po_remove_cores) != 0) {
1551 total += state.defines.infamy_remove_cores * cb_infamy_country_modifier(state, target);
1552 }
1553 if((bits & cb_flag::po_colony) != 0) {
1554 total += state.defines.infamy_colony * cb_infamy_country_modifier(state, target);
1555 }
1556 if((bits & cb_flag::po_destroy_forts) != 0) {
1557 total += state.defines.infamy_destroy_forts * cb_infamy_country_modifier(state, target);
1558 }
1559 if((bits & cb_flag::po_destroy_naval_bases) != 0) {
1560 total += state.defines.infamy_destroy_naval_bases * cb_infamy_country_modifier(state, target);
1561 }
1562 if((bits & cb_flag::po_add_to_sphere) != 0) {
1563 total += state.defines.infamy_add_to_sphere * cb_infamy_country_modifier(state, target);
1564 }
1565 if((bits & cb_flag::po_disarmament) != 0) {
1566 total += state.defines.infamy_disarmament * cb_infamy_country_modifier(state, target);
1567 }
1568 if((bits & cb_flag::po_reparations) != 0) {
1569 total += state.defines.infamy_reparations * cb_infamy_country_modifier(state, target);
1570 }
1571
1572 auto infamy_state_mod = 1.0f;
1574 infamy_state_mod = cb_infamy_state_modifier(state, target, cb_state);
1575 }
1576
1577 if((bits & cb_flag::po_demand_state) != 0) {
1578 total += state.defines.infamy_demand_state * infamy_state_mod;
1579 }
1580 if((bits & cb_flag::po_transfer_provinces) != 0) {
1581 total += state.defines.infamy_transfer_provinces * infamy_state_mod;
1582 }
1583
1584 return total * state.world.cb_type_get_badboy_factor(t);
1585}
1586
1587float truce_break_cb_prestige_cost(sys::state& state, dcon::cb_type_id t) {
1588 float total = 0.0f;
1589 auto bits = state.world.cb_type_get_type_bits(t);
1590
1591 if((bits & cb_flag::po_clear_union_sphere) != 0) {
1592 total += state.defines.breaktruce_prestige_clear_union_sphere;
1593 }
1594 if((bits & cb_flag::po_gunboat) != 0) {
1595 total += state.defines.breaktruce_prestige_gunboat;
1596 }
1597 if((bits & cb_flag::po_annex) != 0) {
1598 total += state.defines.breaktruce_prestige_annex;
1599 }
1600 if((bits & cb_flag::po_demand_state) != 0) {
1601 total += state.defines.breaktruce_prestige_demand_state;
1602 }
1603 if((bits & cb_flag::po_add_to_sphere) != 0) {
1604 total += state.defines.breaktruce_prestige_add_to_sphere;
1605 }
1606 if((bits & cb_flag::po_disarmament) != 0) {
1607 total += state.defines.breaktruce_prestige_disarmament;
1608 }
1609 if((bits & cb_flag::po_reparations) != 0) {
1610 total += state.defines.breaktruce_prestige_reparations;
1611 }
1612 if((bits & cb_flag::po_transfer_provinces) != 0) {
1613 total += state.defines.breaktruce_prestige_transfer_provinces;
1614 }
1615 if((bits & cb_flag::po_remove_prestige) != 0) {
1616 total += state.defines.breaktruce_prestige_prestige;
1617 }
1618 if((bits & cb_flag::po_make_puppet) != 0) {
1619 total += state.defines.breaktruce_prestige_make_puppet;
1620 }
1621 if((bits & cb_flag::po_release_puppet) != 0) {
1622 total += state.defines.breaktruce_prestige_release_puppet;
1623 }
1624 if((bits & cb_flag::po_status_quo) != 0) {
1625 total += state.defines.breaktruce_prestige_status_quo;
1626 }
1627 if((bits & cb_flag::po_install_communist_gov_type) != 0) {
1628 total += state.defines.breaktruce_prestige_install_communist_gov_type;
1629 }
1630 if((bits & cb_flag::po_uninstall_communist_gov_type) != 0) {
1631 total += state.defines.breaktruce_prestige_uninstall_communist_gov_type;
1632 }
1633 if((bits & cb_flag::po_remove_cores) != 0) {
1634 total += state.defines.breaktruce_prestige_remove_cores;
1635 }
1636 if((bits & cb_flag::po_colony) != 0) {
1637 total += state.defines.breaktruce_prestige_colony;
1638 }
1639 if((bits & cb_flag::po_destroy_forts) != 0) {
1640 total += state.defines.breaktruce_prestige_destroy_forts;
1641 }
1642 if((bits & cb_flag::po_destroy_naval_bases) != 0) {
1643 total += state.defines.breaktruce_prestige_destroy_naval_bases;
1644 }
1645
1646 return total * state.world.cb_type_get_break_truce_prestige_factor(t);
1647}
1648float truce_break_cb_militancy(sys::state& state, dcon::cb_type_id t) {
1649 float total = 0.0f;
1650 auto bits = state.world.cb_type_get_type_bits(t);
1651
1652 if((bits & cb_flag::po_clear_union_sphere) != 0) {
1653 total += state.defines.breaktruce_militancy_clear_union_sphere;
1654 }
1655 if((bits & cb_flag::po_gunboat) != 0) {
1656 total += state.defines.breaktruce_militancy_gunboat;
1657 }
1658 if((bits & cb_flag::po_annex) != 0) {
1659 total += state.defines.breaktruce_militancy_annex;
1660 }
1661 if((bits & cb_flag::po_demand_state) != 0) {
1662 total += state.defines.breaktruce_militancy_demand_state;
1663 }
1664 if((bits & cb_flag::po_add_to_sphere) != 0) {
1665 total += state.defines.breaktruce_militancy_add_to_sphere;
1666 }
1667 if((bits & cb_flag::po_disarmament) != 0) {
1668 total += state.defines.breaktruce_militancy_disarmament;
1669 }
1670 if((bits & cb_flag::po_reparations) != 0) {
1671 total += state.defines.breaktruce_militancy_reparations;
1672 }
1673 if((bits & cb_flag::po_transfer_provinces) != 0) {
1674 total += state.defines.breaktruce_militancy_transfer_provinces;
1675 }
1676 if((bits & cb_flag::po_remove_prestige) != 0) {
1677 total += state.defines.breaktruce_militancy_prestige;
1678 }
1679 if((bits & cb_flag::po_make_puppet) != 0) {
1680 total += state.defines.breaktruce_militancy_make_puppet;
1681 }
1682 if((bits & cb_flag::po_release_puppet) != 0) {
1683 total += state.defines.breaktruce_militancy_release_puppet;
1684 }
1685 if((bits & cb_flag::po_status_quo) != 0) {
1686 total += state.defines.breaktruce_militancy_status_quo;
1687 }
1688 if((bits & cb_flag::po_install_communist_gov_type) != 0) {
1689 total += state.defines.breaktruce_militancy_install_communist_gov_type;
1690 }
1691 if((bits & cb_flag::po_uninstall_communist_gov_type) != 0) {
1692 total += state.defines.breaktruce_militancy_uninstall_communist_gov_type;
1693 }
1694 if((bits & cb_flag::po_remove_cores) != 0) {
1695 total += state.defines.breaktruce_militancy_remove_cores;
1696 }
1697 if((bits & cb_flag::po_colony) != 0) {
1698 total += state.defines.breaktruce_militancy_colony;
1699 }
1700 if((bits & cb_flag::po_destroy_forts) != 0) {
1701 total += state.defines.breaktruce_militancy_destroy_forts;
1702 }
1703 if((bits & cb_flag::po_destroy_naval_bases) != 0) {
1704 total += state.defines.breaktruce_militancy_destroy_naval_bases;
1705 }
1706
1707 return total * state.world.cb_type_get_break_truce_militancy_factor(t);
1708}
1709float truce_break_cb_infamy(sys::state& state, dcon::cb_type_id t, dcon::nation_id target, dcon::state_definition_id cb_state) {
1710 float total = 0.0f;
1711 auto bits = state.world.cb_type_get_type_bits(t);
1712
1713 if((bits & cb_flag::po_clear_union_sphere) != 0) {
1714 total += state.defines.breaktruce_infamy_clear_union_sphere * cb_infamy_country_modifier(state, target);
1715 }
1716 if((bits & cb_flag::po_gunboat) != 0) {
1717 total += state.defines.breaktruce_infamy_gunboat * cb_infamy_country_modifier(state, target);
1718 }
1719 if((bits & cb_flag::po_annex) != 0) {
1720 total += state.defines.breaktruce_infamy_annex * cb_infamy_country_modifier(state, target);
1721 }
1722 if((bits & cb_flag::po_demand_state) != 0) {
1723 total += state.defines.breaktruce_infamy_demand_state * cb_infamy_state_modifier(state, target, cb_state);
1724 }
1725 if((bits & cb_flag::po_add_to_sphere) != 0) {
1726 total += state.defines.breaktruce_infamy_add_to_sphere * cb_infamy_country_modifier(state, target);
1727 }
1728 if((bits & cb_flag::po_disarmament) != 0) {
1729 total += state.defines.breaktruce_infamy_disarmament * cb_infamy_country_modifier(state, target);
1730 }
1731 if((bits & cb_flag::po_reparations) != 0) {
1732 total += state.defines.breaktruce_infamy_reparations * cb_infamy_country_modifier(state, target);
1733 }
1734 if((bits & cb_flag::po_transfer_provinces) != 0) {
1735 total += state.defines.breaktruce_infamy_transfer_provinces * cb_infamy_state_modifier(state, target, cb_state);
1736 }
1737 if((bits & cb_flag::po_remove_prestige) != 0) {
1738 total += state.defines.breaktruce_infamy_prestige;
1739 }
1740 if((bits & cb_flag::po_make_puppet) != 0) {
1741 total += state.defines.breaktruce_infamy_make_puppet * cb_infamy_country_modifier(state, target);
1742 }
1743 if((bits & cb_flag::po_release_puppet) != 0) {
1744 total += state.defines.breaktruce_infamy_release_puppet * cb_infamy_country_modifier(state, target);
1745 }
1746 if((bits & cb_flag::po_status_quo) != 0) {
1747 total += state.defines.breaktruce_infamy_status_quo;
1748 }
1749 if((bits & cb_flag::po_install_communist_gov_type) != 0) {
1750 total += state.defines.breaktruce_infamy_install_communist_gov_type * cb_infamy_country_modifier(state, target);
1751 }
1752 if((bits & cb_flag::po_uninstall_communist_gov_type) != 0) {
1753 total += state.defines.breaktruce_infamy_uninstall_communist_gov_type * cb_infamy_country_modifier(state, target);
1754 }
1755 if((bits & cb_flag::po_remove_cores) != 0) {
1756 total += state.defines.breaktruce_infamy_remove_cores * cb_infamy_country_modifier(state, target);
1757 }
1758 if((bits & cb_flag::po_colony) != 0) {
1759 total += state.defines.breaktruce_infamy_colony * cb_infamy_country_modifier(state, target);
1760 }
1761 if((bits & cb_flag::po_destroy_forts) != 0) {
1762 total += state.defines.breaktruce_infamy_destroy_forts * cb_infamy_country_modifier(state, target);
1763 }
1764 if((bits & cb_flag::po_destroy_naval_bases) != 0) {
1765 total += state.defines.breaktruce_infamy_destroy_naval_bases * cb_infamy_country_modifier(state, target);
1766 }
1767
1768 return total * state.world.cb_type_get_break_truce_infamy_factor(t);
1769}
1770
1771int32_t province_point_cost(sys::state& state, dcon::province_id p, dcon::nation_id n) {
1772 /*
1773 All provinces have a base value of 1. For non colonial provinces: each level of naval base increases its value by 1. If it is
1774 a state capital, its value increases by 1 for every factory in the state (factory level does not matter). Provinces get 1
1775 point per fort level. This value is the doubled for non-overseas provinces where the owner has a core. It is then tripled for
1776 the nation's capital province.
1777 */
1778 int32_t total = 1;
1779 if(!state.world.province_get_is_colonial(p)) {
1780 total += state.world.province_get_building_level(p, uint8_t(economy::province_building_type::naval_base));
1781 }
1782 auto fac_range = state.world.province_get_factory_location(p);
1783 total += int32_t(fac_range.end() - fac_range.begin());
1784 total += state.world.province_get_building_level(p, uint8_t(economy::province_building_type::fort));
1785
1786 auto owner_cap = state.world.nation_get_capital(n);
1787 auto overseas = (state.world.province_get_continent(p) != state.world.province_get_continent(owner_cap)) &&
1788 (state.world.province_get_connected_region_id(p) != state.world.province_get_connected_region_id(owner_cap));
1789
1790 if(state.world.province_get_is_owner_core(p) && !overseas) {
1791 total *= 2;
1792 }
1793 if(state.world.nation_get_capital(n) == p) {
1794 total *= 3;
1795 }
1796 return total;
1797}
1798
1799int32_t peace_cost(sys::state& state, dcon::war_id war, dcon::cb_type_id wargoal, dcon::nation_id from, dcon::nation_id target,
1800 dcon::nation_id secondary_nation, dcon::state_definition_id wargoal_state, dcon::national_identity_id wargoal_tag) {
1801
1802 /*
1803 Each war goal has a value that determines how much it is worth in a peace offer (and peace offers are capped at 100 points of
1804 war goals). Great war obligatory war goals cost 0. Then we iterate over the po tags and sum up the peace cost for each. Some
1805 tags have a fixed cost in the defines, such as define:PEACE_COST_RELEASE_PUPPET. For anything that conquers provinces directly
1806 (ex: demand state), each province is worth its value relative to the total cost of provinces owned by the target (see below)
1807 x 2.8. For po_clear_union_sphere, the cost is defines:PEACE_COST_CLEAR_UNION_SPHERE x the number of nations that will be
1808 affected. If the war is a great war, this cost is multiplied by defines:GW_WARSCORE_COST_MOD. If it is a great war, world wars
1809 are enabled, and the war score is at least defines:GW_WARSCORE_2_THRESHOLD, the cost is multiplied by
1810 defines:GW_WARSCORE_COST_MOD_2 instead. The peace cost of a single war goal is capped at 100.0.
1811 */
1812
1813 float total = 0.0f;
1814 auto bits = state.world.cb_type_get_type_bits(wargoal);
1815
1816 if((bits & cb_flag::great_war_obligatory) != 0) {
1817 return 0;
1818 }
1819
1820 if((bits & cb_flag::po_gunboat) != 0) {
1821 total += state.defines.peace_cost_gunboat;
1822 }
1823 if((bits & cb_flag::po_annex) != 0) {
1824 total += state.defines.peace_cost_annex;
1825 }
1826 if((bits & cb_flag::po_add_to_sphere) != 0) {
1827 total += state.defines.peace_cost_add_to_sphere;
1828 }
1829 if((bits & cb_flag::po_disarmament) != 0) {
1830 total += state.defines.peace_cost_disarmament;
1831 }
1832 if((bits & cb_flag::po_reparations) != 0) {
1833 total += state.defines.peace_cost_reparations;
1834 }
1835 if((bits & cb_flag::po_remove_prestige) != 0) {
1836 total += state.defines.peace_cost_prestige;
1837 }
1838 if((bits & cb_flag::po_make_puppet) != 0) {
1839 total += state.defines.peace_cost_make_puppet;
1840 }
1841 if((bits & cb_flag::po_release_puppet) != 0) {
1842 total += state.defines.peace_cost_release_puppet;
1843 }
1844 if((bits & cb_flag::po_status_quo) != 0) {
1845 total += state.defines.peace_cost_status_quo;
1846 }
1847 if((bits & cb_flag::po_install_communist_gov_type) != 0) {
1848 total += state.defines.peace_cost_install_communist_gov_type;
1849 }
1850 if((bits & cb_flag::po_uninstall_communist_gov_type) != 0) {
1851 total += state.defines.peace_cost_uninstall_communist_gov_type;
1852 }
1853 if((bits & cb_flag::po_remove_cores) != 0) {
1854 total += state.defines.peace_cost_remove_cores;
1855 }
1856 if((bits & cb_flag::po_colony) != 0) {
1857 total += state.defines.peace_cost_colony;
1858 }
1859 if((bits & cb_flag::po_destroy_forts) != 0) {
1860 total += state.defines.peace_cost_destroy_forts;
1861 }
1862 if((bits & cb_flag::po_destroy_naval_bases) != 0) {
1863 total += state.defines.peace_cost_destroy_naval_bases;
1864 }
1865
1866 if((bits & cb_flag::po_clear_union_sphere) != 0) {
1867 auto from_group = state.world.nation_get_primary_culture(from).get_group_from_culture_group_membership();
1868 for(auto target_sphere : state.world.nation_get_gp_relationship_as_great_power(target)) {
1869 if((target_sphere.get_status() & nations::influence::level_mask) == nations::influence::level_in_sphere &&
1870 target_sphere.get_influence_target().get_primary_culture().get_group_from_culture_group_membership() == from_group) {
1871
1872 total += state.defines.peace_cost_clear_union_sphere;
1873 }
1874 }
1875 }
1876 if((bits & (cb_flag::po_transfer_provinces | cb_flag::po_demand_state)) != 0) {
1877 int32_t sum_target_prov_values = 0;
1878 for(auto prv : state.world.nation_get_province_ownership(target)) {
1879 sum_target_prov_values += province_point_cost(state, prv.get_province(), target);
1880 }
1881 auto secondary = secondary_nation ? secondary_nation : state.world.national_identity_get_nation_from_identity_holder(wargoal_tag);
1882 bool is_lib = (bits & cb_flag::po_transfer_provinces) != 0;
1883
1884 if(sum_target_prov_values > 0) {
1885 if(auto allowed_states = state.world.cb_type_get_allowed_states(wargoal); allowed_states && (bits & cb_flag::all_allowed_states) != 0) {
1886 for(auto si : state.world.nation_get_state_ownership(target)) {
1887 if(trigger::evaluate(state, allowed_states, trigger::to_generic(si.get_state().id), trigger::to_generic(from),
1888 is_lib ? trigger::to_generic(secondary) : trigger::to_generic(from))) {
1889
1890 province::for_each_province_in_state_instance(state, si.get_state(), [&](dcon::province_id tprov) {
1891 total += 280.0f * float(province_point_cost(state, tprov, target)) / float(sum_target_prov_values);
1892 });
1893 }
1894 }
1895 } else {
1896 for(auto tprov : state.world.state_definition_get_abstract_state_membership(wargoal_state)) {
1897 if(tprov.get_province().get_nation_from_province_ownership() == target)
1898 total += 280.0f * float(province_point_cost(state, tprov.get_province(), target)) / float(sum_target_prov_values);
1899 }
1900 }
1901 }
1902 }
1903
1904 if(state.world.war_get_is_great(war)) {
1905 bool is_attacker = false;
1906 for(auto par : state.world.war_get_war_participant(war)) {
1907 if(par.get_nation() == from) {
1908 is_attacker = par.get_is_attacker();
1909 break;
1910 }
1911 }
1912 if(state.military_definitions.world_wars_enabled &&
1913 ((is_attacker && primary_warscore(state, war) >= state.defines.gw_warscore_2_threshold) ||
1914 (!is_attacker && primary_warscore(state, war) <= -state.defines.gw_warscore_2_threshold))) {
1915 total *= state.defines.gw_warscore_cost_mod_2;
1916 } else {
1917 total *= state.defines.gw_warscore_cost_mod;
1918 }
1919 }
1920
1921 return std::min(int32_t(total * state.world.cb_type_get_peace_cost_factor(wargoal)), 100);
1922}
1923
1924int32_t cost_of_peace_offer(sys::state& state, dcon::peace_offer_id offer) {
1925 auto war = state.world.peace_offer_get_war_from_war_settlement(offer);
1926 int32_t total = 0;
1927 for(auto wg : state.world.peace_offer_get_peace_offer_item(offer)) {
1928 total += peace_cost(state, war, wg.get_wargoal().get_type(), wg.get_wargoal().get_added_by(),
1929 wg.get_wargoal().get_target_nation(), wg.get_wargoal().get_secondary_nation(), wg.get_wargoal().get_associated_state(),
1930 wg.get_wargoal().get_associated_tag());
1931 }
1932 return total;
1933}
1934int32_t peace_offer_truce_months(sys::state& state, dcon::peace_offer_id offer) {
1935 int32_t max_months = 0;
1936 for(auto wg : state.world.peace_offer_get_peace_offer_item(offer)) {
1937 max_months = std::max(max_months, int32_t(wg.get_wargoal().get_type().get_truce_months()));
1938 }
1939 return max_months + int32_t(state.defines.base_truce_months);
1940}
1941int32_t attacker_peace_cost(sys::state& state, dcon::war_id war) {
1942 int32_t total = 0;
1943 for(auto wg : state.world.war_get_wargoals_attached(war)) {
1944 if(is_attacker(state, war, wg.get_wargoal().get_added_by())) {
1945 total += peace_cost(state, war, wg.get_wargoal().get_type(), wg.get_wargoal().get_added_by(),
1946 wg.get_wargoal().get_target_nation(), wg.get_wargoal().get_secondary_nation(), wg.get_wargoal().get_associated_state(),
1947 wg.get_wargoal().get_associated_tag());
1948 }
1949 }
1950 return total;
1951}
1952int32_t defender_peace_cost(sys::state& state, dcon::war_id war) {
1953 int32_t total = 0;
1954 for(auto wg : state.world.war_get_wargoals_attached(war)) {
1955 if(!is_attacker(state, war, wg.get_wargoal().get_added_by())) {
1956 total += peace_cost(state, war, wg.get_wargoal().get_type(), wg.get_wargoal().get_added_by(),
1957 wg.get_wargoal().get_target_nation(), wg.get_wargoal().get_secondary_nation(), wg.get_wargoal().get_associated_state(),
1958 wg.get_wargoal().get_associated_tag());
1959 }
1960 }
1961 return total;
1962}
1963
1964float successful_cb_prestige(sys::state& state, dcon::cb_type_id t, dcon::nation_id actor) {
1965 float total = 0.0f;
1966 auto bits = state.world.cb_type_get_type_bits(t);
1967 float actor_prestige = nations::prestige_score(state, actor);
1968
1969 if((bits & cb_flag::po_clear_union_sphere) != 0) {
1970 total += std::max(state.defines.prestige_clear_union_sphere * actor_prestige, state.defines.prestige_clear_union_sphere_base);
1971 }
1972 if((bits & cb_flag::po_gunboat) != 0) {
1973 total += std::max(state.defines.prestige_gunboat * actor_prestige, state.defines.prestige_gunboat_base);
1974 }
1975 if((bits & cb_flag::po_annex) != 0) {
1976 total += std::max(state.defines.prestige_annex * actor_prestige, state.defines.prestige_annex_base);
1977 }
1978 if((bits & cb_flag::po_demand_state) != 0) {
1979 total += std::max(state.defines.prestige_demand_state * actor_prestige, state.defines.prestige_demand_state_base);
1980 }
1981 if((bits & cb_flag::po_add_to_sphere) != 0) {
1982 total += std::max(state.defines.prestige_add_to_sphere * actor_prestige, state.defines.prestige_add_to_sphere_base);
1983 }
1984 if((bits & cb_flag::po_disarmament) != 0) {
1985 total += std::max(state.defines.prestige_disarmament * actor_prestige, state.defines.prestige_disarmament_base);
1986 }
1987 if((bits & cb_flag::po_reparations) != 0) {
1988 total += std::max(state.defines.prestige_reparations * actor_prestige, state.defines.prestige_reparations_base);
1989 }
1990 if((bits & cb_flag::po_transfer_provinces) != 0) {
1991 total += std::max(state.defines.prestige_transfer_provinces * actor_prestige, state.defines.prestige_transfer_provinces_base);
1992 }
1993 if((bits & cb_flag::po_remove_prestige) != 0) {
1994 total += std::max(state.defines.prestige_prestige * actor_prestige, state.defines.prestige_prestige_base);
1995 }
1996 if((bits & cb_flag::po_make_puppet) != 0) {
1997 total += std::max(state.defines.prestige_make_puppet * actor_prestige, state.defines.prestige_make_puppet_base);
1998 }
1999 if((bits & cb_flag::po_release_puppet) != 0) {
2000 total += std::max(state.defines.prestige_release_puppet * actor_prestige, state.defines.prestige_release_puppet_base);
2001 }
2002 if((bits & cb_flag::po_status_quo) != 0) {
2003 total += std::max(state.defines.prestige_status_quo * actor_prestige, state.defines.prestige_status_quo_base);
2004 }
2005 if((bits & cb_flag::po_install_communist_gov_type) != 0) {
2006 total += std::max(state.defines.prestige_install_communist_gov_type * actor_prestige,
2007 state.defines.prestige_install_communist_gov_type_base);
2008 }
2009 if((bits & cb_flag::po_uninstall_communist_gov_type) != 0) {
2010 total += std::max(state.defines.prestige_uninstall_communist_gov_type * actor_prestige,
2011 state.defines.prestige_uninstall_communist_gov_type_base);
2012 }
2013 if((bits & cb_flag::po_remove_cores) != 0) {
2014 total += std::max(state.defines.prestige_remove_cores * actor_prestige, state.defines.prestige_remove_cores_base);
2015 }
2016 if((bits & cb_flag::po_colony) != 0) {
2017 total += std::max(state.defines.prestige_colony * actor_prestige, state.defines.prestige_colony_base);
2018 }
2019 if((bits & cb_flag::po_destroy_forts) != 0) {
2020 total += std::max(state.defines.prestige_destroy_forts * actor_prestige, state.defines.prestige_destroy_forts_base);
2021 }
2022 if((bits & cb_flag::po_destroy_naval_bases) != 0) {
2023 total +=
2024 std::max(state.defines.prestige_destroy_naval_bases * actor_prestige, state.defines.prestige_destroy_naval_bases_base);
2025 }
2026
2027 return total * state.world.cb_type_get_prestige_factor(t);
2028}
2029
2030float crisis_cb_addition_infamy_cost(sys::state& state, dcon::cb_type_id type, dcon::nation_id from, dcon::nation_id target, dcon::state_definition_id cb_state) {
2031 if((state.world.cb_type_get_type_bits(type) & (military::cb_flag::always | military::cb_flag::is_not_constructing_cb)) != 0) {
2032 // not a constructible CB
2033 return 0.0f;
2034 }
2035
2036 return cb_infamy(state, type, target, cb_state);
2037}
2038float cb_addition_infamy_cost(sys::state& state, dcon::war_id war, dcon::cb_type_id type, dcon::nation_id from,
2039 dcon::nation_id target, dcon::state_definition_id cb_state) {
2040 if((state.world.cb_type_get_type_bits(type) & (military::cb_flag::always | military::cb_flag::is_not_constructing_cb)) != 0) {
2041 // not a constructible CB
2042 return 0.0f;
2043 }
2044
2045 auto other_cbs = state.world.nation_get_available_cbs(from);
2046 for(auto& cb : other_cbs) {
2047 if(cb.target == target && cb.cb_type == type && cb_conditions_satisfied(state, from, target, cb.cb_type))
2048 return 0.0f;
2049 }
2050
2051 if(state.world.war_get_is_great(war))
2052 return cb_infamy(state, type, target, cb_state) * state.defines.gw_justify_cb_badboy_impact;
2053 else
2054 return cb_infamy(state, type, target, cb_state);
2055}
2056
2057bool cb_requires_selection_of_a_valid_nation(sys::state const& state, dcon::cb_type_id t) {
2058 auto allowed_nation = state.world.cb_type_get_allowed_countries(t);
2059 return bool(allowed_nation);
2060}
2061bool cb_requires_selection_of_a_liberatable_tag(sys::state const& state, dcon::cb_type_id t) {
2062 auto bits = state.world.cb_type_get_type_bits(t);
2063 return (bits & (cb_flag::po_transfer_provinces)) != 0;
2064}
2065bool cb_requires_selection_of_a_state(sys::state const& state, dcon::cb_type_id t) {
2066 auto bits = state.world.cb_type_get_type_bits(t);
2067 return (bits & (cb_flag::po_demand_state | cb_flag::po_transfer_provinces | cb_flag::po_destroy_naval_bases | cb_flag::po_destroy_forts)) != 0
2068 && (bits & cb_flag::all_allowed_states) == 0;
2069}
2070
2071void execute_cb_discovery(sys::state& state, dcon::nation_id n) {
2072 /*
2073 If discovered, the fabricating country gains the infamy for that war goal x the fraction of fabrication remaining. If
2074 discovered relations between the two nations are changed by define:ON_CB_DETECTED_RELATION_CHANGE. If discovered, any states
2075 with a flashpoint in the target nation will have their tension increase by define:TENSION_ON_CB_DISCOVERED
2076 */
2077 auto infamy = cb_infamy(state, state.world.nation_get_constructing_cb_type(n), state.world.nation_get_constructing_cb_target(n), state.world.nation_get_constructing_cb_target_state(n));
2078 auto adj_infamy = std::max(0.0f, ((100.0f - state.world.nation_get_constructing_cb_progress(n)) / 100.0f) * infamy);
2079 state.world.nation_get_infamy(n) += adj_infamy;
2080
2081 auto target = state.world.nation_get_constructing_cb_target(n);
2082
2083 nations::adjust_relationship(state, n, target, state.defines.on_cb_detected_relation_change);
2084
2085 for(auto si : state.world.nation_get_state_ownership(target)) {
2086 if(si.get_state().get_flashpoint_tag()) {
2087 si.get_state().set_flashpoint_tension(
2088 std::min(100.0f, si.get_state().get_flashpoint_tension() + state.defines.tension_on_cb_discovered));
2089 }
2090 }
2091
2093 [n, target, adj_infamy](sys::state& state, text::layout_base& contents) {
2094 if(n == state.local_player_nation) {
2095 text::add_line(state, contents, "msg_fab_discovered_1", text::variable_type::x, text::fp_one_place{ adj_infamy });
2096 } else {
2097 text::add_line(state, contents, "msg_fab_discovered_2", text::variable_type::x, n, text::variable_type::y, target);
2098 }
2099 },
2100 "msg_fab_discovered_title",
2101 n, target, dcon::nation_id{},
2103 });
2104}
2105
2106bool leader_is_in_combat(sys::state& state, dcon::leader_id l) {
2107 auto army = state.world.leader_get_army_from_army_leadership(l);
2108 if(state.world.army_get_battle_from_army_battle_participation(army))
2109 return true;
2110 auto navy = state.world.leader_get_navy_from_navy_leadership(l);
2111 if(state.world.navy_get_battle_from_navy_battle_participation(navy))
2112 return true;
2113 return false;
2114}
2115
2116dcon::leader_id make_new_leader(sys::state& state, dcon::nation_id n, bool is_general) {
2117 auto l = fatten(state.world, state.world.create_leader());
2118 l.set_is_admiral(!is_general);
2119
2120 uint32_t seed_base = (uint32_t(n.index()) << 6) ^ uint32_t(l.id.index());
2121
2122 auto num_personalities = uint32_t(state.military_definitions.first_background_trait.index() - 1);
2123 auto num_backgrounds = uint32_t((state.world.leader_trait_size() - num_personalities) - 2);
2124
2125 auto trait_pair = rng::get_random_pair(state, seed_base);
2126
2127 l.set_personality(dcon::leader_trait_id{dcon::leader_trait_id::value_base_t(1 + rng::reduce(uint32_t(trait_pair.high), num_personalities))});
2128 l.set_background(dcon::leader_trait_id{dcon::leader_trait_id::value_base_t(state.military_definitions.first_background_trait.index() + 1 + rng::reduce(uint32_t(trait_pair.low), num_backgrounds))});
2129
2130 auto names_pair = rng::get_random_pair(state, seed_base + 1);
2131
2132 auto names = state.world.culture_get_last_names(state.world.nation_get_primary_culture(n));
2133 if(names.size() > 0) {
2134 l.set_name(names.at(rng::reduce(uint32_t(names_pair.high), names.size())));
2135 }
2136
2137 l.set_since(state.current_date);
2138
2139 /* defines:LEADER_MAX_RANDOM_PRESTIGE */
2140 float r_factor = float(rng::reduce(uint32_t(rng::get_random(state, seed_base + 1)), 100)) / 100.f;
2141 l.set_prestige(r_factor * state.defines.leader_max_random_prestige);
2142
2143 state.world.try_create_leader_loyalty(n, l);
2144
2145 state.world.nation_get_leadership_points(n) -= state.defines.leader_recruit_cost;
2146
2147 return l;
2148}
2149
2150void kill_leader(sys::state& state, dcon::leader_id l) {
2151 /*
2152 the player only gets leader death messages if the leader is currently assigned to an army or navy (assuming the message
2153 setting for it is turned on).
2154 */
2155 if(state.world.leader_get_nation_from_leader_loyalty(l) == state.local_player_nation) {
2156 if(state.world.leader_get_army_from_army_leadership(l) || state.world.leader_get_navy_from_navy_leadership(l)) {
2157 dcon::nation_id n = state.local_player_nation;
2158
2159 auto is_admiral = state.world.leader_get_is_admiral(l);
2160 auto location = is_admiral ? state.world.navy_get_location_from_navy_location(state.world.leader_get_navy_from_navy_leadership(l)) : state.world.army_get_location_from_army_location(state.world.leader_get_army_from_army_leadership(l));
2161 auto name = state.world.leader_get_name(l);
2162
2164 [is_admiral, location, name](sys::state& state, text::layout_base& contents) {
2165 if(is_admiral)
2166 text::add_line(state, contents, "msg_leader_died_2", text::variable_type::x, state.to_string_view(name), text::variable_type::y, location);
2167 else
2168 text::add_line(state, contents, "msg_leader_died_1", text::variable_type::x, state.to_string_view(name), text::variable_type::y, location);
2169 },
2170 "msg_leader_died_title",
2171 n, dcon::nation_id{}, dcon::nation_id{},
2173 });
2174 }
2175 }
2176
2177 state.world.delete_leader(l);
2178}
2179
2181 int32_t admirals = 0;
2182 int32_t generals = 0;
2183};
2184
2185leader_counts count_leaders(sys::state& state, dcon::nation_id n) {
2186 leader_counts result{};
2187 for(auto l : state.world.nation_get_leader_loyalty(n)) {
2188 if(l.get_leader().get_is_admiral())
2189 ++result.admirals;
2190 else
2191 ++result.generals;
2192 }
2193 return result;
2194}
2195int32_t count_armies(sys::state& state, dcon::nation_id n) {
2196 auto x = state.world.nation_get_army_control(n);
2197 return int32_t(x.end() - x.begin());
2198}
2199int32_t count_navies(sys::state& state, dcon::nation_id n) {
2200 auto x = state.world.nation_get_navy_control(n);
2201 return int32_t(x.end() - x.begin());
2202}
2203
2205 /*
2206 - A nation gets ((number-of-officers / total-population) / officer-optimum)^1 x officer-leadership-amount +
2207 national-modifier-to-leadership x (national-modifier-to-leadership-modifier + 1) leadership points per month.
2208 */
2209
2210 state.world.execute_serial_over_nation(
2211 [&, optimum_officers = state.world.pop_type_get_research_optimum(state.culture_definitions.officers)](auto ids) {
2212 auto ofrac = state.world.nation_get_demographics(ids, demographics::to_key(state, state.culture_definitions.officers)) /
2213 ve::max(state.world.nation_get_demographics(ids, demographics::total), 1.0f);
2214 auto omod = ve::min(1.0f, ofrac / optimum_officers) * float(state.culture_definitions.officer_leadership_points);
2215 auto nmod = (state.world.nation_get_modifier_values(ids, sys::national_mod_offsets::leadership_modifier) + 1.0f) *
2216 state.world.nation_get_modifier_values(ids, sys::national_mod_offsets::leadership);
2217
2218 state.world.nation_set_leadership_points(ids, ve::select(state.world.nation_get_owned_province_count(ids) != 0, state.world.nation_get_leadership_points(ids) + omod + nmod, 0.0f));
2219 });
2220
2221 for(auto n : state.world.in_nation) {
2222 if(n.get_leadership_points() > state.defines.leader_recruit_cost * 3.0f) {
2223 // automatically make new leader
2224 auto new_l = [&]() {
2225 auto existing_leaders = count_leaders(state, n);
2226 auto army_count = count_armies(state, n);
2227 auto navy_count = count_navies(state, n);
2228 if(existing_leaders.generals < army_count) {
2229 return make_new_leader(state, n, true);
2230 } else if(existing_leaders.admirals < navy_count) {
2231 return make_new_leader(state, n, false);
2232 } else {
2233 auto too_many_generals =
2234 (existing_leaders.admirals > 0 && navy_count > 0)
2235 ? float(existing_leaders.generals) / float(existing_leaders.admirals) > float(army_count) / float(navy_count)
2236 : false;
2237 return make_new_leader(state, n, !too_many_generals);
2238 }
2239 }();
2240 if(state.world.leader_get_is_admiral(new_l)) {
2241 for(auto v : state.world.nation_get_navy_control(n)) {
2242 if(!v.get_navy().get_admiral_from_navy_leadership()) {
2243 state.world.try_create_navy_leadership(v.get_navy(), new_l);
2244 break;
2245 }
2246 }
2247 } else {
2248 for(auto a : state.world.nation_get_army_control(n)) {
2249 if(!a.get_army().get_general_from_army_leadership()) {
2250 state.world.try_create_army_leadership(a.get_army(), new_l);
2251 break;
2252 }
2253 }
2254 }
2255 }
2256 }
2257}
2259 /*
2260 Leaders who are both less than 26 years old and not in combat have no chance of death. Otherwise, we take the age of the
2261 leader and divide by define:LEADER_AGE_DEATH_FACTOR. Then we multiply that result by 2 if the leader is currently in combat.
2262 That is then the leader's current chance of death out of ... my notes say 11,000 here.
2263 */
2264
2265 for(uint32_t i = state.world.leader_size(); i-- > 0;) {
2266 dcon::leader_id l{dcon::leader_id::value_base_t(i)};
2267 if(!state.world.leader_is_valid(l))
2268 continue;
2269
2270 auto age_in_days = state.current_date.to_raw_value() - state.world.leader_get_since(l).to_raw_value();
2271 if(age_in_days > 365 * 26) { // assume leaders are created at age 20; no death chance prior to 46
2272 float age_in_years = float(age_in_days) / 365.0f;
2273 float death_chance =
2274 (age_in_years * (leader_is_in_combat(state, l) ? 2.0f : 1.0f) / state.defines.leader_age_death_factor) / 11000.0f;
2275
2276 /*
2277 float live_chance = 1.0f - death_chance;
2278 float live_chance_2 = live_chance * live_chance;
2279 float live_chance_4 = live_chance_2 * live_chance_2;
2280 float live_chance_8 = live_chance_4 * live_chance_4;
2281 float live_chance_16 = live_chance_8 * live_chance_8;
2282 float live_chance_32 = live_chance_16 * live_chance_16;
2283
2284 float monthly_chance = 1.0f - (live_chance_32 / live_chance_2);
2285 */
2286
2287 auto int_chance = uint32_t(death_chance * float(0xFFFFFFFF));
2288 auto rvalue = uint32_t(rng::get_random(state, uint32_t(l.index())) & 0xFFFFFFFF);
2289
2290 if(rvalue < int_chance)
2291 kill_leader(state, l);
2292 }
2293 }
2294}
2295
2296bool has_truce_with(sys::state& state, dcon::nation_id attacker, dcon::nation_id target) {
2297 auto rel = state.world.get_diplomatic_relation_by_diplomatic_pair(target, attacker);
2298 if(rel) {
2299 auto truce_ends = state.world.diplomatic_relation_get_truce_until(rel);
2300 if(truce_ends && state.current_date < truce_ends)
2301 return true;
2302 }
2303 return false;
2304}
2305
2306dcon::regiment_id create_new_regiment(sys::state& state, dcon::nation_id n, dcon::unit_type_id t) {
2307 auto reg = fatten(state.world, state.world.create_regiment());
2308 reg.set_type(t);
2309 // TODO make name
2310 auto exp = state.world.nation_get_modifier_values(n, sys::national_mod_offsets::land_unit_start_experience) / 100.f;
2311 exp += state.world.nation_get_modifier_values(n, sys::national_mod_offsets::regular_experience_level) / 100.f;
2312 reg.set_experience(std::clamp(exp, 0.f, 1.f));
2313 reg.set_strength(1.f);
2314 reg.set_org(1.f);
2315 return reg.id;
2316}
2317dcon::ship_id create_new_ship(sys::state& state, dcon::nation_id n, dcon::unit_type_id t) {
2318 auto shp = fatten(state.world, state.world.create_ship());
2319 shp.set_type(t);
2320 // TODO make name
2321 auto exp = state.world.nation_get_modifier_values(n, sys::national_mod_offsets::naval_unit_start_experience) / 100.f;
2322 exp += state.world.nation_get_modifier_values(n, sys::national_mod_offsets::regular_experience_level) / 100.f;
2323 shp.set_experience(std::clamp(exp, 0.f, 1.f));
2324 shp.set_strength(1.f);
2325 shp.set_org(1.f);
2326 return shp.id;
2327}
2328
2329void give_military_access(sys::state& state, dcon::nation_id accessing_nation, dcon::nation_id target) {
2330 auto ur = state.world.get_unilateral_relationship_by_unilateral_pair(target, accessing_nation);
2331 if(!ur) {
2332 ur = state.world.force_create_unilateral_relationship(target, accessing_nation);
2333 }
2334 state.world.unilateral_relationship_set_military_access(ur, true);
2335}
2336void remove_military_access(sys::state& state, dcon::nation_id accessing_nation, dcon::nation_id target) {
2337 auto ur = state.world.get_unilateral_relationship_by_unilateral_pair(target, accessing_nation);
2338 if(ur) {
2339 state.world.unilateral_relationship_set_military_access(ur, false);
2340 }
2341}
2342
2343void end_wars_between(sys::state& state, dcon::nation_id a, dcon::nation_id b) {
2344 dcon::war_id w = find_war_between(state, a, b);
2345 while(w) {
2346 military::remove_from_war(state, w, b, false);
2347 // cleanup_war(state, w, war_result::draw);
2348 w = find_war_between(state, a, b);
2349 }
2350}
2351
2353 auto war = fatten(state.world, w);
2354
2355 dcon::nation_id primary_attacker = state.world.war_get_primary_attacker(war);
2356 dcon::nation_id primary_defender = state.world.war_get_original_target(war);
2357
2359
2365 text::add_to_substitution_map(sub, text::variable_type::country_adj, state.world.national_identity_get_adjective(war.get_over_tag()));
2366}
2367
2368void add_to_war(sys::state& state, dcon::war_id w, dcon::nation_id n, bool as_attacker, bool on_war_creation) {
2369 assert(n);
2370 if(state.world.nation_get_owned_province_count(n) == 0)
2371 return;
2372
2373 auto participant = state.world.force_create_war_participant(w, n);
2374 state.world.war_participant_set_is_attacker(participant, as_attacker);
2375 state.world.nation_set_is_at_war(n, true);
2376 state.world.nation_set_disarmed_until(n, sys::date{});
2377
2378 for(auto dep : state.world.nation_get_overlord_as_ruler(n)) {
2379 add_to_war(state, w, dep.get_subject(), as_attacker);
2380 }
2381
2382 for(auto wp : state.world.war_get_war_participant(w)) {
2383 if(wp.get_is_attacker() == !as_attacker && nations::are_allied(state, n, wp.get_nation()))
2384 nations::break_alliance(state, n, wp.get_nation());
2385 }
2386
2387 if(!as_attacker && state.world.nation_get_rank(state.world.war_get_primary_defender(w)) > state.world.nation_get_rank(n)) {
2388 state.world.war_set_primary_defender(w, n);
2389 }
2390
2391 for(auto ul : state.world.nation_get_unilateral_relationship_as_source(n)) {
2392 if(ul.get_war_subsidies()) {
2393 auto role = get_role(state, w, ul.get_target());
2394 if(role != war_role::none) {
2395 if((as_attacker && role == war_role::defender) || (!as_attacker && role == war_role::attacker)) {
2396 ul.set_war_subsidies(false);
2397
2399 [source = n, target = ul.get_target().id](sys::state& state, text::layout_base& contents) {
2400 text::add_line(state, contents, "msg_wsub_end_1", text::variable_type::x, source, text::variable_type::y, target);
2401 },
2402 "msg_wsub_end_title",
2403 n, ul.get_target().id, dcon::nation_id{},
2405 });
2406 }
2407 }
2408 }
2409 }
2410 for(auto ul : state.world.nation_get_unilateral_relationship_as_target(n)) {
2411 if(ul.get_war_subsidies()) {
2412 auto role = get_role(state, w, ul.get_source());
2413 if(role != war_role::none) {
2414 if((as_attacker && role == war_role::defender) || (!as_attacker && role == war_role::attacker)) {
2415 ul.set_war_subsidies(false);
2416
2418 [source = ul.get_source().id, target = n](sys::state& state, text::layout_base& contents) {
2419 text::add_line(state, contents, "msg_wsub_end_1", text::variable_type::x, source, text::variable_type::y, target);
2420 },
2421 "msg_wsub_end_title",
2422 n, ul.get_target().id, dcon::nation_id{},
2424 });
2425 }
2426 }
2427 }
2428 }
2429
2430 if(state.military_definitions.great_wars_enabled && !state.world.war_get_is_great(w)) {
2431 int32_t gp_attackers = 0;
2432 int32_t gp_defenders = 0;
2433
2434 for(auto par : state.world.war_get_war_participant(w)) {
2435 if(nations::is_great_power(state, par.get_nation())) {
2436 if(par.get_is_attacker())
2437 ++gp_attackers;
2438 else
2439 ++gp_defenders;
2440 }
2441 }
2442
2443 if(gp_attackers >= 2 && gp_defenders >= 2) {
2444 auto old_name = get_war_name(state, w);
2445 state.world.war_set_is_great(w, true);
2446 auto it = state.lookup_key(std::string_view{"great_war_name"});
2447 if(it) {
2448 state.world.war_set_name(w, it);
2449 }
2450
2452 [old_name, w](sys::state& state, text::layout_base& contents) {
2453 std::string new_war_name = get_war_name(state, w);
2454 text::add_line(state, contents, "msg_war_becomes_great_1", text::variable_type::x, std::string_view{old_name}, text::variable_type::y, std::string_view{new_war_name});
2455 },
2456 "msg_war_becomes_great_title",
2457 state.local_player_nation, dcon::nation_id{}, dcon::nation_id{},
2459 });
2460 }
2461 }
2462
2464 [w, n](sys::state& state, text::layout_base& contents) {
2465 std::string war_name = get_war_name(state, w);
2466 text::add_line(state, contents, "msg_war_join_1", text::variable_type::x, n, text::variable_type::val, std::string_view{war_name});
2467 },
2468 "msg_war_join_title",
2469 n, get_role(state, w, state.local_player_nation) != war_role::none ? state.local_player_nation : dcon::nation_id{}, dcon::nation_id{ },
2471 });
2472
2473 if(!on_war_creation && state.world.nation_get_is_player_controlled(n) == false) {
2474 ai::add_free_ai_cbs_to_war(state, n, w);
2475 }
2476
2477 for(auto o : state.world.nation_get_army_control(n)) {
2478 if(o.get_army().get_is_retreating() || o.get_army().get_black_flag() || o.get_army().get_navy_from_army_transport() || o.get_army().get_battle_from_army_battle_participation())
2479 continue;
2480
2481 army_arrives_in_province(state, o.get_army(), o.get_army().get_location_from_army_location(), crossing_type::none);
2482 }
2483 for(auto o : state.world.nation_get_navy_control(n)) {
2484 if(o.get_navy().get_is_retreating() || o.get_navy().get_battle_from_navy_battle_participation())
2485 continue;
2486
2487 auto loc = o.get_navy().get_location_from_navy_location();
2488 if(loc.id.index() >= state.province_definitions.first_sea_province.index())
2489 navy_arrives_in_province(state, o.get_navy(), loc);
2490 }
2491}
2492
2493bool is_attacker(sys::state& state, dcon::war_id w, dcon::nation_id n) {
2494 for(auto p : state.world.war_get_war_participant(w)) {
2495 if(p.get_nation() == n)
2496 return p.get_is_attacker();
2497 }
2498 return false;
2499}
2500
2501war_role get_role(sys::state const& state, dcon::war_id w, dcon::nation_id n) {
2502 for(auto p : state.world.war_get_war_participant(w)) {
2503 if(p.get_nation() == n)
2504 return p.get_is_attacker() ? war_role::attacker : war_role::defender;
2505 }
2506 return war_role::none;
2507}
2508
2509dcon::war_id create_war(sys::state& state, dcon::nation_id primary_attacker, dcon::nation_id primary_defender,
2510 dcon::cb_type_id primary_wargoal, dcon::state_definition_id primary_wargoal_state,
2511 dcon::national_identity_id primary_wargoal_tag, dcon::nation_id primary_wargoal_secondary) {
2512 assert(primary_attacker);
2513 assert(primary_defender);
2514 auto new_war = fatten(state.world, state.world.create_war());
2515
2516 // release puppet if subject declares on overlord or vice versa
2517 {
2518 auto ol_rel = state.world.nation_get_overlord_as_subject(primary_defender);
2519 if(auto ol = state.world.overlord_get_ruler(ol_rel); ol && ol == primary_attacker)
2520 nations::release_vassal(state, ol_rel);
2521 }
2522 {
2523 auto ol_rel = state.world.nation_get_overlord_as_subject(primary_attacker);
2524 if(auto ol = state.world.overlord_get_ruler(ol_rel); ol && ol == primary_defender)
2525 nations::release_vassal(state, ol_rel);
2526 }
2527
2528 auto real_target = primary_defender;
2529 auto target_ol_rel = state.world.nation_get_overlord_as_subject(primary_defender);
2530 if(auto ol = state.world.overlord_get_ruler(target_ol_rel); ol && ol != primary_attacker)
2531 real_target = ol;
2532
2533 new_war.set_primary_attacker(primary_attacker);
2534 new_war.set_primary_defender(real_target);
2535 new_war.set_start_date(state.current_date);
2536 new_war.set_over_state(primary_wargoal_state);
2537 new_war.set_over_tag(primary_wargoal_tag);
2538 new_war.set_original_target(primary_defender);
2539 if(primary_wargoal_secondary) {
2540 new_war.set_over_tag(state.world.nation_get_identity_from_identity_holder(primary_wargoal_secondary));
2541 }
2542
2543 add_to_war(state, new_war, primary_attacker, true, true);
2544 add_to_war(state, new_war, real_target, false, true);
2545
2546 if(primary_wargoal) {
2547 add_wargoal(state, new_war, primary_attacker, primary_defender, primary_wargoal, primary_wargoal_state, primary_wargoal_tag,
2548 primary_wargoal_secondary);
2549 new_war.set_name(state.world.cb_type_get_war_name(primary_wargoal));
2550 } else {
2551 auto it = state.lookup_key(std::string_view{"agression_war_name"}); // misspelling is intentional; DO NOT CORRECT
2552 if(it) {
2553 new_war.set_name(it);
2554 }
2555 }
2556
2557 if(state.world.nation_get_is_player_controlled(primary_attacker) == false)
2558 ai::add_free_ai_cbs_to_war(state, primary_attacker, new_war);
2559 if(state.world.nation_get_is_player_controlled(primary_defender) == false)
2560 ai::add_free_ai_cbs_to_war(state, primary_defender, new_war);
2561
2563 [primary_attacker, primary_defender, w = new_war.id](sys::state& state, text::layout_base& contents) {
2564 std::string resolved_war_name = get_war_name(state, w);
2565 text::add_line(state, contents, "msg_war_1", text::variable_type::x, primary_attacker, text::variable_type::y, primary_defender, text::variable_type::val, std::string_view{resolved_war_name});
2566 },
2567 "msg_war_title",
2568 primary_attacker, primary_defender, dcon::nation_id{},
2570 });
2571
2572 return new_war;
2573}
2574
2575std::string get_war_name(sys::state& state, dcon::war_id war) {
2577 populate_war_text_subsitutions(state, war, sub);
2578
2579 auto fat = fatten(state.world, war);
2580 auto attacker = state.world.nation_get_identity_from_identity_holder(fat.get_primary_attacker());
2581 auto defender = state.world.nation_get_identity_from_identity_holder(fat.get_primary_defender());
2582 auto attacker_tag = fatten(state.world, attacker).get_name();
2583 auto defender_tag = fatten(state.world, defender).get_name();
2584 auto war_name_sequence = fat.get_name();
2585
2586 auto attacker_tag_key = attacker_tag;
2587 auto defender_tag_key = defender_tag;
2588 auto war_name_key = war_name_sequence;
2589
2590 if(attacker_tag_key && defender_tag_key && war_name_key) {
2591 std::string war_name = std::string{ state.to_string_view(war_name_key) };
2592 auto attacker_name = state.to_string_view(attacker_tag_key);
2593 auto defender_name = state.to_string_view(defender_tag_key);
2594 auto combined_name = war_name + std::string("_") + std::string{attacker_name} + std::string("_") + std::string{ defender_name };
2595 std::string_view name = std::string_view(combined_name);
2596 if(auto it = state.lookup_key(name); it)
2597 return text::resolve_string_substitution(state, it, sub);
2598 }
2599
2600 return text::resolve_string_substitution(state, war_name_sequence, sub);
2601}
2602
2603bool standard_war_joining_is_possible(sys::state& state, dcon::war_id wfor, dcon::nation_id n, bool as_attacker) {
2604 auto ol_relation = state.world.nation_get_overlord_as_subject(n);
2605 auto ol_nation = state.world.overlord_get_ruler(ol_relation);
2606
2607 if(!as_attacker) {
2608 return joining_war_does_not_violate_constraints(state, n, wfor, false) &&
2609 state.world.nation_get_in_sphere_of(n) != state.world.war_get_primary_attacker(wfor) &&
2610 ol_nation != state.world.war_get_primary_attacker(wfor);
2611 } else {
2612 return joining_war_does_not_violate_constraints(state, n, wfor, true) &&
2613 state.world.nation_get_in_sphere_of(n) != state.world.war_get_primary_attacker(wfor) &&
2614 ol_nation != state.world.war_get_primary_attacker(wfor);
2615 }
2616}
2617void call_defender_allies(sys::state& state, dcon::war_id wfor) {
2618 if(is_civil_war(state, wfor))
2619 return;
2620
2621 auto n = state.world.war_get_primary_defender(wfor);
2622 for(auto drel : state.world.nation_get_diplomatic_relation(n)) {
2623 auto other_nation = drel.get_related_nations(0) != n ? drel.get_related_nations(0) : drel.get_related_nations(1);
2624 if(drel.get_are_allied() && standard_war_joining_is_possible(state, wfor, other_nation, false)) {
2625
2627 std::memset(&m, 0, sizeof(m));
2628 m.from = n;
2629 m.to = other_nation;
2631 m.data.war = wfor;
2632 diplomatic_message::post(state, m);
2633 }
2634 }
2635 if(state.world.nation_get_in_sphere_of(n)) {
2636 if(joining_war_does_not_violate_constraints(state, state.world.nation_get_in_sphere_of(n), wfor, false)) {
2637
2639 std::memset(&m, 0, sizeof(m));
2640 m.from = n;
2641 m.to = state.world.nation_get_in_sphere_of(n);
2643 m.data.war = wfor;
2644 diplomatic_message::post(state, m);
2645 }
2646 }
2647}
2648void call_attacker_allies(sys::state& state, dcon::war_id wfor) {
2649 if(is_civil_war(state, wfor))
2650 return;
2651
2652 auto n = state.world.war_get_primary_attacker(wfor);
2653 for(auto drel : state.world.nation_get_diplomatic_relation(n)) {
2654 auto other_nation = drel.get_related_nations(0) != n ? drel.get_related_nations(0) : drel.get_related_nations(1);
2655 if(drel.get_are_allied() && !has_truce_with(state, other_nation, state.world.war_get_primary_defender(wfor)) &&
2656 standard_war_joining_is_possible(state, wfor, other_nation, true)) {
2657
2659 std::memset(&m, 0, sizeof(m));
2660 m.from = n;
2661 m.to = other_nation;
2663 m.data.war = wfor;
2664 diplomatic_message::post(state, m);
2665 }
2666 }
2667}
2668void add_wargoal(sys::state& state, dcon::war_id wfor, dcon::nation_id added_by, dcon::nation_id target, dcon::cb_type_id type,
2669 dcon::state_definition_id sd, dcon::national_identity_id tag, dcon::nation_id secondary_nation) {
2670
2671 if(sd) {
2672 auto for_attacker = is_attacker(state, wfor, added_by);
2673 std::vector<dcon::nation_id> targets;
2674 for(auto p : state.world.state_definition_get_abstract_state_membership(sd)) {
2675 auto owner = p.get_province().get_nation_from_province_ownership();
2676 if(std::find(targets.begin(), targets.end(), owner) == targets.end()) {
2677 for(auto par : state.world.war_get_war_participant(wfor)) {
2678 if(par.get_nation() == owner && par.get_is_attacker() != for_attacker) {
2679 auto new_wg = fatten(state.world, state.world.create_wargoal());
2680 new_wg.set_added_by(added_by);
2681 new_wg.set_associated_state(sd);
2682 new_wg.set_associated_tag(tag);
2683 new_wg.set_secondary_nation(secondary_nation);
2684 new_wg.set_target_nation(owner);
2685 new_wg.set_type(type);
2686 new_wg.set_war_from_wargoals_attached(wfor);
2687 targets.push_back(owner.id);
2688 }
2689 }
2690 }
2691 }
2692
2693 bool is_colonial_claim = (state.world.cb_type_get_type_bits(type) & cb_flag::po_colony) != 0; //doesn't require an owned state
2694 if(targets.empty()) {
2695 if(!is_colonial_claim) {
2696 return; // wargoal not added
2697 } else {
2698 auto new_wg = fatten(state.world, state.world.create_wargoal());
2699 new_wg.set_added_by(added_by);
2700 new_wg.set_associated_state(sd);
2701 new_wg.set_associated_tag(tag);
2702 new_wg.set_secondary_nation(secondary_nation);
2703 new_wg.set_target_nation(target);
2704 new_wg.set_type(type);
2705 new_wg.set_war_from_wargoals_attached(wfor);
2706 }
2707 }
2708 } else {
2709 auto new_wg = fatten(state.world, state.world.create_wargoal());
2710 new_wg.set_added_by(added_by);
2711 new_wg.set_associated_state(sd);
2712 new_wg.set_associated_tag(tag);
2713 new_wg.set_secondary_nation(secondary_nation);
2714 new_wg.set_target_nation(target);
2715 new_wg.set_type(type);
2716 new_wg.set_war_from_wargoals_attached(wfor);
2717 }
2718
2719 if(auto on_add = state.world.cb_type_get_on_add(type); on_add) {
2720 effect::execute(state, on_add, trigger::to_generic(added_by), trigger::to_generic(added_by), trigger::to_generic(target),
2721 uint32_t(state.current_date.value), uint32_t((added_by.index() << 7) ^ target.index() ^ (type.index() << 3)));
2723 }
2724
2725 if((state.world.cb_type_get_type_bits(type) & cb_flag::always) == 0) {
2726 auto cb_set = state.world.nation_get_available_cbs(added_by);
2727 for(uint32_t i = cb_set.size(); i-- > 0;) {
2728 if(cb_set.at(i).cb_type == type && cb_set.at(i).target == target) {
2729 cb_set.remove_at(i);
2730 break;
2731 }
2732 }
2733 }
2734 if(added_by != state.local_player_nation && get_role(state, wfor, state.local_player_nation) != war_role::none) {
2736 [added_by, target, type, sd, tag, secondary_nation](sys::state& state, text::layout_base& contents) {
2737 text::add_line(state, contents, "msg_wargoal_1", text::variable_type::x, added_by);
2738
2742 if(secondary_nation)
2744 else if(tag)
2747
2748 auto box = text::open_layout_box(contents);
2749 text::add_to_layout_box(state, contents, box, state.world.cb_type_get_shortest_desc(type), sub);
2750 text::close_layout_box(contents, box);
2751 },
2752 "msg_wargoal_title",
2753 added_by, target, state.local_player_nation,
2755 });
2756 }
2757}
2758
2759void remove_from_war(sys::state& state, dcon::war_id w, dcon::nation_id n, bool as_loss) {
2760 for(auto vas : state.world.nation_get_overlord_as_ruler(n)) {
2761 remove_from_war(state, w, vas.get_subject(), as_loss);
2762 }
2763
2764 dcon::war_participant_id par;
2765 for(auto p : state.world.nation_get_war_participant(n)) {
2766 if(p.get_war() == w) {
2767 par = p.id;
2768 break;
2769 }
2770 }
2771
2772 if(!par)
2773 return;
2774
2775 /*
2776 When a losing peace offer is accepted, the ruling party in the losing nation has its party loyalty reduced by
2777 define:PARTY_LOYALTY_HIT_ON_WAR_LOSS percent in all provinces (this includes accepting a crisis resolution offer in which you
2778 lose).
2779 */
2780
2781 if(as_loss) {
2782 auto rp_ideology = state.world.nation_get_ruling_party(n).get_ideology();
2783 if(rp_ideology) {
2784 for(auto prv : state.world.nation_get_province_ownership(n)) {
2785 prv.get_province().get_party_loyalty(rp_ideology) *= (1.0f - state.defines.party_loyalty_hit_on_war_loss);
2786 }
2787 }
2788 }
2789
2790 /*
2791 When a war goal is failed (the nation it targets leaves the war without that war goal being enacted): the nation that added it
2792 loses WAR_FAILED_GOAL_PRESTIGE_BASE^(WAR_FAILED_GOAL_PRESTIGE x current-prestige) x CB-penalty-factor prestige. Every pop in
2793 that nation gains CB-penalty-factor x define:WAR_FAILED_GOAL_MILITANCY militancy.
2794 */
2795
2796 float pop_militancy = 0.0f;
2797
2798 for(auto wg : state.world.war_get_wargoals_attached(w)) {
2799 if(wg.get_wargoal().get_added_by() == n) {
2800 float prestige_loss = std::min(state.defines.war_failed_goal_prestige_base, state.defines.war_failed_goal_prestige * nations::prestige_score(state, n)) * wg.get_wargoal().get_type().get_penalty_factor();
2801 nations::adjust_prestige(state, n, prestige_loss);
2802
2803 pop_militancy += state.defines.war_failed_goal_militancy * wg.get_wargoal().get_type().get_penalty_factor();
2804 }
2805 }
2806
2807 if(pop_militancy > 0) {
2808 for(auto prv : state.world.nation_get_province_ownership(n)) {
2809 for(auto pop : prv.get_province().get_pop_location()) {
2810 auto mil = pop_demographics::get_militancy(state, pop.get_pop());
2811 pop_demographics::set_militancy(state, pop.get_pop().id, std::min(mil + pop_militancy, 10.0f));
2812 }
2813 }
2814 }
2815
2816 state.world.delete_war_participant(par);
2817 auto rem_wars = state.world.nation_get_war_participant(n);
2818 if(rem_wars.begin() == rem_wars.end()) {
2819 state.world.nation_set_is_at_war(n, false);
2820 }
2821
2822 // Remove invalid occupations
2823 for(auto p : state.world.nation_get_province_ownership(n)) {
2824 if(auto c = p.get_province().get_nation_from_province_control(); c && c != n) {
2825 if(!military::are_at_war(state, c, n)) {
2826 state.world.province_set_siege_progress(p.get_province(), 0.0f);
2827 province::set_province_controller(state, p.get_province(), n);
2828 military::eject_ships(state, p.get_province());
2829 }
2830 }
2831 }
2832
2833 for(auto p : state.world.nation_get_province_control(n)) {
2834 if(auto c = p.get_province().get_nation_from_province_ownership(); c && c != n) {
2835 if(!military::are_at_war(state, c, n)) {
2836 state.world.province_set_siege_progress(p.get_province(), 0.0f);
2837 province::set_province_controller(state, p.get_province(), c);
2838 military::eject_ships(state, p.get_province());
2839 }
2840 }
2841 }
2842
2843 if(as_loss) {
2844 state.world.nation_set_last_war_loss(n, state.current_date);
2845 }
2846}
2847
2848void cleanup_war(sys::state& state, dcon::war_id w, war_result result) {
2849 auto par = state.world.war_get_war_participant(w);
2850 state.military_definitions.pending_blackflag_update = true;
2851
2852 if(state.world.war_get_is_crisis_war(w)) {
2854 }
2855
2856 while(par.begin() != par.end()) {
2857 if((*par.begin()).get_is_attacker()) {
2858 remove_from_war(state, w, (*par.begin()).get_nation(), result == war_result::defender_won);
2859 } else {
2860 remove_from_war(state, w, (*par.begin()).get_nation(), result == war_result::attacker_won);
2861 }
2862 }
2863
2864 auto nbattles = state.world.war_get_naval_battle_in_war(w);
2865 while(nbattles.begin() != nbattles.end()) {
2866 end_battle(state, (*nbattles.begin()).get_battle().id, battle_result::indecisive);
2867 }
2868
2869 auto lbattles = state.world.war_get_land_battle_in_war(w);
2870 while(lbattles.begin() != lbattles.end()) {
2871 end_battle(state, (*lbattles.begin()).get_battle().id, battle_result::indecisive);
2872 }
2873
2874 state.world.delete_war(w);
2875}
2876
2878 for(auto a : state.world.in_army) {
2879 if(!bool(a.get_general_from_army_leadership())) {
2880 auto controller = a.get_controller_from_army_control();
2881 for(auto l : controller.get_leader_loyalty()) {
2882 if(l.get_leader().get_is_admiral() == false && !bool(l.get_leader().get_army_from_army_leadership())) {
2883 a.set_general_from_army_leadership(l.get_leader());
2884 break;
2885 }
2886 }
2887 }
2888 }
2889 for(auto a : state.world.in_navy) {
2890 if(!bool(a.get_admiral_from_navy_leadership())) {
2891 auto controller = a.get_controller_from_navy_control();
2892 for(auto l : controller.get_leader_loyalty()) {
2893 if(l.get_leader().get_is_admiral() == true && !bool(l.get_leader().get_navy_from_navy_leadership())) {
2894 a.set_admiral_from_navy_leadership(l.get_leader());
2895 break;
2896 }
2897 }
2898 }
2899 }
2900}
2901
2902void take_from_sphere(sys::state& state, dcon::nation_id member, dcon::nation_id new_gp) {
2903 auto existing_sphere_leader = state.world.nation_get_in_sphere_of(member);
2904 if(existing_sphere_leader) {
2905 auto rel = state.world.get_gp_relationship_by_gp_influence_pair(member, existing_sphere_leader);
2906 assert(rel);
2907 state.world.gp_relationship_get_status(rel) &= ~nations::influence::level_mask;
2908 state.world.gp_relationship_get_status(rel) |= nations::influence::level_hostile;
2909 state.world.nation_set_in_sphere_of(member, dcon::nation_id{});
2910 }
2911
2912 if(!nations::is_great_power(state, new_gp))
2913 return;
2914 if(state.world.nation_get_owned_province_count(member) == 0)
2915 return;
2916
2917 auto nrel = state.world.get_gp_relationship_by_gp_influence_pair(member, new_gp);
2918 if(!nrel) {
2919 nrel = state.world.force_create_gp_relationship(member, new_gp);
2920 }
2921
2922 state.world.gp_relationship_get_status(nrel) &= ~nations::influence::level_mask;
2923 state.world.gp_relationship_get_status(nrel) |= nations::influence::level_in_sphere;
2924 state.world.gp_relationship_set_influence(nrel, state.defines.max_influence);
2925 state.world.nation_set_in_sphere_of(member, new_gp);
2926
2928 [member, existing_sphere_leader, new_gp](sys::state& state, text::layout_base& contents) {
2929 text::add_line(state, contents, "msg_rem_sphere_2", text::variable_type::x, new_gp, text::variable_type::y, member, text::variable_type::val, existing_sphere_leader);
2930 },
2931 "msg_rem_sphere_title",
2932 new_gp, existing_sphere_leader, member,
2934 });
2935}
2936
2937void implement_war_goal(sys::state& state, dcon::war_id war, dcon::cb_type_id wargoal, dcon::nation_id from,
2938 dcon::nation_id target, dcon::nation_id secondary_nation, dcon::state_definition_id wargoal_state,
2939 dcon::national_identity_id wargoal_t) {
2940 assert(from);
2941 assert(target);
2942 assert(wargoal);
2943
2944 auto bits = state.world.cb_type_get_type_bits(wargoal);
2945 bool for_attacker = is_attacker(state, war, from);
2946
2947 // po_add_to_sphere: leaves its current sphere and has its opinion of that nation set to hostile. Is added to the nation that
2948 // added the war goal's sphere with max influence.
2949 if((bits & cb_flag::po_add_to_sphere) != 0) {
2950 if(secondary_nation) {
2951 if(secondary_nation != target) {
2952 if(state.world.nation_get_owned_province_count(secondary_nation) != 0)
2953 take_from_sphere(state, secondary_nation, from);
2954 } else { // we see this in the independence cb
2955 if(state.world.nation_get_owned_province_count(from) != 0)
2956 take_from_sphere(state, from, dcon::nation_id{});
2957 }
2958 } else {
2959 take_from_sphere(state, target, from);
2960 }
2961 }
2962
2963 // po_clear_union_sphere: every nation of the actors culture group in the target nation's sphere leaves (and has relation set
2964 // to friendly) and is added to the actor nation's sphere with max influence. All vassals of the target nation affected by
2965 // this are freed.
2966 if((bits & cb_flag::po_clear_union_sphere) != 0) {
2967 auto from_cg = state.world.nation_get_primary_culture(from).get_group_from_culture_group_membership();
2968 for(auto gprl : state.world.nation_get_gp_relationship_as_great_power(target)) {
2969 if(gprl.get_influence_target().get_in_sphere_of() == target &&
2970 gprl.get_influence_target().get_primary_culture().get_group_from_culture_group_membership() == from_cg) {
2971
2972 take_from_sphere(state, gprl.get_influence_target(), from);
2973 }
2974 }
2975 }
2976
2977 // po_release_puppet: nation stops being a vassal
2978 if((bits & cb_flag::po_release_puppet) != 0) {
2979 assert(secondary_nation);
2980 if(secondary_nation != target) {
2981 auto ol = state.world.nation_get_overlord_as_subject(secondary_nation);
2982 if(ol) {
2983 nations::release_vassal(state, ol);
2984 }
2985 } else { // we see this in the independence cb
2986 auto ol = state.world.nation_get_overlord_as_subject(from);
2987 if(ol) {
2988 nations::release_vassal(state, ol);
2989 }
2990 }
2991 }
2992
2993 // po_make_puppet: the target nation releases all of its vassals and then becomes a vassal of the acting nation.
2994 if((bits & cb_flag::po_make_puppet) != 0) {
2995 for(auto sub : state.world.nation_get_overlord_as_ruler(target)) {
2996 nations::release_vassal(state, sub);
2997 }
2998 if(state.world.nation_get_owned_province_count(target) > 0) {
2999 nations::make_vassal(state, target, from);
3000 take_from_sphere(state, target, from);
3001 }
3002 }
3003
3004 // po_destory_forts: reduces fort levels to zero in any targeted states
3005 if((bits & cb_flag::po_destroy_forts) != 0) {
3006 if((bits & cb_flag::all_allowed_states) == 0) {
3007 for(auto prov : state.world.state_definition_get_abstract_state_membership(wargoal_state)) {
3008 if(prov.get_province().get_nation_from_province_ownership() == target) {
3009 prov.get_province().set_building_level(uint8_t(economy::province_building_type::fort), 0);
3010 }
3011 }
3012 } else if(auto allowed_states = state.world.cb_type_get_allowed_states(wargoal); allowed_states) {
3013 for(auto si : state.world.nation_get_state_ownership(target)) {
3014 if(trigger::evaluate(state, allowed_states, trigger::to_generic(si.get_state().id), trigger::to_generic(from),
3015 trigger::to_generic(from))) {
3017 [&](dcon::province_id prov) { state.world.province_set_building_level(prov, uint8_t(economy::province_building_type::fort), 0); });
3018 }
3019 }
3020 }
3021 }
3022
3023 // po_destory_naval_bases: as above
3024 if((bits & cb_flag::po_destroy_naval_bases) != 0) {
3025 if((bits & cb_flag::all_allowed_states) == 0) {
3026 for(auto prov : state.world.state_definition_get_abstract_state_membership(wargoal_state)) {
3027 if(prov.get_province().get_nation_from_province_ownership() == target) {
3028 prov.get_province().set_building_level(uint8_t(economy::province_building_type::naval_base), 0);
3029 }
3030 }
3031 } else if(auto allowed_states = state.world.cb_type_get_allowed_states(wargoal); allowed_states) {
3032 for(auto si : state.world.nation_get_state_ownership(target)) {
3033 if(trigger::evaluate(state, allowed_states, trigger::to_generic(si.get_state().id), trigger::to_generic(from),
3034 trigger::to_generic(from))) {
3036 [&](dcon::province_id prov) { state.world.province_set_building_level(prov, uint8_t(economy::province_building_type::naval_base), 0); });
3037 }
3038 }
3039 }
3040 }
3041
3042 // po_disarmament: a random define:DISARMAMENT_ARMY_HIT fraction of the nations units are destroyed. All current unit
3043 // constructions are canceled. The nation is disarmed. Disarmament lasts until define:REPARATIONS_YEARS or the nation is at
3044 // war again.
3045 if((bits & cb_flag::po_disarmament) != 0) {
3046 if(state.world.nation_get_owned_province_count(target) > 0) {
3047 state.world.nation_set_disarmed_until(target, state.current_date + int32_t(state.defines.reparations_years) * 365);
3048 }
3049 // Cancel all constructions
3050 for(const auto po : state.world.nation_get_province_ownership(target)) {
3051 auto lc = po.get_province().get_province_building_construction();
3052 while(lc.begin() != lc.end()) {
3053 state.world.delete_province_building_construction(*(lc.begin()));
3054 }
3055 auto nc = po.get_province().get_province_naval_construction();
3056 while(nc.begin() != nc.end()) {
3057 state.world.delete_province_naval_construction(*(nc.begin()));
3058 }
3059 }
3060 auto uc = state.world.nation_get_province_land_construction(target);
3061 while(uc.begin() != uc.end()) {
3062 state.world.delete_province_land_construction(*(uc.begin()));
3063 }
3064 // Destroy units (fraction is disarmament hit)
3065 if(state.defines.disarmament_army_hit > 0.f) {
3066 auto ar = state.world.nation_get_army_control(target);
3067 auto total = int32_t(ar.end() - ar.begin());
3068 auto rem = int32_t(float(total) * state.defines.disarmament_army_hit);
3069 auto it = ar.begin();
3070 while(it != ar.end() && rem > 0) {
3071 if((*it).get_army().get_army_battle_participation()) {
3072 ++it;
3073 } else {
3074 military::cleanup_army(state, (*it).get_army());
3075 delete (&it);
3076 new (&it) dcon::internal::iterator_nation_foreach_army_control_as_controller(ar.begin());
3077 --rem;
3078 }
3079 }
3080 }
3081 }
3082
3083 // po_reparations: the nation is set to pay reparations for define:REPARATIONS_YEARS
3084 if((bits & cb_flag::po_reparations) != 0) {
3085 if(state.world.nation_get_owned_province_count(target) > 0) {
3086 state.world.nation_set_reparations_until(target, state.current_date + int32_t(state.defines.reparations_years) * 365);
3087 auto urel = state.world.get_unilateral_relationship_by_unilateral_pair(target, from);
3088 if(!urel) {
3089 urel = state.world.force_create_unilateral_relationship(target, from);
3090 }
3091 state.world.unilateral_relationship_set_reparations(urel, true);
3092 }
3093 }
3094
3095 // po_remove_prestige: the target loses (current-prestige x define:PRESTIGE_REDUCTION) + define:PRESTIGE_REDUCTION_BASE
3096 // prestige
3097 if((bits & cb_flag::po_remove_prestige) != 0) {
3098 if(state.world.nation_get_owned_province_count(target) > 0) {
3099 nations::adjust_prestige(state, target,
3100 -(state.defines.prestige_reduction * nations::prestige_score(state, target) + state.defines.prestige_reduction_base));
3101 }
3102 }
3103
3104 // po_install_communist_gov: The target switches its government type and ruling ideology (if possible) to that of the nation
3105 // that added the war goal. Relations with the nation that added the war goal are set to 0. The nation leaves its current
3106 // sphere and enters the actor's sphere if it is a GP. If the war continues, the war leader on the opposite side gains the
3107 // appropriate `counter_wargoal_on_install_communist_gov` CB, if any and allowed by the conditions of that CB.
3108 if((bits & cb_flag::po_install_communist_gov_type) != 0) {
3109 if(state.world.nation_get_owned_province_count(target) > 0) {
3110 politics::change_government_type(state, target, state.world.nation_get_government_type(from));
3112 state.world.political_party_get_ideology(state.world.nation_get_ruling_party(from)));
3113 take_from_sphere(state, target, from);
3114 }
3115 }
3116
3117 // po_uninstall_communist_gov_type: The target switches its government type to that of the nation that added the war goal. The
3118 // nation leaves its current sphere and enters the actor's sphere if it is a GP.
3119 if((bits & cb_flag::po_uninstall_communist_gov_type) != 0) {
3120 if(state.world.nation_get_owned_province_count(target) > 0) {
3121 politics::change_government_type(state, target, state.world.nation_get_government_type(from));
3122 take_from_sphere(state, target, from);
3123 }
3124 }
3125
3126 // po_colony: colonization finishes, with the adder of the war goal getting the colony and all other colonizers being kicked
3127 // out
3128 if((bits & cb_flag::po_colony) != 0) {
3129 std::vector<dcon::colonization_id> to_del;
3130 for(auto c : state.world.state_definition_get_colonization(wargoal_state)) {
3131 if(c.get_colonizer() != from)
3132 to_del.push_back(c.id);
3133 }
3134 for(auto c : to_del) {
3135 state.world.delete_colonization(c);
3136 }
3137 }
3138
3139 bool target_existed = state.world.nation_get_owned_province_count(target);
3140
3141 // po_remove_cores: also cores are removed from any territory taken / target territory if it is already owned by sender
3142 // po_transfer_provinces: all the valid states are transferred to the nation specified in the war goal. Relations between that
3143 // country and the nation that added the war goal increase by define:LIBERATE_STATE_RELATION_INCREASE. If the nation is newly
3144 // created by this, the nation it was created from gets a truce of define:BASE_TRUCE_MONTHS months with it (and it is placed
3145 // in the liberator's sphere if that nation is a GP).
3146
3147 if((bits & cb_flag::po_transfer_provinces) != 0) {
3148 auto target_tag = state.world.nation_get_identity_from_identity_holder(target);
3149 // No defined wargoal_t leads to defaulting to the from nation
3150 dcon::nation_id holder = from;
3151 if(wargoal_t) {
3152 holder = state.world.national_identity_get_nation_from_identity_holder(wargoal_t);
3153 if(!holder) {
3154 holder = state.world.create_nation();
3155 state.world.nation_set_identity_from_identity_holder(holder, wargoal_t);
3156 }
3157 if(auto lprovs = state.world.nation_get_province_ownership(holder); lprovs.begin() == lprovs.end()) {
3158 nations::create_nation_based_on_template(state, holder, from);
3159 }
3160 }
3161 // add to sphere if not existed
3162 if(!target_existed && state.world.nation_get_is_great_power(from)) {
3163 auto sr = state.world.force_create_gp_relationship(holder, from);
3164 auto& flags = state.world.gp_relationship_get_status(sr);
3166 state.world.nation_set_in_sphere_of(holder, from);
3167 }
3168 add_truce(state, holder, target, int32_t(state.defines.base_truce_months) * 30);
3169
3170 if((bits & cb_flag::all_allowed_states) == 0) {
3171 for(auto prov : state.world.state_definition_get_abstract_state_membership(wargoal_state)) {
3172 if(prov.get_province().get_nation_from_province_ownership() == target) {
3173 province::conquer_province(state, prov.get_province(), holder);
3174 if((bits & cb_flag::po_remove_cores) != 0) {
3175 auto find_core = state.world.get_core_by_prov_tag_key(prov.get_province(), target_tag);
3176 if(find_core) {
3177 state.world.delete_core(find_core);
3178 }
3179 }
3180 }
3181 }
3182 } else if(auto allowed_states = state.world.cb_type_get_allowed_states(wargoal); allowed_states) {
3183 std::vector<dcon::state_instance_id> prior_states;
3184 for(auto si : state.world.nation_get_state_ownership(target)) {
3185 if(trigger::evaluate(state, allowed_states, trigger::to_generic(si.get_state().id), trigger::to_generic(from), trigger::to_generic(holder))) {
3186 prior_states.push_back(si.get_state());
3187 }
3188 }
3189 for(auto si : prior_states) {
3190 for(auto prov : state.world.state_definition_get_abstract_state_membership(state.world.state_instance_get_definition(si))) {
3191 if(prov.get_province().get_nation_from_province_ownership() == target) {
3192 province::conquer_province(state, prov.get_province(), holder);
3193 if((bits & cb_flag::po_remove_cores) != 0) {
3194 auto find_core = state.world.get_core_by_prov_tag_key(prov.get_province(), target_tag);
3195 if(find_core) {
3196 state.world.delete_core(find_core);
3197 }
3198 }
3199 }
3200 }
3201 }
3202 }
3203 }
3204
3205 // po_demand_state: state is taken (this can turn into annex if it is the last state)
3206 if((bits & cb_flag::po_demand_state) != 0) {
3207 auto target_tag = state.world.nation_get_identity_from_identity_holder(target);
3208
3209 if((bits & cb_flag::all_allowed_states) == 0) {
3210 for(auto prov : state.world.state_definition_get_abstract_state_membership(wargoal_state)) {
3211 if(prov.get_province().get_nation_from_province_ownership() == target) {
3212 province::conquer_province(state, prov.get_province(), from);
3213 if((bits & cb_flag::po_remove_cores) != 0) {
3214 auto find_core = state.world.get_core_by_prov_tag_key(prov.get_province(), target_tag);
3215 if(find_core) {
3216 state.world.delete_core(find_core);
3217 }
3218 }
3219 }
3220 }
3221 } else if(auto allowed_states = state.world.cb_type_get_allowed_states(wargoal); allowed_states) {
3222 std::vector<dcon::state_instance_id> prior_states;
3223 for(auto si : state.world.nation_get_state_ownership(target)) {
3224 if(trigger::evaluate(state, allowed_states, trigger::to_generic(si.get_state().id), trigger::to_generic(from), trigger::to_generic(from))) {
3225 prior_states.push_back(si.get_state());
3226 }
3227 }
3228 for(auto si : prior_states) {
3229 for(auto prov :
3230 state.world.state_definition_get_abstract_state_membership(state.world.state_instance_get_definition(si))) {
3231
3232 if(prov.get_province().get_nation_from_province_ownership() == target) {
3233 province::conquer_province(state, prov.get_province(), from);
3234 if((bits & cb_flag::po_remove_cores) != 0) {
3235 auto find_core = state.world.get_core_by_prov_tag_key(prov.get_province(), target_tag);
3236 if(find_core) {
3237 state.world.delete_core(find_core);
3238 }
3239 }
3240 }
3241 }
3242 }
3243 }
3244
3245 if(war && target_existed && state.world.nation_get_owned_province_count(target) == 0) {
3246 if(state.military_definitions.liberate) {
3247 auto counter_wg = fatten(state.world, state.world.create_wargoal());
3248 counter_wg.set_added_by(
3249 for_attacker ? state.world.war_get_primary_defender(war) : state.world.war_get_primary_attacker(war));
3250 counter_wg.set_target_nation(from);
3251 counter_wg.set_associated_tag(state.world.nation_get_identity_from_identity_holder(target));
3252 counter_wg.set_type(state.military_definitions.liberate);
3253 state.world.force_create_wargoals_attached(war, counter_wg);
3254 }
3255 }
3256 }
3257
3258 // po_annex: nation is annexed, vassals and substates are freed, diplomatic relations are dissolved.
3259 if((bits & cb_flag::po_annex) != 0 && target != from) {
3260 for(auto n : state.world.in_nation) {
3261 auto rel = state.world.nation_get_overlord_as_subject(n);
3262 auto overlord = state.world.overlord_get_ruler(rel);
3263
3264 if(overlord == target) {
3265 state.world.overlord_set_ruler(rel, from);
3266 }
3267 }
3268
3269 auto target_tag = state.world.nation_get_identity_from_identity_holder(target);
3270 auto target_owns = state.world.nation_get_province_ownership(target);
3271
3272 while(target_owns.begin() != target_owns.end()) {
3273 auto prov = (*target_owns.begin()).get_province();
3274 province::conquer_province(state, prov, from);
3275 if((bits & cb_flag::po_remove_cores) != 0) {
3276 auto find_core = state.world.get_core_by_prov_tag_key(prov, target_tag);
3277 if(find_core) {
3278 state.world.delete_core(find_core);
3279 }
3280 }
3281 }
3282
3283 if(target_existed) {
3284 if(war && state.military_definitions.liberate) {
3285 auto counter_wg = fatten(state.world, state.world.create_wargoal());
3286 counter_wg.set_added_by(
3287 for_attacker ? state.world.war_get_primary_defender(war) : state.world.war_get_primary_attacker(war));
3288 counter_wg.set_target_nation(from);
3289 counter_wg.set_associated_tag(state.world.nation_get_identity_from_identity_holder(target));
3290 counter_wg.set_type(state.military_definitions.liberate);
3291 state.world.force_create_wargoals_attached(war, counter_wg);
3292 }
3293 }
3294 }
3295
3296 // other/in general: the `on_po_accepted` member of the CB is run. Primary slot: target of the war goal. This slot: nation
3297 // that added the war goal.
3298 auto accepted_effect = state.world.cb_type_get_on_po_accepted(wargoal);
3299 if(accepted_effect) {
3300 effect::execute(state, accepted_effect, trigger::to_generic(target), trigger::to_generic(from), trigger::to_generic(from),
3301 uint32_t((state.current_date.value << 8) ^ target.index()), uint32_t(from.index() ^ (wargoal.index() << 3)));
3302 }
3303
3304 // The nation that added the war goal gains prestige. This is done, by calculating the sum ,over all the po tags, of
3305 // base-prestige-for-that-tag v (nations-current-prestige x prestige-for-that-tag) and then multiplying the result by the CB's
3306 // prestige factor. The nation that was targeted by the war goal also loses that much prestige.
3307 float prestige_gain = successful_cb_prestige(state, wargoal, from) * (war ? 1.0f : state.defines.crisis_wargoal_prestige_mult);
3308 if(state.world.nation_get_owned_province_count(from) > 0)
3309 nations::adjust_prestige(state, from, prestige_gain);
3310 if(state.world.nation_get_owned_province_count(target) > 0)
3311 nations::adjust_prestige(state, target, -prestige_gain);
3312}
3313
3314void run_gc(sys::state& state) {
3315
3316 //
3317 // peace offers from dead nations
3318 //
3319
3320 auto remove_pending_offer = [&](dcon::peace_offer_id id) {
3321 for(auto& m : state.pending_messages) {
3322 if(m.type == diplomatic_message::type::peace_offer && m.data.peace == id) {
3324 return;
3325 }
3326 }
3327 };
3328 for(auto po : state.world.in_peace_offer) {
3329 if(!po.get_nation_from_pending_peace_offer()) {
3330 remove_pending_offer(po);
3331 state.world.delete_peace_offer(po);
3332 } else if(!po.get_war_from_war_settlement() && !po.get_is_crisis_offer()) {
3333 remove_pending_offer(po);
3334 state.world.delete_peace_offer(po);
3335 } else if(state.current_crisis_state == sys::crisis_state::inactive && po.get_is_crisis_offer()) {
3336 remove_pending_offer(po);
3337 state.world.delete_peace_offer(po);
3338 }
3339 }
3340
3341 //
3342 // war goals from nations that have left the war
3343 //
3344
3345 static std::vector<dcon::wargoal_id> to_delete;
3346 to_delete.clear();
3347 for(auto w : state.world.in_war) {
3348 for(auto wg : w.get_wargoals_attached()) {
3349 if(get_role(state, w, wg.get_wargoal().get_added_by()) == war_role::none || get_role(state, w, wg.get_wargoal().get_target_nation()) == war_role::none)
3350 to_delete.push_back(wg.get_wargoal());
3351 }
3352 }
3353 for(auto g : to_delete)
3354 state.world.delete_wargoal(g);
3355
3356 for(auto w : state.world.in_war) {
3357 if(get_role(state, w, w.get_primary_attacker()) != war_role::attacker || w.get_primary_attacker().get_overlord_as_subject().get_ruler()) {
3358 int32_t best_rank = 0;
3359 dcon::nation_id n;
3360 for(auto par : w.get_war_participant()) {
3361 if(par.get_is_attacker() && !(par.get_nation().get_overlord_as_subject().get_ruler())) {
3362 if(!n || par.get_nation().get_rank() < best_rank) {
3363 best_rank = par.get_nation().get_rank();
3364 n = par.get_nation();
3365 }
3366 }
3367 }
3368 if(!n) {
3370 continue;
3371 }
3372 w.set_primary_attacker(n);
3373 }
3374 if(get_role(state, w, w.get_primary_defender()) != war_role::defender || w.get_primary_defender().get_overlord_as_subject().get_ruler()) {
3375 int32_t best_rank = 0;
3376 dcon::nation_id n;
3377 for(auto par : w.get_war_participant()) {
3378 if(!par.get_is_attacker() && !(par.get_nation().get_overlord_as_subject().get_ruler())) {
3379 if(!n || par.get_nation().get_rank() < best_rank) {
3380 best_rank = par.get_nation().get_rank();
3381 n = par.get_nation();
3382 }
3383 }
3384 }
3385 if(!n) {
3387 continue;
3388 }
3389 w.set_primary_defender(n);
3390 }
3391 bool non_sq_war_goal = false;
3392 for(auto wg : w.get_wargoals_attached()) {
3393 // Has to truly be a status quo, not a pseudo status quo like the american cb on GFM
3394 if(wg.get_wargoal().get_type().get_type_bits() == cb_flag::po_status_quo) {
3395 // ignore status quo
3396 } else {
3397 non_sq_war_goal = true;
3398 break;
3399 }
3400 }
3401 if(!non_sq_war_goal)
3403 }
3404
3405
3406 //
3407 // wargoals that do not belong to a peace offer or war
3408 //
3409 for(auto wg : state.world.in_wargoal) {
3410 auto po = wg.get_peace_offer_from_peace_offer_item();
3411 auto wr = wg.get_war_from_wargoals_attached();
3412 if(!po && !wr)
3413 state.world.delete_wargoal(wg);
3414 }
3415
3416 //
3417 // empty armies / navies or leaderless ones
3418 //
3419 for(uint32_t i = state.world.navy_size(); i-- > 0; ) {
3420 dcon::navy_id n{ dcon::navy_id::value_base_t(i) };
3421 if(state.world.navy_is_valid(n)) {
3422 auto rng = state.world.navy_get_navy_membership(n);
3423 if(!state.world.navy_get_battle_from_navy_battle_participation(n)) {
3424 if(rng.begin() == rng.end() || !state.world.navy_get_controller_from_navy_control(n)) {
3425 military::cleanup_navy(state, n);
3426 }
3427 }
3428 }
3429 }
3430 for(uint32_t i = state.world.army_size(); i-- > 0; ) {
3431 dcon::army_id n{ dcon::army_id::value_base_t(i) };
3432 if(state.world.army_is_valid(n)) {
3433 auto rng = state.world.army_get_army_membership(n);
3434 if(!state.world.army_get_battle_from_army_battle_participation(n)) {
3435 if(rng.begin() == rng.end() || (!state.world.army_get_controller_from_army_rebel_control(n) && !state.world.army_get_controller_from_army_control(n))) {
3436 military::cleanup_army(state, n);
3437 }
3438 }
3439 }
3440 }
3441}
3442
3443void add_truce_between_sides(sys::state& state, dcon::war_id w, int32_t months) {
3444 auto wpar = state.world.war_get_war_participant(w);
3445 auto num_par = int32_t(wpar.end() - wpar.begin());
3446 auto end_truce = state.current_date + months * 31;
3447
3448 for(int32_t i = 0; i < num_par; ++i) {
3449 auto this_par = *(wpar.begin() + i);
3450 auto this_nation = this_par.get_nation();
3451
3452 if(this_nation.get_overlord_as_subject().get_ruler())
3453 continue;
3454
3455 auto attacker = this_par.get_is_attacker();
3456
3457 for(int32_t j = i + 1; j < num_par; ++j) {
3458 auto other_par = *(wpar.begin() + j);
3459 auto other_nation = other_par.get_nation();
3460
3461 if(!other_nation.get_overlord_as_subject().get_ruler() && attacker != other_par.get_is_attacker()) {
3462 auto rel = state.world.get_diplomatic_relation_by_diplomatic_pair(this_nation, other_nation);
3463 if(!rel) {
3464 rel = state.world.force_create_diplomatic_relation(this_nation, other_nation);
3465 }
3466 auto& current_truce = state.world.diplomatic_relation_get_truce_until(rel);
3467 if(!current_truce || current_truce < end_truce)
3468 current_truce = end_truce;
3469 }
3470 }
3471 }
3472}
3473void add_truce_from_nation(sys::state& state, dcon::war_id w, dcon::nation_id n, int32_t months) {
3474 auto end_truce = state.current_date + months * 31;
3475 bool attacker = military::is_attacker(state, w, n);
3476
3477 for(auto par : state.world.war_get_war_participant(w)) {
3478 auto other_nation = par.get_nation();
3479 if(other_nation.get_overlord_as_subject().get_ruler())
3480 continue;
3481
3482
3483 auto rel = state.world.get_diplomatic_relation_by_diplomatic_pair(n, other_nation);
3484 if(!rel) {
3485 rel = state.world.force_create_diplomatic_relation(n, other_nation);
3486 }
3487 auto& current_truce = state.world.diplomatic_relation_get_truce_until(rel);
3488 if(!current_truce || current_truce < end_truce)
3489 current_truce = end_truce;
3490 }
3491}
3492
3493void add_truce(sys::state& state, dcon::nation_id a, dcon::nation_id b, int32_t days) {
3494 auto end_truce = state.current_date + days;
3495
3496 auto rel = state.world.get_diplomatic_relation_by_diplomatic_pair(a, b);
3497 if(!rel) {
3498 rel = state.world.force_create_diplomatic_relation(a, b);
3499 }
3500 auto& current_truce = state.world.diplomatic_relation_get_truce_until(rel);
3501 if(!current_truce || current_truce < end_truce)
3502 current_truce = end_truce;
3503}
3504
3505void implement_peace_offer(sys::state& state, dcon::peace_offer_id offer) {
3506 dcon::nation_id from = state.world.peace_offer_get_nation_from_pending_peace_offer(offer);
3507 dcon::nation_id target = state.world.peace_offer_get_target(offer);
3508
3509
3510 auto war = state.world.peace_offer_get_war_from_war_settlement(offer);
3511
3512
3513 if(war) {
3515 [from, target, pa = state.world.war_get_primary_attacker(war), pd = state.world.war_get_primary_defender(war), name = state.world.war_get_name(war), tag = state.world.war_get_over_tag(war), st = state.world.war_get_over_state(war)](sys::state& state, text::layout_base& contents) {
3516 text::substitution_map sub;
3517 text::add_to_substitution_map(sub, text::variable_type::order, std::string_view(""));
3518 text::add_to_substitution_map(sub, text::variable_type::second, text::get_adjective(state, pd));
3519 text::add_to_substitution_map(sub, text::variable_type::second_country, pd);
3520 text::add_to_substitution_map(sub, text::variable_type::first, text::get_adjective(state, pa));
3521 text::add_to_substitution_map(sub, text::variable_type::third, tag);
3522 text::add_to_substitution_map(sub, text::variable_type::state, st);
3523
3524 std::string resolved_war_name = text::resolve_string_substitution(state, name, sub);
3525 text::add_line(state, contents, "msg_peace_offer_accepted_1", text::variable_type::x, target, text::variable_type::y, from, text::variable_type::val, std::string_view{resolved_war_name});
3526 },
3527 "msg_peace_offer_accepted_title",
3528 target, from, dcon::nation_id{},
3530 });
3531 }
3532
3533 bool contains_sq = false;
3534 //implementation order matters
3535 for(auto wg_offered : state.world.peace_offer_get_peace_offer_item(offer)) {
3536 auto wg = wg_offered.get_wargoal();
3537 implement_war_goal(state, state.world.peace_offer_get_war_from_war_settlement(offer), wg.get_type(),
3538 wg.get_added_by(), wg.get_target_nation(), wg.get_secondary_nation(), wg.get_associated_state(), wg.get_associated_tag());
3539 if((wg.get_type().get_type_bits() & military::cb_flag::po_status_quo) != 0)
3540 contains_sq = true;
3541 }
3542
3543 if(war) {
3544 auto truce_months = military::peace_offer_truce_months(state, offer);
3545 // remove successful WG
3546 auto offer_range = state.world.peace_offer_get_peace_offer_item(offer);
3547 while(offer_range.begin() != offer_range.end()) {
3548 state.world.delete_wargoal((*offer_range.begin()).get_wargoal());
3549 }
3550
3551 if((state.world.war_get_primary_attacker(war) == from && state.world.war_get_primary_defender(war) == target)
3552 || (contains_sq && military::is_attacker(state, war, from))) {
3553
3554 if(state.world.war_get_is_great(war)) {
3555 if(state.world.peace_offer_get_is_concession(offer) == false) {
3556 for(auto par : state.world.war_get_war_participant(war)) {
3557 if(par.get_is_attacker() == false) {
3558 implement_war_goal(state, war, state.military_definitions.standard_great_war, from, par.get_nation(),
3559 dcon::nation_id{}, dcon::state_definition_id{}, dcon::national_identity_id{});
3560 }
3561 }
3562 } else {
3563 for(auto par : state.world.war_get_war_participant(war)) {
3564 if(par.get_is_attacker() == true) {
3565 implement_war_goal(state, war, state.military_definitions.standard_great_war, target, par.get_nation(),
3566 dcon::nation_id{}, dcon::state_definition_id{}, dcon::national_identity_id{});
3567 }
3568 }
3569 }
3570 }
3571 add_truce_between_sides(state, war, truce_months);
3572 cleanup_war(state, war,
3573 state.world.peace_offer_get_is_concession(offer) ? war_result::defender_won : war_result::attacker_won);
3574
3575 } else if((state.world.war_get_primary_attacker(war) == target && state.world.war_get_primary_defender(war) == from)
3576 || (contains_sq && !military::is_attacker(state, war, from))) {
3577
3578 if(state.world.war_get_is_great(war)) {
3579 if(state.world.peace_offer_get_is_concession(offer) == false) {
3580 for(auto par : state.world.war_get_war_participant(war)) {
3581 if(par.get_is_attacker() == true) {
3582 implement_war_goal(state, war, state.military_definitions.standard_great_war, from, par.get_nation(),
3583 dcon::nation_id{}, dcon::state_definition_id{}, dcon::national_identity_id{});
3584 }
3585 }
3586 } else {
3587 for(auto par : state.world.war_get_war_participant(war)) {
3588 if(par.get_is_attacker() == false) {
3589 implement_war_goal(state, war, state.military_definitions.standard_great_war, target, par.get_nation(),
3590 dcon::nation_id{}, dcon::state_definition_id{}, dcon::national_identity_id{});
3591 }
3592 }
3593 }
3594 }
3595 add_truce_between_sides(state, war, truce_months);
3596 cleanup_war(state, war,
3597 state.world.peace_offer_get_is_concession(offer) ? war_result::attacker_won : war_result::defender_won);
3598
3599 } else if(state.world.war_get_primary_attacker(war) == from || state.world.war_get_primary_defender(war) == from) {
3600
3601 if(state.world.war_get_is_great(war) && state.world.peace_offer_get_is_concession(offer) == false) {
3602 implement_war_goal(state, war, state.military_definitions.standard_great_war, from, target, dcon::nation_id{},
3603 dcon::state_definition_id{}, dcon::national_identity_id{});
3604 }
3605
3606 if(state.world.nation_get_owned_province_count(state.world.war_get_primary_attacker(war)) == 0) {
3607 add_truce_between_sides(state, war, truce_months);
3608 cleanup_war(state, war, war_result::defender_won);
3609 } else if(state.world.nation_get_owned_province_count(state.world.war_get_primary_defender(war)) == 0) {
3610 add_truce_between_sides(state, war, truce_months);
3611 cleanup_war(state, war, war_result::attacker_won);
3612 } else {
3613 add_truce_from_nation(state, war, target, truce_months);
3614 remove_from_war(state, war, target, state.world.peace_offer_get_is_concession(offer) == false);
3615 }
3616 } else if(state.world.war_get_primary_attacker(war) == target || state.world.war_get_primary_defender(war) == target) {
3617
3618 if(state.world.war_get_is_great(war) && state.world.peace_offer_get_is_concession(offer) == true) {
3619 implement_war_goal(state, war, state.military_definitions.standard_great_war, target, from, dcon::nation_id{},
3620 dcon::state_definition_id{}, dcon::national_identity_id{});
3621 }
3622
3623 if(state.world.nation_get_owned_province_count(state.world.war_get_primary_attacker(war)) == 0) {
3624 add_truce_between_sides(state, war, truce_months);
3625 cleanup_war(state, war, war_result::defender_won);
3626 } else if(state.world.nation_get_owned_province_count(state.world.war_get_primary_defender(war)) == 0) {
3627 add_truce_between_sides(state, war, truce_months);
3628 cleanup_war(state, war, war_result::attacker_won);
3629 } else {
3630 add_truce_from_nation(state, war, from, truce_months);
3631 remove_from_war(state, war, from, state.world.peace_offer_get_is_concession(offer) == true);
3632 }
3633 } else {
3634 assert(false);
3635 }
3636 } else { // crisis offer
3637 bool crisis_attackers_won = (from == state.primary_crisis_attacker) == (state.world.peace_offer_get_is_concession(offer) == false);
3638
3639 auto truce_months = military::peace_offer_truce_months(state, offer);
3640
3641 for(auto wg : state.world.peace_offer_get_peace_offer_item(offer)) {
3642 add_truce(state, wg.get_wargoal().get_added_by(), wg.get_wargoal().get_target_nation(), truce_months * 31);
3643 }
3644
3645 for(auto swg : state.crisis_attacker_wargoals) {
3646 bool was_part_of_offer = false;
3647 for(auto wg : state.world.peace_offer_get_peace_offer_item(offer)) {
3648 if(wg.get_wargoal().get_added_by() == swg.added_by)
3649 was_part_of_offer = true;
3650 }
3651
3652 if(!was_part_of_offer) {
3653 float prestige_loss = std::min(state.defines.war_failed_goal_prestige_base,
3654 state.defines.war_failed_goal_prestige * state.defines.crisis_wargoal_prestige_mult *
3655 nations::prestige_score(state, swg.added_by)) *
3656 state.world.cb_type_get_penalty_factor(swg.cb);
3657 nations::adjust_prestige(state, swg.added_by, prestige_loss);
3658
3659 auto pop_militancy = state.defines.war_failed_goal_militancy * state.defines.crisis_wargoal_militancy_mult * state.world.cb_type_get_penalty_factor(swg.cb);
3660 if(pop_militancy > 0) {
3661 for(auto prv : state.world.nation_get_province_ownership(swg.added_by)) {
3662 for(auto pop : prv.get_province().get_pop_location()) {
3663 auto mil = pop_demographics::get_militancy(state, pop.get_pop());
3664 pop_demographics::set_militancy(state, pop.get_pop().id, std::min(mil + pop_militancy, 10.0f));
3665 }
3666 }
3667 }
3668
3669 if(crisis_attackers_won) {
3670 // Wargoal added by attacker. They won, but leader ignored wargoal
3671 nations::adjust_relationship(state, swg.added_by, state.primary_crisis_attacker,
3672 -state.defines.crisis_winner_relations_impact);
3673 }
3674 else {
3675 // Wargoal added by attacker. They lost
3676 nations::adjust_relationship(state, swg.added_by, state.primary_crisis_defender,
3677 -state.defines.crisis_winner_relations_impact);
3678 }
3679 } else {
3680 if(crisis_attackers_won) {
3681 // Wargoal added by attacker. They won and leader enforced WG
3682 nations::adjust_relationship(state, swg.added_by, state.primary_crisis_attacker,
3683 state.defines.crisis_winner_relations_impact);
3684 } else {
3685 // Wargoal added by attacker. They lost and defender leader enforced WG
3686 nations::adjust_relationship(state, swg.added_by, state.primary_crisis_defender,
3687 state.defines.crisis_winner_relations_impact);
3688 }
3689 }
3690 }
3691
3692 for(auto swg : state.crisis_attacker_wargoals) {
3693 bool was_part_of_offer = false;
3694 for(auto wg : state.world.peace_offer_get_peace_offer_item(offer)) {
3695 if(wg.get_wargoal().get_added_by() == swg.added_by)
3696 was_part_of_offer = true;
3697 }
3698
3699 if(!was_part_of_offer) {
3700 float prestige_loss = std::min(state.defines.war_failed_goal_prestige_base,
3701 state.defines.war_failed_goal_prestige * state.defines.crisis_wargoal_prestige_mult *
3702 nations::prestige_score(state, swg.added_by)) *
3703 state.world.cb_type_get_penalty_factor(swg.cb);
3704 nations::adjust_prestige(state, swg.added_by, prestige_loss);
3705
3706 auto pop_militancy = state.defines.war_failed_goal_militancy * state.defines.crisis_wargoal_militancy_mult * state.world.cb_type_get_penalty_factor(swg.cb);
3707 if(pop_militancy > 0) {
3708 for(auto prv : state.world.nation_get_province_ownership(swg.added_by)) {
3709 for(auto pop : prv.get_province().get_pop_location()) {
3710 auto mil = pop_demographics::get_militancy(state, pop.get_pop());
3711 pop_demographics::set_militancy(state, pop.get_pop().id, std::min(mil + pop_militancy, 10.0f));
3712 }
3713 }
3714 }
3715
3716 if(crisis_attackers_won) {
3717 // Wargoal added by defender. They lost, but leader ignored wargoal
3718 nations::adjust_relationship(state, swg.added_by, state.primary_crisis_defender,
3719 -state.defines.crisis_winner_relations_impact);
3720 } else {
3721 // Wargoal added by defender. They won and defender leader enforced WG
3722 nations::adjust_relationship(state, swg.added_by, state.primary_crisis_attacker,
3723 -state.defines.crisis_winner_relations_impact);
3724 }
3725 } else {
3726 if(crisis_attackers_won) {
3727 // Wargoal added by defender. They lost and attacker leader enforced WG
3728 nations::adjust_relationship(state, swg.added_by, state.primary_crisis_attacker,
3729 state.defines.crisis_winner_relations_impact);
3730 } else {
3731 // Wargoal added by defender. They won and defender leader enforced WG
3732 nations::adjust_relationship(state, swg.added_by, state.primary_crisis_defender,
3733 state.defines.crisis_winner_relations_impact);
3734 }
3735 }
3736 }
3737
3738 for(auto& par : state.crisis_participants) {
3739 if(!par.id)
3740 break;
3741
3742 if(par.merely_interested == false && par.id != state.primary_crisis_attacker && par.id != state.primary_crisis_defender) {
3743 if(crisis_attackers_won != par.supports_attacker) {
3744 if(par.supports_attacker) {
3745 nations::adjust_relationship(state, par.id, state.primary_crisis_attacker,
3746 -state.defines.crisis_winner_relations_impact);
3747 } else {
3748 nations::adjust_relationship(state, par.id, state.primary_crisis_defender,
3749 -state.defines.crisis_winner_relations_impact);
3750 }
3751 } else {
3752 if(par.supports_attacker) {
3753 nations::adjust_relationship(state, par.id, state.primary_crisis_attacker,
3754 state.defines.crisis_winner_relations_impact);
3755 } else {
3756 nations::adjust_relationship(state, par.id, state.primary_crisis_defender,
3757 state.defines.crisis_winner_relations_impact);
3758 }
3759 }
3760 }
3761 }
3762
3763 if(crisis_attackers_won) {
3764
3765 float p_factor = 0.05f * (state.defines.crisis_winner_prestige_factor_base +
3766 state.defines.crisis_winner_prestige_factor_year * float(state.current_date.value) / float(365));
3767
3768 nations::adjust_prestige(state, state.primary_crisis_defender,
3769 -p_factor * nations::prestige_score(state, state.primary_crisis_attacker));
3770 nations::adjust_prestige(state, state.primary_crisis_attacker,
3771 p_factor * nations::prestige_score(state, state.primary_crisis_attacker));
3772
3773 auto rp_ideology = state.world.nation_get_ruling_party(state.primary_crisis_defender).get_ideology();
3774 if(rp_ideology) {
3775 for(auto prv : state.world.nation_get_province_ownership(state.primary_crisis_defender)) {
3776 prv.get_province().get_party_loyalty(rp_ideology) *= (1.0f - state.defines.party_loyalty_hit_on_war_loss);
3777 }
3778 }
3779 } else {
3780
3781 float p_factor = 0.05f * (state.defines.crisis_winner_prestige_factor_base +
3782 state.defines.crisis_winner_prestige_factor_year * float(state.current_date.value) / float(365));
3783
3784 nations::adjust_prestige(state, state.primary_crisis_attacker,
3785 -p_factor * nations::prestige_score(state, state.primary_crisis_attacker));
3786 nations::adjust_prestige(state, state.primary_crisis_defender,
3787 p_factor * nations::prestige_score(state, state.primary_crisis_attacker));
3788
3789 auto rp_ideology = state.world.nation_get_ruling_party(state.primary_crisis_attacker).get_ideology();
3790 if(rp_ideology) {
3791 for(auto prv : state.world.nation_get_province_ownership(state.primary_crisis_attacker)) {
3792 prv.get_province().get_party_loyalty(rp_ideology) *= (1.0f - state.defines.party_loyalty_hit_on_war_loss);
3793 }
3794 }
3795 }
3796 }
3797
3798 state.world.peace_offer_set_war_from_war_settlement(offer, dcon::war_id{});
3799 state.world.peace_offer_set_is_crisis_offer(offer, false);
3800}
3801
3802void reject_peace_offer(sys::state& state, dcon::peace_offer_id offer) {
3803 dcon::nation_id from = state.world.peace_offer_get_nation_from_pending_peace_offer(offer);
3804 dcon::nation_id target = state.world.peace_offer_get_target(offer);
3806
3807 auto war = state.world.peace_offer_get_war_from_war_settlement(offer);
3808
3809 if(war) {
3811 [from, target, pa = state.world.war_get_primary_attacker(war), pd = state.world.war_get_primary_defender(war), name = state.world.war_get_name(war), tag = state.world.war_get_over_tag(war), st = state.world.war_get_over_state(war)](sys::state& state, text::layout_base& contents) {
3812 text::substitution_map sub;
3813 text::add_to_substitution_map(sub, text::variable_type::order, std::string_view(""));
3814 text::add_to_substitution_map(sub, text::variable_type::second, text::get_adjective(state, pd));
3815 text::add_to_substitution_map(sub, text::variable_type::second_country, pd);
3816 text::add_to_substitution_map(sub, text::variable_type::first, text::get_adjective(state, pa));
3817 text::add_to_substitution_map(sub, text::variable_type::third, tag);
3818 text::add_to_substitution_map(sub, text::variable_type::state, st);
3819
3820 std::string resolved_war_name = text::resolve_string_substitution(state, name, sub);
3821 text::add_line(state, contents, "msg_peace_offer_rejected_1", text::variable_type::x, target, text::variable_type::y, from, text::variable_type::val, std::string_view{resolved_war_name});
3822 },
3823 "msg_peace_offer_rejected_title",
3824 target, from, dcon::nation_id{},
3826 });
3827 }
3828
3829 // TODO: detect
3830 /*
3831 If a "good" peace offer is refused, the refusing nation gains define:GOOD_PEACE_REFUSAL_WAREXH war exhaustion and all of its
3832 pops gain define:GOOD_PEACE_REFUSAL_MILITANCY. What counts as a good offer, well if the peace offer is considered "better"
3833 than expected. This seems to be a complicated thing to calculate involving: the direction the war is going in (sign of the
3834 latest war score change), the overall quantity of forces on each side (with navies counting for less), time since the war
3835 began, war exhaustion, war score, the peace cost of the offer, and whether the recipient will be annexed as a result.
3836 */
3837
3838 state.world.peace_offer_set_war_from_war_settlement(offer, dcon::war_id{});
3839 state.world.peace_offer_set_is_crisis_offer(offer, false);
3840}
3841
3843 for(auto wg : state.world.in_wargoal) {
3844 auto war = wg.get_war_from_wargoals_attached();
3845 if(!war)
3846 continue;
3847
3848 auto attacker_goal = military::is_attacker(state, war, wg.get_added_by());
3849 auto role = attacker_goal ? war_role::attacker : war_role::defender;
3850
3851 /*
3852 #### Occupation score
3853
3854 Increases by occupation-percentage x define:TWS_FULFILLED_SPEED (up to define:TWS_CB_LIMIT_DEFAULT) when the percentage
3855 occupied is >= define:TWS_FULFILLED_IDLE_SPACE or when the occupation percentage is > 0 and the current occupation score
3856 is negative. If there is no occupation, the score decreases by define:TWS_NOT_FULFILLED_SPEED. This can only take the
3857 score into negative after define:TWS_GRACE_PERIOD_DAYS, at which point it can go to -define:TWS_CB_LIMIT_DEFAULT.
3858 */
3859
3860 auto bits = wg.get_type().get_type_bits();
3861 if((bits & (cb_flag::po_annex | cb_flag::po_transfer_provinces | cb_flag::po_demand_state)) != 0) {
3862 float total_count = 0.0f;
3863 float occupied = 0.0f;
3864 if(wg.get_associated_state()) {
3865 for(auto prv : wg.get_associated_state().get_abstract_state_membership()) {
3866 if(prv.get_province().get_nation_from_province_ownership() == wg.get_target_nation()) {
3867 ++total_count;
3868 if(get_role(state, war, prv.get_province().get_nation_from_province_control()) == role) {
3869 ++occupied;
3870 }
3871 }
3872 }
3873 } else if((bits & cb_flag::po_annex) != 0) {
3874 for(auto prv : wg.get_target_nation().get_province_ownership()) {
3875 ++total_count;
3876 if(get_role(state, war, prv.get_province().get_nation_from_province_control()) == role) {
3877 ++occupied;
3878 }
3879 }
3880 } else if(auto allowed_states = wg.get_type().get_allowed_states(); allowed_states) {
3881 auto from_slot = wg.get_secondary_nation().id ? wg.get_secondary_nation().id : wg.get_associated_tag().get_nation_from_identity_holder().id;
3882 bool is_lib = (bits & cb_flag::po_transfer_provinces) != 0;
3883 for(auto st : wg.get_target_nation().get_state_ownership()) {
3884 if(trigger::evaluate(state, allowed_states, trigger::to_generic(st.get_state().id), trigger::to_generic(wg.get_added_by().id), is_lib ? trigger::to_generic(from_slot) : trigger::to_generic(wg.get_added_by().id))) {
3885
3886 province::for_each_province_in_state_instance(state, st.get_state(), [&](dcon::province_id prv) {
3887 ++total_count;
3888 if(get_role(state, war, state.world.province_get_nation_from_province_control(prv)) == role) {
3889 ++occupied;
3890 }
3891 });
3892 }
3893 }
3894 }
3895
3896 if(total_count > 0.0f) {
3897 float fraction = occupied / total_count;
3898 if(fraction >= state.defines.tws_fulfilled_idle_space || (wg.get_ticking_war_score() < 0 && occupied > 0.0f)) {
3899 wg.get_ticking_war_score() += state.defines.tws_fulfilled_speed * fraction;
3900 } else if(occupied == 0.0f) {
3901 if(wg.get_ticking_war_score() > 0.0f || war.get_start_date() + int32_t(state.defines.tws_grace_period_days) <= state.current_date) {
3902 wg.get_ticking_war_score() -= state.defines.tws_not_fulfilled_speed;
3903 }
3904 }
3905 }
3906 }
3907
3908 // Ticking warscope for make_puppet war
3909 if((bits & cb_flag::po_make_puppet) != 0) {
3910
3911 auto target = wg.get_target_nation().get_capital();
3912
3913 if(get_role(state, war, target.get_nation_from_province_control()) == role) {
3914 wg.get_ticking_war_score() += state.defines.tws_fulfilled_speed;
3915 }
3916 else if(wg.get_ticking_war_score() > 0.0f || war.get_start_date() + int32_t(state.defines.tws_grace_period_days) <= state.current_date) {
3917 wg.get_ticking_war_score() -= state.defines.tws_not_fulfilled_speed;
3918 }
3919 }
3920
3921 if(wg.get_type().get_tws_battle_factor() > 0) {
3922
3923 /*
3924 #### Battle score
3925
3926 - zero if fewer than define:TWS_BATTLE_MIN_COUNT have been fought
3927 - only if the war goal has tws_battle_factor > 0
3928 - calculate relative losses for each side (something on the order of the difference in losses / 10,000 for land combat
3929 or the difference in losses / 10 for sea combat) with the points going to the winner, and then take the total of the
3930 relative loss scores for both sides and divide by the relative loss score for the defender.
3931 - subtract from tws_battle_factor and then divide by define:TWS_BATTLE_MAX_ASPECT (limited to -1 to +1). This then
3932 works is the occupied percentage described below.
3933 */
3934 if(war.get_number_of_battles() >= uint16_t(state.defines.tws_battle_min_count)) {
3935
3936 float ratio = 0.0f;
3937 if(attacker_goal) {
3938 ratio = war.get_defender_battle_score() > 0.0f ? war.get_attacker_battle_score() / war.get_defender_battle_score() : 10.0f;
3939 } else {
3940 ratio = war.get_attacker_battle_score() > 0.0f ? war.get_defender_battle_score() / war.get_attacker_battle_score() : 10.0f;
3941 }
3942 if(ratio >= wg.get_type().get_tws_battle_factor()) {
3943 auto effective_percentage = std::min(ratio / state.defines.tws_battle_max_aspect, 1.0f);
3944 wg.get_ticking_war_score() += state.defines.tws_fulfilled_speed * effective_percentage;
3945 } else if(ratio <= 1.0f / wg.get_type().get_tws_battle_factor() && ratio > 0.0f) {
3946 auto effective_percentage = std::min(1.0f / (ratio * state.defines.tws_battle_max_aspect), 1.0f);
3947 wg.get_ticking_war_score() -= state.defines.tws_fulfilled_speed * effective_percentage;
3948 } else if(ratio == 0.0f) {
3949 wg.get_ticking_war_score() -= state.defines.tws_fulfilled_speed;
3950 }
3951 }
3952 }
3953 auto max_score = peace_cost(state, war, wg.get_type(), wg.get_added_by(), wg.get_target_nation(), wg.get_secondary_nation(), wg.get_associated_state(), wg.get_associated_tag());
3954
3955 wg.get_ticking_war_score() =
3956 std::clamp(wg.get_ticking_war_score(), -float(max_score), float(max_score));
3957 }
3959
3960float primary_warscore_from_blockades(sys::state& state, dcon::war_id w) {
3961 auto pattacker = state.world.war_get_primary_attacker(w);
3962 auto pdefender = state.world.war_get_primary_defender(w);
3963
3964 auto d_cpc = state.world.nation_get_central_ports(pdefender);
3965 int32_t d_blockaded_in_war = 0;
3966 for(auto p : state.world.nation_get_province_ownership(pdefender)) {
3967 if(military::province_is_blockaded(state, p.get_province()) && !province::is_overseas(state, p.get_province().id)) {
3968 for(auto v : state.world.province_get_navy_location(p.get_province().get_port_to())) {
3969 if(!v.get_navy().get_is_retreating() && !v.get_navy().get_battle_from_navy_battle_participation()) {
3970 if(military::get_role(state, w, v.get_navy().get_controller_from_navy_control()) == military::war_role::attacker) {
3971 ++d_blockaded_in_war;
3972 break; // out of inner loop
3973 }
3974 }
3975 }
3976 }
3977 }
3978 auto def_b_frac = std::clamp(d_cpc > 0 ? float(d_blockaded_in_war) / float(d_cpc) : 0.0f, 0.0f, 1.0f);
3979
3980 int32_t a_blockaded_in_war = 0;
3981 for(auto p : state.world.nation_get_province_ownership(pattacker)) {
3982 if(military::province_is_blockaded(state, p.get_province()) && !province::is_overseas(state, p.get_province().id)) {
3983 for(auto v : state.world.province_get_navy_location(p.get_province().get_port_to())) {
3984 if(!v.get_navy().get_is_retreating() && !v.get_navy().get_battle_from_navy_battle_participation()) {
3985 if(military::get_role(state, w, v.get_navy().get_controller_from_navy_control()) == military::war_role::defender) {
3986 ++a_blockaded_in_war;
3987 break; // out of inner loop
3988 }
3989 }
3990 }
3991 }
3992 }
3993 auto a_cpc = state.world.nation_get_central_ports(pattacker);
3994 auto att_b_frac = std::clamp(a_cpc > 0 ? float(a_blockaded_in_war) / float(a_cpc) : 0.0f, 0.0f, 1.0f);
3995
3996 return 25.0f * (def_b_frac - att_b_frac);
3998
3999float primary_warscore(sys::state& state, dcon::war_id w) {
4000 return std::clamp(
4001 primary_warscore_from_occupation(state, w)
4002 + primary_warscore_from_battles(state, w)
4003 + primary_warscore_from_blockades(state, w)
4004 + primary_warscore_from_war_goals(state, w), -100.0f, 100.0f);
4006
4007float primary_warscore_from_occupation(sys::state& state, dcon::war_id w) {
4008 float total = 0.0f;
4009
4010 auto pattacker = state.world.war_get_primary_attacker(w);
4011 auto pdefender = state.world.war_get_primary_defender(w);
4012
4013 int32_t sum_attacker_prov_values = 0;
4014 int32_t sum_attacker_occupied_values = 0;
4015 for(auto prv : state.world.nation_get_province_ownership(pattacker)) {
4016 auto v = province_point_cost(state, prv.get_province(), pattacker);
4017 sum_attacker_prov_values += v;
4018 if(get_role(state, w, prv.get_province().get_nation_from_province_control()) == war_role::defender)
4019 sum_attacker_occupied_values += v;
4020 }
4021
4022 int32_t sum_defender_prov_values = 0;
4023 int32_t sum_defender_occupied_values = 0;
4024 for(auto prv : state.world.nation_get_province_ownership(pdefender)) {
4025 auto v = province_point_cost(state, prv.get_province(), pdefender);
4026 sum_defender_prov_values += v;
4027 if(get_role(state, w, prv.get_province().get_nation_from_province_control()) == war_role::attacker)
4028 sum_defender_occupied_values += v;
4029 }
4030
4031 if(sum_defender_prov_values > 0)
4032 total += (float(sum_defender_occupied_values) * 100.0f) / float(sum_defender_prov_values);
4033 if(sum_attacker_prov_values > 0)
4034 total -= (float(sum_attacker_occupied_values) * 100.0f) / float(sum_attacker_prov_values);
4035
4036 return total;
4037}
4038float primary_warscore_from_battles(sys::state& state, dcon::war_id w) {
4039 return std::clamp(state.world.war_get_attacker_battle_score(w) - state.world.war_get_defender_battle_score(w),
4040 -state.defines.max_warscore_from_battles, state.defines.max_warscore_from_battles);
4041}
4042float primary_warscore_from_war_goals(sys::state& state, dcon::war_id w) {
4043 float total = 0.0f;
4044
4045 for(auto wg : state.world.war_get_wargoals_attached(w)) {
4046 if(is_attacker(state, w, wg.get_wargoal().get_added_by())) {
4047 total += wg.get_wargoal().get_ticking_war_score();
4048 } else {
4049 total -= wg.get_wargoal().get_ticking_war_score();
4050 }
4051 }
4052
4053 return total;
4055
4056float directed_warscore(sys::state& state, dcon::war_id w, dcon::nation_id primary, dcon::nation_id secondary) {
4057 float total = 0.0f;
4058
4059 auto is_pattacker = state.world.war_get_primary_attacker(w) == primary;
4060 auto is_pdefender = state.world.war_get_primary_defender(w) == primary;
4061
4062 auto is_tpattacker = state.world.war_get_primary_attacker(w) == secondary;
4063 auto is_tpdefender = state.world.war_get_primary_defender(w) == secondary;
4064
4065 if(is_pattacker && is_tpdefender)
4066 return primary_warscore(state, w);
4067 if(is_pdefender && is_tpattacker)
4068 return -primary_warscore(state, w);
4069
4070 int32_t sum_attacker_prov_values = 0;
4071 int32_t sum_attacker_occupied_values = 0;
4072 for(auto prv : state.world.nation_get_province_ownership(primary)) {
4073 auto v = province_point_cost(state, prv.get_province(), primary);
4074 sum_attacker_prov_values += v;
4075
4076 if(is_tpattacker) {
4077 if(get_role(state, w, prv.get_province().get_nation_from_province_control()) == war_role::attacker)
4078 sum_attacker_occupied_values += v;
4079 } else if(is_tpdefender) {
4080 if(get_role(state, w, prv.get_province().get_nation_from_province_control()) == war_role::defender)
4081 sum_attacker_occupied_values += v;
4082 } else {
4083 if(prv.get_province().get_nation_from_province_control() == secondary)
4084 sum_attacker_occupied_values += v;
4085 }
4086 }
4087
4088 int32_t sum_defender_prov_values = 0;
4089 int32_t sum_defender_occupied_values = 0;
4090 for(auto prv : state.world.nation_get_province_ownership(secondary)) {
4091 auto v = province_point_cost(state, prv.get_province(), secondary);
4092 sum_defender_prov_values += v;
4093
4094 if(is_pattacker) {
4095 if(get_role(state, w, prv.get_province().get_nation_from_province_control()) == war_role::attacker)
4096 sum_defender_occupied_values += v;
4097 } else if(is_pdefender) {
4098 if(get_role(state, w, prv.get_province().get_nation_from_province_control()) == war_role::defender)
4099 sum_defender_occupied_values += v;
4100 } else {
4101 if(prv.get_province().get_nation_from_province_control() == primary)
4102 sum_defender_occupied_values += v;
4103 }
4104 }
4105
4106 if(sum_defender_prov_values > 0)
4107 total += (float(sum_defender_occupied_values) * 100.0f) / float(sum_defender_prov_values);
4108 if(sum_attacker_prov_values > 0)
4109 total -= (float(sum_attacker_occupied_values) * 100.0f) / float(sum_attacker_prov_values);
4110
4111 for(auto wg : state.world.war_get_wargoals_attached(w)) {
4112 if((wg.get_wargoal().get_added_by() == primary || is_pattacker || is_pdefender)
4113 && wg.get_wargoal().get_target_nation() == secondary) {
4114
4115 total += wg.get_wargoal().get_ticking_war_score();
4116 } else if(wg.get_wargoal().get_added_by() == secondary
4117 && (wg.get_wargoal().get_target_nation() == primary || is_pattacker || is_pdefender)) {
4118
4119 total -= wg.get_wargoal().get_ticking_war_score();
4120 } else if(wg.get_wargoal().get_added_by() == primary
4121 && (wg.get_wargoal().get_target_nation() == secondary || is_tpattacker || is_tpdefender)) {
4122
4123 total += wg.get_wargoal().get_ticking_war_score();
4124 } else if((wg.get_wargoal().get_added_by() == secondary || is_tpattacker || is_tpdefender)
4125 && (wg.get_wargoal().get_target_nation() == primary)) {
4126
4127 total -= wg.get_wargoal().get_ticking_war_score();
4128 }
4129 }
4130
4131 auto d_cpc = state.world.nation_get_central_ports(secondary);
4132 int32_t d_blockaded_in_war = 0;
4133 for(auto p : state.world.nation_get_province_ownership(secondary)) {
4134 if(military::province_is_blockaded(state, p.get_province()) && !province::is_overseas(state, p.get_province().id)) {
4135 for(auto v : state.world.province_get_navy_location(p.get_province().get_port_to())) {
4136 if(!v.get_navy().get_is_retreating() && !v.get_navy().get_battle_from_navy_battle_participation()) {
4137
4138 if(is_pattacker) {
4139 if(get_role(state, w, v.get_navy().get_controller_from_navy_control()) == war_role::attacker) {
4140 ++d_blockaded_in_war;
4141 break; // out of inner loop
4142 }
4143 } else if(is_pdefender) {
4144 if(get_role(state, w, v.get_navy().get_controller_from_navy_control()) == war_role::defender) {
4145 ++d_blockaded_in_war;
4146 break; // out of inner loop
4147 }
4148 } else {
4149 if(v.get_navy().get_controller_from_navy_control() == primary) {
4150 ++d_blockaded_in_war;
4151 break; // out of inner loop
4152 }
4153 }
4154 }
4155 }
4156 }
4157 }
4158 auto def_b_frac = std::clamp(d_cpc > 0 ? float(d_blockaded_in_war) / float(d_cpc) : 0.0f, 0.0f, 1.0f);
4159
4160 int32_t a_blockaded_in_war = 0;
4161 for(auto p : state.world.nation_get_province_ownership(primary)) {
4162 if(military::province_is_blockaded(state, p.get_province()) && !province::is_overseas(state, p.get_province().id)) {
4163 for(auto v : state.world.province_get_navy_location(p.get_province().get_port_to())) {
4164 if(!v.get_navy().get_is_retreating() && !v.get_navy().get_battle_from_navy_battle_participation()) {
4165 if(is_tpattacker) {
4166 if(get_role(state, w, v.get_navy().get_controller_from_navy_control()) == war_role::attacker) {
4167 ++a_blockaded_in_war;
4168 break; // out of inner loop
4169 }
4170 } else if(is_tpdefender) {
4171 if(get_role(state, w, v.get_navy().get_controller_from_navy_control()) == war_role::defender) {
4172 ++a_blockaded_in_war;
4173 break; // out of inner loop
4174 }
4175 } else {
4176 if(v.get_navy().get_controller_from_navy_control() == secondary) {
4177 ++a_blockaded_in_war;
4178 break; // out of inner loop
4179 }
4180 }
4181 }
4182 }
4183 }
4184 }
4185 auto a_cpc = state.world.nation_get_central_ports(primary);
4186 auto att_b_frac = std::clamp(a_cpc > 0 ? float(a_blockaded_in_war) / float(a_cpc) : 0.0f, 0.0f, 1.0f);
4187
4188 total += 25.0f * (def_b_frac - att_b_frac);
4189
4190 return std::clamp(total, 0.0f, 100.0f);
4192
4193bool can_embark_onto_sea_tile(sys::state& state, dcon::nation_id from, dcon::province_id p, dcon::army_id a) {
4194 int32_t max_cap = 0;
4195 for(auto n : state.world.province_get_navy_location(p)) {
4196 if(n.get_navy().get_controller_from_navy_control() == from &&
4197 !bool(n.get_navy().get_battle_from_navy_battle_participation())) {
4198 max_cap = std::max(free_transport_capacity(state, n.get_navy()), max_cap);
4199 }
4200 }
4201 auto regs = state.world.army_get_army_membership(a);
4202 return int32_t(regs.end() - regs.begin()) <= max_cap;
4204
4205dcon::navy_id find_embark_target(sys::state& state, dcon::nation_id from, dcon::province_id p, dcon::army_id a) {
4206 auto regs = state.world.army_get_army_membership(a);
4207 int32_t count = int32_t(regs.end() - regs.begin());
4208
4209 int32_t max_cap = 0;
4210 for(auto n : state.world.province_get_navy_location(p)) {
4211 if(n.get_navy().get_controller_from_navy_control() == from) {
4212 if(free_transport_capacity(state, n.get_navy()) >= count)
4213 return n.get_navy();
4214 }
4215 }
4216 return dcon::navy_id{};
4218
4219float effective_army_speed(sys::state& state, dcon::army_id a) {
4220 auto owner = state.world.army_get_controller_from_army_control(a);
4221 if(!owner)
4222 owner = state.world.rebel_faction_get_ruler_from_rebellion_within(state.world.army_get_controller_from_army_rebel_control(a));
4223
4224 float min_speed = 10000.0f;
4225 for(auto reg : state.world.army_get_army_membership(a)) {
4226 auto reg_speed = state.world.nation_get_unit_stats(owner, reg.get_regiment().get_type()).maximum_speed;
4227 min_speed = std::min(min_speed, reg_speed);
4228 }
4229
4230 /*
4231 slowest ship or regiment x (1 + infrastructure-provided-by-railroads x railroad-level-of-origin) x
4232 (possibly-some-modifier-for-crossing-water) x (define:LAND_SPEED_MODIFIER or define:NAVAL_SPEED_MODIFIER) x (leader-speed-trait
4233 + 1)
4234 */
4235 auto leader = state.world.army_get_general_from_army_leadership(a);
4236 auto bg = state.world.leader_get_background(leader);
4237 auto per = state.world.leader_get_personality(leader);
4238 auto leader_move = state.world.leader_trait_get_speed(bg) + state.world.leader_trait_get_speed(per);
4239 return min_speed * (state.world.army_get_is_retreating(a) ? 2.0f : 1.0f) *
4240 (1.0f + state.world.province_get_building_level(state.world.army_get_location_from_army_location(a), uint8_t(economy::province_building_type::railroad)) *
4241 state.economy_definitions.building_definitions[int32_t(economy::province_building_type::railroad)].infrastructure) *
4242 (leader_move + 1.0f);
4243}
4244float effective_navy_speed(sys::state& state, dcon::navy_id n) {
4245 auto owner = state.world.navy_get_controller_from_navy_control(n);
4246
4247 float min_speed = 10000.0f;
4248 for(auto reg : state.world.navy_get_navy_membership(n)) {
4249 auto reg_speed = state.world.nation_get_unit_stats(owner, reg.get_ship().get_type()).maximum_speed;
4250 min_speed = std::min(min_speed, reg_speed);
4251 }
4252
4253 auto leader = state.world.navy_get_admiral_from_navy_leadership(n);
4254 auto bg = state.world.leader_get_background(leader);
4255 auto per = state.world.leader_get_personality(leader);
4256 auto leader_move = state.world.leader_trait_get_speed(bg) + state.world.leader_trait_get_speed(per);
4257 return min_speed * (state.world.navy_get_is_retreating(n) ? 2.0f : 1.0f) * (leader_move + 1.0f);
4259
4260int32_t movement_time_from_to(sys::state& state, dcon::army_id a, dcon::province_id from, dcon::province_id to) {
4261 auto adj = state.world.get_province_adjacency_by_province_pair(from, to);
4262 float distance = province::distance(state, adj);
4263 float sum_mods = state.world.province_get_modifier_values(to, sys::provincial_mod_offsets::movement_cost) +
4264 state.world.province_get_modifier_values(to, sys::provincial_mod_offsets::movement_cost);
4265 float effective_distance = std::max(0.1f, distance * (sum_mods + 1.0f));
4266
4267 float effective_speed = effective_army_speed(state, a);
4268
4269 int32_t days = effective_speed > 0.0f ? int32_t(std::ceil(effective_distance / effective_speed)) : 50;
4270 assert(days > 0);
4271 return days;
4272}
4273int32_t movement_time_from_to(sys::state& state, dcon::navy_id n, dcon::province_id from, dcon::province_id to) {
4274 auto adj = state.world.get_province_adjacency_by_province_pair(from, to);
4275 float distance = province::distance(state, adj);
4276 float sum_mods = state.world.province_get_modifier_values(to, sys::provincial_mod_offsets::movement_cost) +
4277 state.world.province_get_modifier_values(from, sys::provincial_mod_offsets::movement_cost);
4278 float effective_distance = std::max(0.1f, distance * (sum_mods + 1.0f));
4279
4280 float effective_speed = effective_navy_speed(state, n);
4281
4282 int32_t days = effective_speed > 0.0f ? int32_t(std::ceil(effective_distance / effective_speed)) : 50;
4283 return days;
4285
4286sys::date arrival_time_to(sys::state& state, dcon::army_id a, dcon::province_id p) {
4287 auto current_location = state.world.army_get_location_from_army_location(a);
4288 auto adj = state.world.get_province_adjacency_by_province_pair(current_location, p);
4289 float distance = province::distance(state, adj);
4290 float sum_mods = state.world.province_get_modifier_values(p, sys::provincial_mod_offsets::movement_cost) +
4291 state.world.province_get_modifier_values(p, sys::provincial_mod_offsets::movement_cost);
4292 float effective_distance = std::max(0.1f, distance * (sum_mods + 1.0f));
4293
4294 float effective_speed = effective_army_speed(state, a);
4295
4296 int32_t days = effective_speed > 0.0f ? int32_t(std::ceil(effective_distance / effective_speed)) : 50;
4297 assert(days > 0);
4298 return state.current_date + days;
4299}
4300sys::date arrival_time_to(sys::state& state, dcon::navy_id n, dcon::province_id p) {
4301 auto current_location = state.world.navy_get_location_from_navy_location(n);
4302 auto adj = state.world.get_province_adjacency_by_province_pair(current_location, p);
4303 float distance = province::distance(state, adj);
4304 float sum_mods = state.world.province_get_modifier_values(p, sys::provincial_mod_offsets::movement_cost) +
4305 state.world.province_get_modifier_values(p, sys::provincial_mod_offsets::movement_cost);
4306 float effective_distance = std::max(0.1f, distance * (sum_mods + 1.0f));
4307
4308 float effective_speed = effective_navy_speed(state, n);
4309
4310 int32_t days = effective_speed > 0.0f ? int32_t(std::ceil(effective_distance / effective_speed)) : 50;
4311 return state.current_date + days;
4313
4314void add_army_to_battle(sys::state& state, dcon::army_id a, dcon::land_battle_id b, war_role r) {
4315 assert(state.world.army_is_valid(a));
4316 bool battle_attacker = (r == war_role::attacker) == state.world.land_battle_get_war_attacker_is_attacker(b);
4317 if(battle_attacker) {
4318 if(!state.world.land_battle_get_general_from_attacking_general(b)) {
4319 state.world.land_battle_set_general_from_attacking_general(b, state.world.army_get_general_from_army_leadership(a));
4320 }
4321
4322 auto reserves = state.world.land_battle_get_reserves(b);
4323 for(auto reg : state.world.army_get_army_membership(a)) {
4324 if(reg.get_regiment().get_strength() <= 0.0f)
4325 continue;
4326
4327 auto type = state.military_definitions.unit_base_definitions[reg.get_regiment().get_type()].type;
4328 switch(type) {
4329 case unit_type::infantry:
4330 reserves.push_back(
4331 reserve_regiment{reg.get_regiment().id, reserve_regiment::is_attacking | reserve_regiment::type_infantry});
4332 state.world.land_battle_get_attacker_infantry(b) += reg.get_regiment().get_strength();
4333 break;
4334 case unit_type::cavalry:
4335 reserves.push_back(
4336 reserve_regiment{reg.get_regiment().id, reserve_regiment::is_attacking | reserve_regiment::type_cavalry});
4337 state.world.land_battle_get_attacker_cav(b) += reg.get_regiment().get_strength();
4338 break;
4339 case unit_type::special:
4340 case unit_type::support:
4341 reserves.push_back(
4342 reserve_regiment{reg.get_regiment().id, reserve_regiment::is_attacking | reserve_regiment::type_support});
4343 state.world.land_battle_get_attacker_support(b) += reg.get_regiment().get_strength();
4344 break;
4345 default:
4346 assert(false);
4347 }
4348 }
4349 } else {
4350 auto& def_bonus = state.world.land_battle_get_defender_bonus(b);
4351 auto prev_dig_in = def_bonus | defender_bonus_dig_in_mask;
4352 auto new_dig_in = std::min(prev_dig_in, state.world.army_get_dig_in(a) & defender_bonus_dig_in_mask);
4353 def_bonus &= ~defender_bonus_dig_in_mask;
4354 def_bonus |= new_dig_in;
4355
4356 if(!state.world.land_battle_get_general_from_defending_general(b)) {
4357 state.world.land_battle_set_general_from_defending_general(b, state.world.army_get_general_from_army_leadership(a));
4358 }
4359 auto reserves = state.world.land_battle_get_reserves(b);
4360 for(auto reg : state.world.army_get_army_membership(a)) {
4361 if(reg.get_regiment().get_strength() <= 0.0f)
4362 continue;
4363
4364 auto type = state.military_definitions.unit_base_definitions[reg.get_regiment().get_type()].type;
4365 switch(type) {
4366 case unit_type::infantry:
4367 reserves.push_back(reserve_regiment{reg.get_regiment().id, reserve_regiment::type_infantry});
4368 state.world.land_battle_get_defender_infantry(b) += reg.get_regiment().get_strength();
4369 break;
4370 case unit_type::cavalry:
4371 reserves.push_back(reserve_regiment{reg.get_regiment().id, reserve_regiment::type_cavalry});
4372 state.world.land_battle_get_defender_cav(b) += reg.get_regiment().get_strength();
4373 break;
4374 case unit_type::special:
4375 case unit_type::support:
4376 reserves.push_back(reserve_regiment{reg.get_regiment().id, reserve_regiment::type_support});
4377 state.world.land_battle_get_defender_support(b) += reg.get_regiment().get_strength();
4378 break;
4379 default:
4380 assert(false);
4381 }
4382 }
4383 }
4384
4385 state.world.army_set_battle_from_army_battle_participation(a, b);
4386 state.world.army_set_arrival_time(a, sys::date{}); // pause movement
4388
4389void army_arrives_in_province(sys::state& state, dcon::army_id a, dcon::province_id p, crossing_type crossing, dcon::land_battle_id from) {
4390 assert(state.world.army_is_valid(a));
4391 assert(!state.world.army_get_battle_from_army_battle_participation(a));
4392
4393 state.world.army_set_location_from_army_location(a, p);
4394 auto regs = state.world.army_get_army_membership(a);
4395 if(!state.world.army_get_black_flag(a) && !state.world.army_get_is_retreating(a) && regs.begin() != regs.end()) {
4396 auto owner_nation = state.world.army_get_controller_from_army_control(a);
4397
4398 // look for existing battle
4399 for(auto b : state.world.province_get_land_battle_location(p)) {
4400 if(b.get_battle() != from) {
4401 auto battle_war = b.get_battle().get_war_from_land_battle_in_war();
4402 if(battle_war) {
4403 auto owner_role = get_role(state, battle_war, owner_nation);
4404 if(owner_role != war_role::none) {
4405 add_army_to_battle(state, a, b.get_battle(), owner_role);
4406 return; // done -- only one battle per
4407 }
4408 } else { // rebels
4409 add_army_to_battle(state, a, b.get_battle(), bool(owner_nation) ? war_role::defender : war_role::attacker);
4410 return;
4411 }
4412 }
4413 }
4414
4415 // start battle
4416 dcon::land_battle_id gather_to_battle;
4417 dcon::war_id battle_in_war;
4418
4419 for(auto o : state.world.province_get_army_location(p)) {
4420 if(o.get_army() == a)
4421 continue;
4422 if(o.get_army().get_is_retreating() || o.get_army().get_black_flag() || o.get_army().get_navy_from_army_transport() || o.get_army().get_battle_from_army_battle_participation())
4423 continue;
4424
4425 auto other_nation = o.get_army().get_controller_from_army_control();
4426
4427 if(bool(owner_nation) != bool(other_nation)) { // battle vs. rebels
4428 auto new_battle = fatten(state.world, state.world.create_land_battle());
4429 new_battle.set_war_attacker_is_attacker(!bool(owner_nation));
4430 new_battle.set_start_date(state.current_date);
4431 new_battle.set_location_from_land_battle_location(p);
4432 new_battle.set_dice_rolls(make_dice_rolls(state, uint32_t(new_battle.id.value)));
4433
4434 uint8_t flags = defender_bonus_dig_in_mask;
4435 if(crossing == crossing_type::river)
4436 flags |= defender_bonus_crossing_river;
4437 if(crossing == crossing_type::sea)
4438 flags |= defender_bonus_crossing_sea;
4439 new_battle.set_defender_bonus(flags);
4440
4441 auto cw_a = state.defines.base_combat_width +
4442 state.world.nation_get_modifier_values(owner_nation, sys::national_mod_offsets::combat_width);
4443 auto cw_b = state.defines.base_combat_width +
4444 state.world.nation_get_modifier_values(other_nation, sys::national_mod_offsets::combat_width);
4445 new_battle.set_combat_width(uint8_t(
4446 std::clamp(int32_t(std::min(cw_a, cw_b) *
4447 (state.world.province_get_modifier_values(p, sys::provincial_mod_offsets::combat_width) + 1.0f)),
4448 2, 30)));
4449
4450 add_army_to_battle(state, a, new_battle, !bool(owner_nation) ? war_role::attacker : war_role::defender);
4451 add_army_to_battle(state, o.get_army(), new_battle, bool(owner_nation) ? war_role::attacker : war_role::defender);
4452
4453 gather_to_battle = new_battle.id;
4454 break;
4455 } else if(auto par = internal_find_war_between(state, owner_nation, other_nation); par.role != war_role::none) {
4456 auto new_battle = fatten(state.world, state.world.create_land_battle());
4457 new_battle.set_war_attacker_is_attacker(par.role == war_role::attacker);
4458 new_battle.set_start_date(state.current_date);
4459 new_battle.set_war_from_land_battle_in_war(par.w);
4460 new_battle.set_location_from_land_battle_location(p);
4461 new_battle.set_dice_rolls(make_dice_rolls(state, uint32_t(new_battle.id.value)));
4462
4463 uint8_t flags = defender_bonus_dig_in_mask;
4464 if(crossing == crossing_type::river)
4465 flags |= defender_bonus_crossing_river;
4466 if(crossing == crossing_type::sea)
4467 flags |= defender_bonus_crossing_sea;
4468 new_battle.set_defender_bonus(flags);
4469
4470 auto cw_a = state.defines.base_combat_width +
4471 state.world.nation_get_modifier_values(owner_nation, sys::national_mod_offsets::combat_width);
4472 auto cw_b = state.defines.base_combat_width +
4473 state.world.nation_get_modifier_values(other_nation, sys::national_mod_offsets::combat_width);
4474 new_battle.set_combat_width(uint8_t(
4475 std::clamp(int32_t(std::min(cw_a, cw_b) *
4476 (state.world.province_get_modifier_values(p, sys::provincial_mod_offsets::combat_width) + 1.0f)),
4477 2, 30)));
4478
4479 add_army_to_battle(state, a, new_battle, par.role);
4480 add_army_to_battle(state, o.get_army(), new_battle, par.role == war_role::attacker ? war_role::defender : war_role::attacker);
4481
4482 gather_to_battle = new_battle.id;
4483 battle_in_war = par.w;
4484 break;
4485 }
4486
4487 }
4488
4489 if(gather_to_battle) {
4490 for(auto o : state.world.province_get_army_location(p)) {
4491 if(o.get_army() == a)
4492 continue;
4493 if(o.get_army().get_is_retreating() || o.get_army().get_black_flag() || o.get_army().get_navy_from_army_transport() || o.get_army().get_battle_from_army_battle_participation())
4494 continue;
4495
4496 auto other_nation = o.get_army().get_controller_from_army_control();
4497 if(battle_in_war) {
4498 if(auto role = get_role(state, battle_in_war, other_nation); role != war_role::none) {
4499 add_army_to_battle(state, o.get_army(), gather_to_battle, role);
4500 }
4501 } else { // battle vs. rebels
4502 add_army_to_battle(state, o.get_army(), gather_to_battle, !bool(other_nation) ? war_role::attacker : war_role::defender);
4503 }
4504 }
4505
4506 if(battle_in_war) { // gather as part of war
4507 for(auto par : state.world.war_get_war_participant(battle_in_war)) {
4508 if(par.get_nation().get_is_player_controlled() == false)
4509 ai::gather_to_battle(state, par.get_nation(), p);
4510 }
4511 } else if(state.world.nation_get_is_player_controlled(owner_nation) == false) { // gather vs. rebels
4512 ai::gather_to_battle(state, owner_nation, p);
4513 }
4514 }
4515 }
4517
4518void add_navy_to_battle(sys::state& state, dcon::navy_id n, dcon::naval_battle_id b, war_role r) {
4519 assert(state.world.navy_is_valid(n));
4520 bool battle_attacker = (r == war_role::attacker) == state.world.naval_battle_get_war_attacker_is_attacker(b);
4521 if(battle_attacker) {
4522 // try add admiral as leader
4523 if(!state.world.naval_battle_get_admiral_from_attacking_admiral(b)) {
4524 state.world.naval_battle_set_admiral_from_attacking_admiral(b, state.world.navy_get_admiral_from_navy_leadership(n));
4525 }
4526 // put ships in slots
4527 auto slots = state.world.naval_battle_get_slots(b);
4528 for(auto ship : state.world.navy_get_navy_membership(n)) {
4529 if(ship.get_ship().get_strength() <= 0.0f)
4530 continue;
4531
4532 auto type = state.military_definitions.unit_base_definitions[ship.get_ship().get_type()].type;
4533 switch(type) {
4534 case unit_type::big_ship:
4535 slots.push_back(ship_in_battle{ship.get_ship().id, 0,
4536 1000 | ship_in_battle::mode_seeking | ship_in_battle::is_attacking | ship_in_battle::type_big});
4537 state.world.naval_battle_get_attacker_big_ships(b)++;
4538 break;
4539 case unit_type::light_ship:
4540 slots.push_back(ship_in_battle{ship.get_ship().id, 0,
4541 1000 | ship_in_battle::mode_seeking | ship_in_battle::is_attacking | ship_in_battle::type_small});
4542 state.world.naval_battle_get_attacker_small_ships(b)++;
4543 break;
4544 case unit_type::transport:
4545 slots.push_back(ship_in_battle{ship.get_ship().id, 0,
4546 1000 | ship_in_battle::mode_seeking | ship_in_battle::is_attacking | ship_in_battle::type_transport});
4547 state.world.naval_battle_get_attacker_transport_ships(b)++;
4548 break;
4549 default:
4550 assert(false);
4551 }
4552 }
4553 } else {
4554 if(!state.world.naval_battle_get_admiral_from_defending_admiral(b)) {
4555 state.world.naval_battle_set_admiral_from_defending_admiral(b, state.world.navy_get_admiral_from_navy_leadership(n));
4556 }
4557 auto slots = state.world.naval_battle_get_slots(b);
4558 for(auto ship : state.world.navy_get_navy_membership(n)) {
4559 if(ship.get_ship().get_strength() <= 0.0f)
4560 continue;
4561
4562 auto type = state.military_definitions.unit_base_definitions[ship.get_ship().get_type()].type;
4563 switch(type) {
4564 case unit_type::big_ship:
4565 slots.push_back(ship_in_battle{ship.get_ship().id, 0, 1000 | ship_in_battle::mode_seeking | ship_in_battle::type_big});
4566 state.world.naval_battle_get_defender_big_ships(b)++;
4567 break;
4568 case unit_type::light_ship:
4569 slots.push_back(ship_in_battle{ship.get_ship().id, 0, 1000 | ship_in_battle::mode_seeking | ship_in_battle::type_small});
4570 state.world.naval_battle_get_defender_small_ships(b)++;
4571 break;
4572 case unit_type::transport:
4573 slots.push_back(
4574 ship_in_battle{ship.get_ship().id, 0, 1000 | ship_in_battle::mode_seeking | ship_in_battle::type_transport});
4575 state.world.naval_battle_get_defender_transport_ships(b)++;
4576 break;
4577 default:
4578 assert(false);
4579 }
4580 }
4581 }
4582
4583 state.world.navy_set_battle_from_navy_battle_participation(n, b);
4584 state.world.navy_set_arrival_time(n, sys::date{}); // pause movement
4585
4586 for(auto em : state.world.navy_get_army_transport(n)) {
4587 em.get_army().set_arrival_time(sys::date{});
4588 }
4590
4591bool retreat(sys::state& state, dcon::navy_id n) {
4592 auto province_start = state.world.navy_get_location_from_navy_location(n);
4593 auto nation_controller = state.world.navy_get_controller_from_navy_control(n);
4594
4595 if(!nation_controller)
4596 return false;
4597
4598 auto retreat_path = province::make_naval_retreat_path(state, nation_controller, province_start);
4599 if(retreat_path.size() > 0) {
4600 state.world.navy_set_is_retreating(n, true);
4601 auto existing_path = state.world.navy_get_path(n);
4602 existing_path.load_range(retreat_path.data(), retreat_path.data() + retreat_path.size());
4603
4604 state.world.navy_set_arrival_time(n, arrival_time_to(state, n, retreat_path.back()));
4605
4606 for(auto em : state.world.navy_get_army_transport(n)) {
4607 em.get_army().get_path().clear();
4608 }
4609 return true;
4610 } else {
4611 return false;
4612 }
4614
4615bool retreat(sys::state& state, dcon::army_id n) {
4616 auto province_start = state.world.army_get_location_from_army_location(n);
4617 auto nation_controller = state.world.army_get_controller_from_army_control(n);
4618
4619 if(!nation_controller)
4620 return false; // rebels don't retreat
4621
4622 auto retreat_path = province::make_land_retreat_path(state, nation_controller, province_start);
4623 if(retreat_path.size() > 0) {
4624 state.world.army_set_is_retreating(n, true);
4625 auto existing_path = state.world.army_get_path(n);
4626 existing_path.load_range(retreat_path.data(), retreat_path.data() + retreat_path.size());
4627
4628 state.world.army_set_arrival_time(n, arrival_time_to(state, n, retreat_path.back()));
4629 state.world.army_set_dig_in(n, 0);
4630 return true;
4631 } else {
4632 return false;
4633 }
4635
4636dcon::nation_id get_naval_battle_lead_attacker(sys::state& state, dcon::naval_battle_id b) {
4637 auto by_admiral =
4638 state.world.leader_get_nation_from_leader_loyalty(state.world.naval_battle_get_admiral_from_attacking_admiral(b));
4639 if(by_admiral)
4640 return by_admiral;
4641
4642 auto war = state.world.naval_battle_get_war_from_naval_battle_in_war(b);
4643 bool war_attackers = state.world.naval_battle_get_war_attacker_is_attacker(b);
4644
4645 for(auto nbp : state.world.naval_battle_get_navy_battle_participation(b)) {
4646 if(war_attackers && is_attacker(state, war, nbp.get_navy().get_controller_from_navy_control())) {
4647 return nbp.get_navy().get_controller_from_navy_control();
4648 } else if(!war_attackers && !is_attacker(state, war, nbp.get_navy().get_controller_from_navy_control())) {
4649 return nbp.get_navy().get_controller_from_navy_control();
4650 }
4651 }
4652
4653 return dcon::nation_id{};
4655
4656dcon::nation_id get_naval_battle_lead_defender(sys::state& state, dcon::naval_battle_id b) {
4657 auto by_admiral =
4658 state.world.leader_get_nation_from_leader_loyalty(state.world.naval_battle_get_admiral_from_defending_admiral(b));
4659 if(by_admiral)
4660 return by_admiral;
4661
4662 auto war = state.world.naval_battle_get_war_from_naval_battle_in_war(b);
4663 bool war_attackers = state.world.naval_battle_get_war_attacker_is_attacker(b);
4664
4665 for(auto nbp : state.world.naval_battle_get_navy_battle_participation(b)) {
4666 if(!war_attackers && is_attacker(state, war, nbp.get_navy().get_controller_from_navy_control())) {
4667 return nbp.get_navy().get_controller_from_navy_control();
4668 } else if(war_attackers && !is_attacker(state, war, nbp.get_navy().get_controller_from_navy_control())) {
4669 return nbp.get_navy().get_controller_from_navy_control();
4670 }
4671 }
4672
4673 return dcon::nation_id{};
4675
4676dcon::nation_id get_land_battle_lead_attacker(sys::state& state, dcon::land_battle_id b) {
4677 auto by_general =
4678 state.world.leader_get_nation_from_leader_loyalty(state.world.land_battle_get_general_from_attacking_general(b));
4679 if(by_general)
4680 return by_general;
4681
4682 auto war = state.world.land_battle_get_war_from_land_battle_in_war(b);
4683 bool war_attackers = state.world.land_battle_get_war_attacker_is_attacker(b);
4684
4685 if(!war) {
4686 if(war_attackers)
4687 return dcon::nation_id{};
4688
4689 for(auto nbp : state.world.land_battle_get_army_battle_participation(b)) {
4690 auto c = nbp.get_army().get_controller_from_army_control();
4691 if(c)
4692 return c;
4693 }
4694 }
4695
4696 for(auto nbp : state.world.land_battle_get_army_battle_participation(b)) {
4697 if(war_attackers && is_attacker(state, war, nbp.get_army().get_controller_from_army_control())) {
4698 return nbp.get_army().get_controller_from_army_control();
4699 } else if(!war_attackers && !is_attacker(state, war, nbp.get_army().get_controller_from_army_control())) {
4700 return nbp.get_army().get_controller_from_army_control();
4701 }
4702 }
4703
4704 return dcon::nation_id{};
4706
4707dcon::nation_id get_land_battle_lead_defender(sys::state& state, dcon::land_battle_id b) {
4708 auto by_general =
4709 state.world.leader_get_nation_from_leader_loyalty(state.world.land_battle_get_general_from_defending_general(b));
4710 if(by_general)
4711 return by_general;
4712
4713 auto war = state.world.land_battle_get_war_from_land_battle_in_war(b);
4714 bool war_attackers = state.world.land_battle_get_war_attacker_is_attacker(b);
4715
4716 if(!war) {
4717 if(!war_attackers)
4718 return dcon::nation_id{};
4719
4720 for(auto nbp : state.world.land_battle_get_army_battle_participation(b)) {
4721 auto c = nbp.get_army().get_controller_from_army_control();
4722 if(c)
4723 return c;
4724 }
4725 }
4726
4727 for(auto nbp : state.world.land_battle_get_army_battle_participation(b)) {
4728 if(!war_attackers && is_attacker(state, war, nbp.get_army().get_controller_from_army_control())) {
4729 return nbp.get_army().get_controller_from_army_control();
4730 } else if(war_attackers && !is_attacker(state, war, nbp.get_army().get_controller_from_army_control())) {
4731 return nbp.get_army().get_controller_from_army_control();
4732 }
4733 }
4734
4735 return dcon::nation_id{};
4737
4738float get_leader_select_score(sys::state& state, dcon::leader_id l) {
4739 /*
4740 - Each side has a leader that is in charge of the combat, which is the leader with the greatest
4741 value as determined by the following formula: (organization x 5 + attack + defend + morale +
4742 speed + attrition + experience / 2 + reconnaissance / 5 + reliability / 5) x (prestige + 1)
4743 */
4744 auto per = state.world.leader_get_personality(l);
4745 auto bak = state.world.leader_get_background(l);
4746 //
4747 auto org = state.world.leader_trait_get_organisation(per) + state.world.leader_trait_get_organisation(bak);
4748 auto atk = state.world.leader_trait_get_attack(per) + state.world.leader_trait_get_attack(bak);
4749 auto def = state.world.leader_trait_get_defense(per) + state.world.leader_trait_get_defense(bak);
4750 auto spd = state.world.leader_trait_get_speed(per) + state.world.leader_trait_get_speed(bak);
4751 auto mor = state.world.leader_trait_get_morale(per) + state.world.leader_trait_get_morale(bak);
4752 auto att = state.world.leader_trait_get_experience(per) + state.world.leader_trait_get_experience(bak);
4753 auto rel = state.world.leader_trait_get_reliability(per) + state.world.leader_trait_get_reliability(bak);
4754 auto exp = state.world.leader_trait_get_experience(per) + state.world.leader_trait_get_experience(bak);
4755 auto rec = state.world.leader_trait_get_reconnaissance(per) + state.world.leader_trait_get_reconnaissance(bak);
4756 auto lp = state.world.leader_get_prestige(l);
4757 return (org * 5.f + atk + def + mor + spd + att + exp / 2.f + rec / 5.f + rel / 5.f) * (lp + 1.f);
4758}
4759void update_battle_leaders(sys::state& state, dcon::land_battle_id b) {
4760 auto la = get_land_battle_lead_attacker(state, b);
4761 dcon::leader_id a_lid;
4762 float a_score = 0.f;
4763 auto ld = get_land_battle_lead_defender(state, b);
4764 dcon::leader_id d_lid;
4765 float d_score = 0.f;
4766 for(const auto a : state.world.land_battle_get_army_battle_participation(b)) {
4767 auto l = a.get_army().get_general_from_army_leadership();
4768 auto score = get_leader_select_score(state, l);
4769 if(a.get_army().get_controller_from_army_control() == la) {
4770 if(score > a_score) {
4771 a_lid = l;
4772 a_score = score;
4773 }
4774 } else if(a.get_army().get_controller_from_army_control() == ld) {
4775 if(score > d_score) {
4776 d_lid = l;
4777 d_score = score;
4778 }
4779 }
4780 }
4781 auto aa = state.world.land_battle_get_attacking_general(b);
4782 state.world.attacking_general_set_general(aa, a_lid);
4783 auto ab = state.world.land_battle_get_defending_general(b);
4784 state.world.defending_general_set_general(ab, d_lid);
4785}
4786void update_battle_leaders(sys::state& state, dcon::naval_battle_id b) {
4787 auto la = get_naval_battle_lead_attacker(state, b);
4788 dcon::leader_id a_lid;
4789 float a_score = 0.f;
4790 auto ld = get_naval_battle_lead_defender(state, b);
4791 dcon::leader_id d_lid;
4792 float d_score = 0.f;
4793 for(const auto a : state.world.naval_battle_get_navy_battle_participation(b)) {
4794 auto l = a.get_navy().get_admiral_from_navy_leadership();
4795 auto score = get_leader_select_score(state, l);
4796 if(a.get_navy().get_controller_from_navy_control() == la) {
4797 if(score > a_score) {
4798 a_lid = l;
4799 a_score = score;
4800 }
4801 } else if(a.get_navy().get_controller_from_navy_control() == ld) {
4802 if(score > d_score) {
4803 d_lid = l;
4804 d_score = score;
4805 }
4806 }
4807 }
4808 auto aa = state.world.naval_battle_get_attacking_admiral(b);
4809 state.world.attacking_admiral_set_admiral(aa, a_lid);
4810 auto ab = state.world.naval_battle_get_defending_admiral(b);
4811 state.world.defending_admiral_set_admiral(ab, d_lid);
4813
4814void cleanup_army(sys::state& state, dcon::army_id n) {
4815 assert(!state.world.army_get_battle_from_army_battle_participation(n));
4816
4817 auto regs = state.world.army_get_army_membership(n);
4818 while(regs.begin() != regs.end()) {
4819 state.world.delete_regiment((*regs.begin()).get_regiment().id);
4820 }
4821
4822 auto b = state.world.army_get_battle_from_army_battle_participation(n);
4823 if(b) {
4824 state.world.army_set_is_retreating(n, true); // prevents army from re-entering battles
4825
4826 bool should_end = true;
4827 auto controller = state.world.army_get_controller_from_army_control(n);
4828 if(bool(controller)) {
4829 // TODO: Do they have to be in common war or can they just be "hostile against"?
4830 bool has_other = false;
4831 bool has_rebels = false;
4832 for(auto bp : state.world.land_battle_get_army_battle_participation_as_battle(b)) {
4833 if(bp.get_army() != n) {
4834 has_other = true;
4835 if(are_allied_in_war(state, controller, bp.get_army().get_controller_from_army_control())) {
4836 should_end = false;
4837 } else if(bp.get_army().get_controller_from_army_rebel_control()) {
4838 has_rebels = true;
4839 }
4840 }
4841 }
4842 // continue fighting rebels
4843 if(has_other && has_rebels)
4844 should_end = false;
4845 } else if(state.world.army_get_controller_from_army_rebel_control(n)) {
4846 for(auto bp : state.world.land_battle_get_army_battle_participation_as_battle(b)) {
4847 if(bp.get_army() != n && bp.get_army().get_army_rebel_control()) {
4848 should_end = false;
4849 }
4850 }
4851 } else {
4852
4853 }
4854
4855 if(should_end) {
4856 bool as_attacker = state.world.land_battle_get_war_attacker_is_attacker(b);
4857 end_battle(state, b, as_attacker ? battle_result::defender_won : battle_result::attacker_won);
4858 }
4859 }
4860
4861 state.world.delete_army(n);
4863
4864void cleanup_navy(sys::state& state, dcon::navy_id n) {
4865 assert(!state.world.navy_get_battle_from_navy_battle_participation(n));
4866
4867 auto shps = state.world.navy_get_navy_membership(n);
4868 while(shps.begin() != shps.end()) {
4869 state.world.delete_ship((*shps.begin()).get_ship());
4870 }
4871 auto em = state.world.navy_get_army_transport(n);
4872 while(em.begin() != em.end()) {
4873 cleanup_army(state, (*em.begin()).get_army());
4874 }
4875
4876 auto controller = state.world.navy_get_controller_from_navy_control(n);
4877 auto b = state.world.navy_get_battle_from_navy_battle_participation(n);
4878
4879 state.world.navy_set_is_retreating(n, true); // prevents navy from re-entering battles
4880 if(b && controller) {
4881 bool should_end = true;
4882 // TODO: Do they have to be in common war or can they just be "hostile against"?
4883 for(auto bp : state.world.naval_battle_get_navy_battle_participation_as_battle(b)) {
4884 if(bp.get_navy() != n && are_allied_in_war(state, controller, state.world.navy_get_controller_from_navy_control(bp.get_navy()))) {
4885 should_end = false;
4886 }
4887 }
4888 if(should_end) {
4889 bool as_attacker = state.world.naval_battle_get_war_attacker_is_attacker(b);
4890 end_battle(state, b, as_attacker ? battle_result::defender_won : battle_result::attacker_won);
4891 }
4892 }
4893
4894 state.world.delete_navy(n);
4896
4897void adjust_leader_prestige(sys::state& state, dcon::leader_id l, float value) {
4898 auto v = state.world.leader_get_prestige(l);
4899 v = std::clamp(v + value, 0.f, 1.f); //from 0% to 100%
4900 state.world.leader_set_prestige(l, v);
4901}
4902void adjust_regiment_experience(sys::state& state, dcon::nation_id n, dcon::regiment_id l, float value) {
4903 auto v = state.world.regiment_get_experience(l);
4904
4905 auto min_exp = std::clamp(state.world.nation_get_modifier_values(n, sys::national_mod_offsets::regular_experience_level) / 100.f, 0.f, 1.f);
4906
4907 v = std::clamp(v + value, min_exp, 1.f); //from regular_experience_level to 100%
4908
4909 state.world.regiment_set_experience(l, v);
4910}
4911void adjust_ship_experience(sys::state& state, dcon::nation_id n, dcon::ship_id r, float value) {
4912 auto v = state.world.ship_get_experience(r);
4913
4914 auto min_exp = std::clamp(state.world.nation_get_modifier_values(n, sys::national_mod_offsets::regular_experience_level) / 100.f, 0.f, 1.f);
4915
4916 v = std::clamp(v + value * state.defines.exp_gain_div, min_exp, 1.f);
4917 state.world.ship_set_experience(r, v); //from regular_experience_level to 100%
4919
4920void end_battle(sys::state& state, dcon::land_battle_id b, battle_result result) {
4921 auto war = state.world.land_battle_get_war_from_land_battle_in_war(b);
4922 auto location = state.world.land_battle_get_location_from_land_battle_location(b);
4923
4924 assert(location);
4925
4926 auto make_leaderless = [&](dcon::army_id a) {
4927 state.world.army_set_controller_from_army_control(a, dcon::nation_id{});
4928 state.world.army_set_controller_from_army_rebel_control(a, dcon::rebel_faction_id{});
4929 state.world.army_set_is_retreating(a, true);
4930 };
4931
4932 auto a_nation = get_land_battle_lead_attacker(state, b);
4933 auto d_nation = get_land_battle_lead_defender(state, b);
4934
4935 for(auto n : state.world.land_battle_get_army_battle_participation(b)) {
4936 auto nation_owner = state.world.army_get_controller_from_army_control(n.get_army());
4937
4938 auto role_in_war = bool(war)
4939 ? get_role(state, war, n.get_army().get_controller_from_army_control())
4940 : (bool(nation_owner) ? war_role::defender : war_role::attacker);
4941
4942 bool battle_attacker = (role_in_war == war_role::attacker) == state.world.land_battle_get_war_attacker_is_attacker(b);
4943
4944
4945 if(battle_attacker && result == battle_result::defender_won) {
4946 if(!can_retreat_from_battle(state, b)) {
4947 make_leaderless(n.get_army());
4948 } else {
4949 if(!retreat(state, n.get_army()))
4950 make_leaderless(n.get_army());
4951 }
4952 } else if(!battle_attacker && result == battle_result::attacker_won) {
4953 if(!can_retreat_from_battle(state, b)) {
4954 make_leaderless(n.get_army());
4955 } else {
4956 if(!retreat(state, n.get_army()))
4957 make_leaderless(n.get_army());
4958 }
4959 } else {
4960 auto path = n.get_army().get_path();
4961 if(path.size() > 0) {
4962 state.world.army_set_arrival_time(n.get_army(), arrival_time_to(state, n.get_army(), path.at(path.size() - 1)));
4963 }
4964 }
4965 }
4966
4967 /*
4968 On finishing a battle:
4969 Each winning combatant get a random `on_battle_won` event, and each losing combatant gets a random `on_battle_lost` event
4970
4971 War score is gained based on the difference in losses (in absolute terms) divided by 5 plus 0.1 to a minimum of 0.1
4972 */
4973
4974 if(result != battle_result::indecisive) {
4975 if(war)
4976 state.world.war_get_number_of_battles(war)++;
4977
4978 auto a_leader = state.world.land_battle_get_general_from_attacking_general(b);
4979 auto b_leader = state.world.land_battle_get_general_from_defending_general(b);
4980
4981 if(result == battle_result::attacker_won) {
4982 auto total_def_loss = state.world.land_battle_get_defender_cav_lost(b) + state.world.land_battle_get_defender_infantry_lost(b) + state.world.land_battle_get_defender_support_lost(b);
4983 auto total_att_loss = state.world.land_battle_get_attacker_cav_lost(b) + state.world.land_battle_get_attacker_infantry_lost(b) + state.world.land_battle_get_attacker_support_lost(b);
4984 auto score = std::max(0.0f, 3.0f * (total_def_loss - total_att_loss) / 10.0f);
4985
4986 if(war) {
4987 if(state.world.land_battle_get_war_attacker_is_attacker(b)) {
4988 state.world.war_get_attacker_battle_score(war) += score;
4989 } else {
4990 state.world.war_get_defender_battle_score(war) += score;
4991 }
4992 }
4993
4994
4995 if(a_nation && d_nation) { // no prestige for beating up rebels
4996 nations::adjust_prestige(state, a_nation, score / 50.0f);
4997 nations::adjust_prestige(state, d_nation, score / -50.0f);
4998
4999 adjust_leader_prestige(state, a_leader, score / 50.f / 100.f);
5000 adjust_leader_prestige(state, b_leader, -score / 50.f / 100.f);
5001 }
5002
5003 // Report
5004 if(state.local_player_nation == a_nation || state.local_player_nation == d_nation) {
5005 land_battle_report rep;
5006 rep.attacker_infantry_losses = state.world.land_battle_get_attacker_infantry_lost(b);
5007 rep.attacker_infantry = state.world.land_battle_get_attacker_infantry(b);
5008 rep.attacker_cavalry_losses = state.world.land_battle_get_attacker_cav_lost(b);
5009 rep.attacker_cavalry = state.world.land_battle_get_attacker_cav(b);
5010 rep.attacker_support_losses = state.world.land_battle_get_attacker_support_lost(b);
5011 rep.attacker_support = state.world.land_battle_get_attacker_support(b);
5012
5013 rep.defender_infantry_losses = state.world.land_battle_get_defender_infantry_lost(b);
5014 rep.defender_infantry = state.world.land_battle_get_defender_infantry(b);
5015 rep.defender_cavalry_losses = state.world.land_battle_get_defender_cav_lost(b);
5016 rep.defender_cavalry = state.world.land_battle_get_defender_cav(b);
5017 rep.defender_support_losses = state.world.land_battle_get_defender_support_lost(b);
5018 rep.defender_support = state.world.land_battle_get_defender_support(b);
5019
5020 rep.attacker_won = (result == battle_result::attacker_won);
5021
5022 rep.attacking_nation = get_land_battle_lead_attacker(state, b);
5023 rep.defending_nation = get_land_battle_lead_defender(state, b);
5024 rep.attacking_general = state.world.land_battle_get_general_from_attacking_general(b);
5025 rep.defending_general = state.world.land_battle_get_general_from_defending_general(b);
5026
5027 rep.location = state.world.land_battle_get_location_from_land_battle_location(b);
5028 rep.player_on_winning_side = bool(war)
5029 ? is_attacker(state, war, state.local_player_nation) == state.world.land_battle_get_war_attacker_is_attacker(b)
5030 : !state.world.land_battle_get_war_attacker_is_attacker(b);
5031
5032 if(war) {
5033 if(rep.player_on_winning_side) {
5034 rep.warscore_effect = score;
5035 rep.prestige_effect = score / 50.0f;
5036 } else {
5037 rep.warscore_effect = -score;
5038 rep.prestige_effect = -score / 50.0f;
5039 }
5040 }
5041
5042 auto discard = state.land_battle_reports.try_push(rep);
5043 }
5044
5045 } else if(result == battle_result::defender_won) {
5046 auto total_def_loss = state.world.land_battle_get_defender_cav_lost(b) + state.world.land_battle_get_defender_infantry_lost(b) + state.world.land_battle_get_defender_support_lost(b);
5047 auto total_att_loss = state.world.land_battle_get_attacker_cav_lost(b) + state.world.land_battle_get_attacker_infantry_lost(b) + state.world.land_battle_get_attacker_support_lost(b);
5048 auto score = std::max(0.0f, 3.0f * (total_att_loss - total_def_loss) / 10.0f);
5049
5050 if(war) {
5051 if(state.world.land_battle_get_war_attacker_is_attacker(b)) {
5052 state.world.war_get_defender_battle_score(war) += score;
5053 } else {
5054 state.world.war_get_attacker_battle_score(war) += score;
5055 }
5056 }
5057
5058 if(a_nation && d_nation) {
5059 nations::adjust_prestige(state, a_nation, score / -50.0f);
5060 nations::adjust_prestige(state, d_nation, score / 50.0f);
5061
5062 adjust_leader_prestige(state, a_leader, -score / 50.f / 100.f);
5063 adjust_leader_prestige(state, b_leader, score / 50.f / 100.f);
5064 }
5065
5066 // Report
5067 if(state.local_player_nation == a_nation || state.local_player_nation == d_nation) {
5068 land_battle_report rep;
5069 rep.attacker_infantry_losses = state.world.land_battle_get_attacker_infantry_lost(b);
5070 rep.attacker_infantry = state.world.land_battle_get_attacker_infantry(b);
5071 rep.attacker_cavalry_losses = state.world.land_battle_get_attacker_cav_lost(b);
5072 rep.attacker_cavalry = state.world.land_battle_get_attacker_cav(b);
5073 rep.attacker_support_losses = state.world.land_battle_get_attacker_support_lost(b);
5074 rep.attacker_support = state.world.land_battle_get_attacker_support(b);
5075
5076 rep.defender_infantry_losses = state.world.land_battle_get_defender_infantry_lost(b);
5077 rep.defender_infantry = state.world.land_battle_get_defender_infantry(b);
5078 rep.defender_cavalry_losses = state.world.land_battle_get_defender_cav_lost(b);
5079 rep.defender_cavalry = state.world.land_battle_get_defender_cav(b);
5080 rep.defender_support_losses = state.world.land_battle_get_defender_support_lost(b);
5081 rep.defender_support = state.world.land_battle_get_defender_support(b);
5082
5083 rep.attacker_won = (result == battle_result::attacker_won);
5084
5085 rep.attacking_nation = get_land_battle_lead_attacker(state, b);
5086 rep.defending_nation = get_land_battle_lead_defender(state, b);
5087 rep.attacking_general = state.world.land_battle_get_general_from_attacking_general(b);
5088 rep.defending_general = state.world.land_battle_get_general_from_defending_general(b);
5089
5090 rep.location = state.world.land_battle_get_location_from_land_battle_location(b);
5091 rep.player_on_winning_side = bool(war)
5092 ? is_attacker(state, war, state.local_player_nation) != state.world.land_battle_get_war_attacker_is_attacker(b)
5093 : state.world.land_battle_get_war_attacker_is_attacker(b);
5094
5095 if(war) {
5096 if(rep.player_on_winning_side) {
5097 rep.warscore_effect = score;
5098 rep.prestige_effect = score / 50.0f;
5099 } else {
5100 rep.warscore_effect = -score;
5101 rep.prestige_effect = -score / 50.0f;
5102 }
5103 }
5104 auto discard = state.land_battle_reports.try_push(rep);
5105 }
5106 }
5107 }
5108
5109
5110 if(result != battle_result::indecisive) { // so we don't restart battles as the war is ending
5111 auto par_range = state.world.land_battle_get_army_battle_participation(b);
5112 while(par_range.begin() != par_range.end()) {
5113 auto n = (*par_range.begin()).get_army();
5114 n.set_battle_from_army_battle_participation(dcon::land_battle_id{});
5115 army_arrives_in_province(state, n, location, crossing_type::none, b);
5116 }
5117 }
5118
5119 state.world.delete_land_battle(b);
5121
5122void end_battle(sys::state& state, dcon::naval_battle_id b, battle_result result) {
5123 auto war = state.world.naval_battle_get_war_from_naval_battle_in_war(b);
5124 auto location = state.world.naval_battle_get_location_from_naval_battle_location(b);
5125
5126 assert(war);
5127 assert(location);
5128
5129 auto a_nation = get_naval_battle_lead_attacker(state, b);
5130 auto d_nation = get_naval_battle_lead_defender(state, b);
5131
5132 for(auto n : state.world.naval_battle_get_navy_battle_participation(b)) {
5133 auto role_in_war = get_role(state, war, n.get_navy().get_controller_from_navy_control());
5134 bool battle_attacker = (role_in_war == war_role::attacker) == state.world.naval_battle_get_war_attacker_is_attacker(b);
5135
5136
5137 auto transport_cap = military::free_transport_capacity(state, n.get_navy());
5138 if(transport_cap < 0) {
5139 for(auto em : n.get_navy().get_army_transport()) {
5140 auto em_regs = em.get_army().get_army_membership();
5141 while(em_regs.begin() != em_regs.end() && transport_cap < 0) {
5142 auto reg_id = (*em_regs.begin()).get_regiment();
5143 disband_regiment_w_pop_death(state, reg_id);
5144 ++transport_cap;
5145 }
5146 if(transport_cap >= 0)
5147 break;
5148 }
5149 }
5150
5151 if(battle_attacker && result == battle_result::defender_won) {
5152 if(!can_retreat_from_battle(state, b)) {
5153 n.get_navy().set_controller_from_navy_control(dcon::nation_id{});
5154 n.get_navy().set_is_retreating(true);
5155 } else {
5156 if(!retreat(state, n.get_navy())) {
5157 n.get_navy().set_controller_from_navy_control(dcon::nation_id{});
5158 n.get_navy().set_is_retreating(true);
5159 }
5160 }
5161 } else if(!battle_attacker && result == battle_result::attacker_won) {
5162 if(!can_retreat_from_battle(state, b)) {
5163 n.get_navy().set_controller_from_navy_control(dcon::nation_id{});
5164 n.get_navy().set_is_retreating(true);
5165 } else {
5166 if(!retreat(state, n.get_navy())) {
5167 n.get_navy().set_controller_from_navy_control(dcon::nation_id{});
5168 n.get_navy().set_is_retreating(true);
5169 }
5170 }
5171 } else {
5172 auto path = n.get_navy().get_path();
5173 if(path.size() > 0) {
5174 state.world.navy_set_arrival_time(n.get_navy(), arrival_time_to(state, n.get_navy(), path.at(path.size() - 1)));
5175 }
5176
5177 for(auto em : n.get_navy().get_army_transport()) {
5178 auto apath = em.get_army().get_path();
5179 if(apath.size() > 0) {
5180 state.world.army_set_arrival_time(em.get_army(), arrival_time_to(state, em.get_army(), apath.at(apath.size() - 1)));
5181 }
5182 }
5183 }
5184 }
5185
5186 /*
5187 On finishing a naval battle:
5188 Each winning combatant get a random `on_battle_won` event, and each losing combatant gets a random `on_battle_lost` event.
5189
5190 Both sides compute their scaled losses fraction, which is (1 + (sum over ship type: supply-consumption-score x strength-losses))
5191 as a percentage of (total possible naval supply + 1). The scaled losses fraction of the loser / the sum of the scaled losses
5192 forms the base of the prestige gain for the nation and the leader in charge on the winning side. The winning leader gets that
5193 value / 100 as added prestige. The winning nations gets (defineLEADER_PRESTIGE_NAVAL_GAIN + 1) x (prestige-from-tech-modifier +
5194 1) x that value. Similarly, the losing nation and leader have their prestige reduced, calculated in the same way.
5195
5196 War score is gained based on the difference in losses (in absolute terms) divided by 5 plus 0.1 to a minimum of 0.1
5197 */
5198
5199 if(result != battle_result::indecisive) {
5200 state.world.war_get_number_of_battles(war)++;
5201
5202 auto a_leader = state.world.naval_battle_get_admiral_from_attacking_admiral(b);
5203 auto b_leader = state.world.naval_battle_get_admiral_from_defending_admiral(b);
5204
5205 if(result == battle_result::attacker_won) {
5206 auto score = std::max(0.0f,
5207 (state.world.naval_battle_get_defender_loss_value(b) - state.world.naval_battle_get_attacker_loss_value(b)) / 10.0f);
5208 if(state.world.naval_battle_get_war_attacker_is_attacker(b)) {
5209 state.world.war_get_attacker_battle_score(war) += score;
5210 } else {
5211 state.world.war_get_defender_battle_score(war) += score;
5212 }
5213
5214
5215 if(a_nation && d_nation) {
5216 nations::adjust_prestige(state, a_nation, score / 50.0f);
5217 nations::adjust_prestige(state, d_nation, score / -50.0f);
5218 adjust_leader_prestige(state, a_leader, score / 50.f / 100.f);
5219 adjust_leader_prestige(state, b_leader, score / -50.f / 100.f);
5220
5221 // Report
5222 if(state.local_player_nation == a_nation || state.local_player_nation == d_nation) {
5223 naval_battle_report rep;
5224 rep.attacker_big_losses = state.world.naval_battle_get_attacker_big_ships_lost(b);
5225 rep.attacker_big_ships = state.world.naval_battle_get_attacker_big_ships(b);
5226 rep.attacker_small_losses = state.world.naval_battle_get_attacker_small_ships_lost(b);
5227 rep.attacker_small_ships = state.world.naval_battle_get_attacker_small_ships(b);
5228 rep.attacker_transport_losses = state.world.naval_battle_get_attacker_transport_ships_lost(b);
5229 rep.attacker_transport_ships = state.world.naval_battle_get_attacker_transport_ships(b);
5230
5231 rep.defender_big_losses = state.world.naval_battle_get_defender_big_ships_lost(b);
5232 rep.defender_big_ships = state.world.naval_battle_get_defender_big_ships(b);
5233 rep.defender_small_losses = state.world.naval_battle_get_defender_small_ships_lost(b);
5234 rep.defender_small_ships = state.world.naval_battle_get_defender_small_ships(b);
5235 rep.defender_transport_losses = state.world.naval_battle_get_defender_transport_ships_lost(b);
5236 rep.defender_transport_ships = state.world.naval_battle_get_defender_transport_ships(b);
5237
5238 rep.attacker_won = (result == battle_result::attacker_won);
5239
5240 rep.attacking_nation = get_naval_battle_lead_attacker(state, b);
5241 rep.defending_nation = get_naval_battle_lead_defender(state, b);
5242 rep.attacking_admiral = state.world.naval_battle_get_admiral_from_attacking_admiral(b);
5243 rep.defending_admiral = state.world.naval_battle_get_admiral_from_defending_admiral(b);
5244
5245 rep.location = state.world.naval_battle_get_location_from_naval_battle_location(b);
5246 rep.player_on_winning_side =
5247 is_attacker(state, war, state.local_player_nation) == state.world.naval_battle_get_war_attacker_is_attacker(b);
5248
5249 if(rep.player_on_winning_side) {
5250 rep.warscore_effect = score;
5251 rep.prestige_effect = score / 50.0f;
5252 } else {
5253 rep.warscore_effect = -score;
5254 rep.prestige_effect = -score / 50.0f;
5255 }
5256 auto discard = state.naval_battle_reports.try_push(rep);
5257 }
5258 }
5259 } else if(result == battle_result::defender_won) {
5260 auto score = std::max(0.0f,
5261 (state.world.naval_battle_get_attacker_loss_value(b) - state.world.naval_battle_get_defender_loss_value(b)) / 10.0f);
5262 if(state.world.naval_battle_get_war_attacker_is_attacker(b)) {
5263 state.world.war_get_attacker_battle_score(war) += score;
5264 } else {
5265 state.world.war_get_defender_battle_score(war) += score;
5266 }
5267
5268 if(a_nation && d_nation) {
5269 nations::adjust_prestige(state, a_nation, score / -50.0f);
5270 nations::adjust_prestige(state, d_nation, score / 50.0f);
5271 adjust_leader_prestige(state, a_leader, score / -50.f / 100.f);
5272 adjust_leader_prestige(state, b_leader, score / 50.f / 100.f);
5273
5274 // Report
5275 if(state.local_player_nation == a_nation || state.local_player_nation == d_nation) {
5276 naval_battle_report rep;
5277 rep.attacker_big_losses = state.world.naval_battle_get_attacker_big_ships_lost(b);
5278 rep.attacker_big_ships = state.world.naval_battle_get_attacker_big_ships(b);
5279 rep.attacker_small_losses = state.world.naval_battle_get_attacker_small_ships_lost(b);
5280 rep.attacker_small_ships = state.world.naval_battle_get_attacker_small_ships(b);
5281 rep.attacker_transport_losses = state.world.naval_battle_get_attacker_transport_ships_lost(b);
5282 rep.attacker_transport_ships = state.world.naval_battle_get_attacker_transport_ships(b);
5283
5284 rep.defender_big_losses = state.world.naval_battle_get_defender_big_ships_lost(b);
5285 rep.defender_big_ships = state.world.naval_battle_get_defender_big_ships(b);
5286 rep.defender_small_losses = state.world.naval_battle_get_defender_small_ships_lost(b);
5287 rep.defender_small_ships = state.world.naval_battle_get_defender_small_ships(b);
5288 rep.defender_transport_losses = state.world.naval_battle_get_defender_transport_ships_lost(b);
5289 rep.defender_transport_ships = state.world.naval_battle_get_defender_transport_ships(b);
5290
5291 rep.attacker_won = (result == battle_result::attacker_won);
5292
5293 rep.attacking_nation = get_naval_battle_lead_attacker(state, b);
5294 rep.defending_nation = get_naval_battle_lead_defender(state, b);
5295 rep.attacking_admiral = state.world.naval_battle_get_admiral_from_attacking_admiral(b);
5296 rep.defending_admiral = state.world.naval_battle_get_admiral_from_defending_admiral(b);
5297
5298 rep.location = state.world.naval_battle_get_location_from_naval_battle_location(b);
5299 rep.player_on_winning_side =
5300 is_attacker(state, war, state.local_player_nation) != state.world.naval_battle_get_war_attacker_is_attacker(b);
5301
5302 if(rep.player_on_winning_side) {
5303 rep.warscore_effect = score;
5304 rep.prestige_effect = score / 50.0f;
5305 } else {
5306 rep.warscore_effect = -score;
5307 rep.prestige_effect = -score / 50.0f;
5308 }
5309 auto discard = state.naval_battle_reports.try_push(rep);
5310 }
5311 }
5312 }
5313 }
5314
5315 if(result != battle_result::indecisive) { // so we don't restart battles as the war is ending
5316 auto par_range = state.world.naval_battle_get_navy_battle_participation(b);
5317 while(par_range.begin() != par_range.end()) {
5318 auto n = (*par_range.begin()).get_navy();
5319 n.set_battle_from_navy_battle_participation(dcon::naval_battle_id{});
5320 navy_arrives_in_province(state, n, location, b);
5321 }
5322 }
5323
5324 state.world.delete_naval_battle(b);
5326
5327inline constexpr float combat_modifier_table[] = {0.0f, 0.02f, 0.04f, 0.06f, 0.08f, 0.10f, 0.12f, 0.16f, 0.20f, 0.25f, 0.30f,
5328 0.35f, 0.40f, 0.45f, 0.50f, 0.60f, 0.70f, 0.80f, 0.90f};
5329
5330dcon::nation_id tech_nation_for_regiment(sys::state& state, dcon::regiment_id r) {
5331 auto army = state.world.regiment_get_army_from_army_membership(r);
5332 auto nation = state.world.army_get_controller_from_army_control(army);
5333 if(nation)
5334 return nation;
5335 auto rf = state.world.army_get_controller_from_army_rebel_control(army);
5336 auto ruler = state.world.rebel_faction_get_ruler_from_rebellion_within(rf);
5337 if(ruler)
5338 return ruler;
5339 return state.world.national_identity_get_nation_from_identity_holder(state.national_definitions.rebel_id);
5341
5342bool will_recieve_attrition(sys::state& state, dcon::navy_id a) {
5343 return false;
5345
5346float peacetime_attrition_limit(sys::state& state, dcon::nation_id n, dcon::province_id prov) {
5347 auto supply_limit = supply_limit_in_province(state, n, prov);
5348 auto prov_attrition_mod = state.world.province_get_modifier_values(prov, sys::provincial_mod_offsets::attrition);
5349 auto attrition_mod = 1.0f + state.world.nation_get_modifier_values(n, sys::national_mod_offsets::land_attrition);
5350
5351 return (supply_limit + prov_attrition_mod) / (attrition_mod * 3.0f);
5353
5354bool will_recieve_attrition(sys::state& state, dcon::army_id a) {
5355 auto prov = state.world.army_get_location_from_army_location(a);
5356
5357 if(state.world.province_get_siege_progress(prov) > 0.f)
5358 return true;
5359
5360 float total_army_weight = 0;
5361 for(auto ar : state.world.province_get_army_location(prov)) {
5362 if(ar.get_army().get_black_flag() == false && ar.get_army().get_is_retreating() == false &&
5363 !bool(ar.get_army().get_navy_from_army_transport())) {
5364 for(auto rg : ar.get_army().get_army_membership()) {
5365 total_army_weight += 3.0f * rg.get_regiment().get_strength();
5366 }
5367 }
5368 }
5369
5370 auto prov_attrition_mod = state.world.province_get_modifier_values(prov, sys::provincial_mod_offsets::attrition);
5371 auto ar = fatten(state.world, a);
5372
5373 auto army_controller = ar.get_controller_from_army_control();
5374 auto supply_limit = supply_limit_in_province(state, army_controller, prov);
5375 auto attrition_mod = 1.0f + army_controller.get_modifier_values(sys::national_mod_offsets::land_attrition);
5376
5377 float greatest_hostile_fort = 0.0f;
5378 for(auto adj : state.world.province_get_province_adjacency(prov)) {
5380 auto other = adj.get_connected_provinces(0) != prov ? adj.get_connected_provinces(0) : adj.get_connected_provinces(1);
5381 if(other.get_building_level(uint8_t(economy::province_building_type::fort)) > 0) {
5382 if(are_at_war(state, army_controller, other.get_nation_from_province_control())) {
5383 greatest_hostile_fort = std::max(greatest_hostile_fort, float(other.get_building_level(uint8_t(economy::province_building_type::fort))));
5384 }
5385 }
5386 }
5387 }
5388 return total_army_weight * attrition_mod - (supply_limit + prov_attrition_mod + greatest_hostile_fort) > 0;
5390
5391float relative_attrition_amount(sys::state& state, dcon::navy_id a, dcon::province_id prov) {
5392 return 0.0f;
5394
5395float local_army_weight(sys::state& state, dcon::province_id prov) {
5396 float total_army_weight = 0;
5397 for(auto ar : state.world.province_get_army_location(prov)) {
5398 if(ar.get_army().get_black_flag() == false && ar.get_army().get_is_retreating() == false &&
5399 !bool(ar.get_army().get_navy_from_army_transport())) {
5400 for(auto rg : ar.get_army().get_army_membership()) {
5401 total_army_weight += 3.0f * rg.get_regiment().get_strength();
5402 }
5403 }
5404 }
5405 return total_army_weight;
5406}
5407float local_army_weight_max(sys::state& state, dcon::province_id prov) {
5408 float total_army_weight = 0;
5409 for(auto ar : state.world.province_get_army_location(prov)) {
5410 if(ar.get_army().get_black_flag() == false && ar.get_army().get_is_retreating() == false &&
5411 !bool(ar.get_army().get_navy_from_army_transport())) {
5412 for(auto rg : ar.get_army().get_army_membership()) {
5413 total_army_weight += 3.0f;
5414 }
5415 }
5416 }
5417 return total_army_weight;
5418}
5419float local_enemy_army_weight_max(sys::state& state, dcon::province_id prov, dcon::nation_id nation) {
5420 float total_army_weight = 0;
5421 for(auto ar : state.world.province_get_army_location(prov)) {
5422 if(
5423 ar.get_army().get_black_flag() == false
5424 && ar.get_army().get_is_retreating() == false
5425 && !bool(ar.get_army().get_navy_from_army_transport())
5426 && are_at_war(state, nation, ar.get_army().get_controller_from_army_control())
5427 ) {
5428 for(auto rg : ar.get_army().get_army_membership()) {
5429 total_army_weight += 3.0f;
5430 }
5431 }
5432 }
5433 return total_army_weight;
5435
5436float relative_attrition_amount(sys::state& state, dcon::army_id a, dcon::province_id prov) {
5437 float total_army_weight = 0;
5438 for(auto ar : state.world.province_get_army_location(prov)) {
5439 if(ar.get_army().get_black_flag() == false && ar.get_army().get_is_retreating() == false &&
5440 !bool(ar.get_army().get_navy_from_army_transport())) {
5441
5442 for(auto rg : ar.get_army().get_army_membership()) {
5443 total_army_weight += 3.0f * rg.get_regiment().get_strength();
5444 }
5445 }
5446 }
5447
5448 auto prov_attrition_mod = state.world.province_get_modifier_values(prov, sys::provincial_mod_offsets::attrition);
5449
5450 auto ar = fatten(state.world, a);
5451
5452 auto army_controller = ar.get_controller_from_army_control();
5453 auto supply_limit = supply_limit_in_province(state, army_controller, prov);
5454 auto attrition_mod = 1.0f + army_controller.get_modifier_values(sys::national_mod_offsets::land_attrition);
5455
5456 float greatest_hostile_fort = 0.0f;
5457
5458 for(auto adj : state.world.province_get_province_adjacency(prov)) {
5460 auto other = adj.get_connected_provinces(0) != prov ? adj.get_connected_provinces(0) : adj.get_connected_provinces(1);
5461 if(other.get_building_level(uint8_t(economy::province_building_type::fort)) > 0) {
5462 if(are_at_war(state, army_controller, other.get_nation_from_province_control())) {
5463 greatest_hostile_fort = std::max(greatest_hostile_fort, float(other.get_building_level(uint8_t(economy::province_building_type::fort))));
5464 }
5465 }
5466 }
5467 }
5468
5469 auto value = std::clamp(total_army_weight * attrition_mod - (supply_limit + prov_attrition_mod + greatest_hostile_fort), 0.0f, state.world.province_get_modifier_values(prov, sys::provincial_mod_offsets::max_attrition))
5470 + state.world.province_get_siege_progress(prov) > 0.f ? state.defines.siege_attrition : 0.0f;
5471 return value * 0.01f;
5472}
5473float attrition_amount(sys::state& state, dcon::navy_id a) {
5474 return relative_attrition_amount(state, a, state.world.navy_get_location_from_navy_location(a));
5475}
5476float attrition_amount(sys::state& state, dcon::army_id a) {
5477 return relative_attrition_amount(state, a, state.world.army_get_location_from_army_location(a));
5479
5480void apply_attrition(sys::state& state) {
5481 concurrency::parallel_for(0, state.province_definitions.first_sea_province.index(), [&](int32_t i) {
5482 dcon::province_id prov{dcon::province_id::value_base_t(i)};
5483 float total_army_weight = 0;
5484 for(auto ar : state.world.province_get_army_location(prov)) {
5485 if(ar.get_army().get_black_flag() == false && ar.get_army().get_is_retreating() == false &&
5486 !bool(ar.get_army().get_navy_from_army_transport()) && !bool(ar.get_army().get_battle_from_army_battle_participation())) {
5487
5488 for(auto rg : ar.get_army().get_army_membership()) {
5489 total_army_weight += 3.0f * rg.get_regiment().get_strength();
5490 }
5491 }
5492 }
5493
5494 auto prov_attrition_mod = state.world.province_get_modifier_values(prov, sys::provincial_mod_offsets::attrition);
5495
5496 for(auto ar : state.world.province_get_army_location(prov)) {
5497 if(ar.get_army().get_black_flag() == false && ar.get_army().get_is_retreating() == false &&
5498 !bool(ar.get_army().get_navy_from_army_transport()) && !bool(ar.get_army().get_battle_from_army_battle_participation())) {
5499
5500 auto army_controller = ar.get_army().get_controller_from_army_control();
5501 auto supply_limit = supply_limit_in_province(state, army_controller, prov);
5502 auto attrition_mod = 1.0f + army_controller.get_modifier_values(sys::national_mod_offsets::land_attrition);
5503
5504 float greatest_hostile_fort = 0.0f;
5505
5506 for(auto adj : state.world.province_get_province_adjacency(prov)) {
5507 if((adj.get_type() & (province::border::impassible_bit | province::border::coastal_bit)) == 0) {
5508 auto other = adj.get_connected_provinces(0) != prov ? adj.get_connected_provinces(0) : adj.get_connected_provinces(1);
5509 if(other.get_building_level(uint8_t(economy::province_building_type::fort)) > 0) {
5510 if(are_at_war(state, army_controller, other.get_nation_from_province_control())) {
5511 greatest_hostile_fort = std::max(greatest_hostile_fort, float(other.get_building_level(uint8_t(economy::province_building_type::fort))));
5512 }
5513 }
5514 }
5515 }
5516
5517 /*
5518 First we calculate (total-strength + leader-attrition-trait) x (attrition-modifier-from-technology + 1) -
5519 effective-province-supply-limit (rounded down to the nearest integer) + province-attrition-modifier +
5520 the-level-of-the-highest-hostile-fort-in-an-adjacent-province. We then reduce that value to at most the max-attrition
5521 modifier of the province, and finally we add define:SEIGE_ATTRITION if the army is conducting a siege. Units taking
5522 attrition lose max-strength x attrition-value x 0.01 points of strength. This strength loss is treated just like damage
5523 taken in combat, meaning that it will reduce the size of the backing pop.
5524 */
5525
5526 float attrition_value =
5527 std::clamp(total_army_weight * attrition_mod - (supply_limit + prov_attrition_mod + greatest_hostile_fort), 0.0f, state.world.province_get_modifier_values(prov, sys::provincial_mod_offsets::max_attrition))
5528 + state.world.province_get_siege_progress(prov) > 0.f ? state.defines.siege_attrition : 0.0f;
5529
5530 for(auto rg : ar.get_army().get_army_membership()) {
5531 rg.get_regiment().get_pending_damage() += attrition_value * 0.01f;
5532 rg.get_regiment().get_strength() -= attrition_value * 0.01f;
5533 }
5534 }
5535 }
5536 });
5538
5539void apply_regiment_damage(sys::state& state) {
5540 for(uint32_t i = state.world.regiment_size(); i-- > 0;) {
5541 dcon::regiment_id s{ dcon::regiment_id::value_base_t(i) };
5542 if(state.world.regiment_is_valid(s)) {
5543 auto& pending_damage = state.world.regiment_get_pending_damage(s);
5544 auto& current_strength = state.world.regiment_get_strength(s);
5545
5546 if(pending_damage > 0) {
5547 auto backing_pop = state.world.regiment_get_pop_from_regiment_source(s);
5548 auto tech_nation = tech_nation_for_regiment(state, s);
5549
5550 if(backing_pop) {
5551 auto& psize = state.world.pop_get_size(backing_pop);
5552 psize -= state.defines.pop_size_per_regiment * pending_damage * state.defines.soldier_to_pop_damage /
5553 (3.0f * (1.0f + state.world.nation_get_modifier_values(tech_nation,
5554 sys::national_mod_offsets::soldier_to_pop_loss)));
5555 if(psize <= 1.0f) {
5556 state.world.delete_pop(backing_pop);
5557 }
5558 }
5559 pending_damage = 0.0f;
5560 }
5561 if(current_strength <= 0.0f) {
5562 // When a rebel regiment is destroyed, divide the militancy of the backing pop by define:REDUCTION_AFTER_DEFEAT.
5563 auto army = state.world.regiment_get_army_from_army_membership(s);
5564 auto controller = state.world.army_get_controller_from_army_control(army);
5565 auto pop_backer = state.world.regiment_get_pop_from_regiment_source(s);
5566
5567 if(!controller) {
5568 if(pop_backer) {
5569 auto mil = pop_demographics::get_militancy(state, pop_backer) / state.defines.reduction_after_defeat;
5570 pop_demographics::set_militancy(state, pop_backer, mil);
5571 }
5572 } else {
5573 auto maxr = state.world.nation_get_recruitable_regiments(controller);
5574 if(maxr > 0 && pop_backer) {
5575 auto& wex = state.world.nation_get_war_exhaustion(controller);
5576 wex = std::min(wex + 0.5f / float(maxr), state.world.nation_get_modifier_values(controller, sys::national_mod_offsets::max_war_exhaustion));
5577 }
5578 }
5579
5580 if(auto b = state.world.army_get_battle_from_army_battle_participation(army); b) {
5581 for(auto& e : state.world.land_battle_get_attacker_back_line(b)) {
5582 if(e == s) {
5583 e = dcon::regiment_id{};
5584 }
5585 }
5586 for(auto& e : state.world.land_battle_get_attacker_front_line(b)) {
5587 if(e == s) {
5588 e = dcon::regiment_id{};
5589 }
5590 }
5591 for(auto& e : state.world.land_battle_get_defender_back_line(b)) {
5592 if(e == s) {
5593 e = dcon::regiment_id{};
5594 }
5595 }
5596 for(auto& e : state.world.land_battle_get_defender_front_line(b)) {
5597 if(e == s) {
5598 e = dcon::regiment_id{};
5599 }
5600 }
5601
5602 auto reserves = state.world.land_battle_get_reserves(b);
5603
5604 for(uint32_t j = reserves.size(); j-- > 0;) {
5605 if(reserves[j].regiment == s) {
5606 std::swap(reserves[j], reserves[reserves.size() - 1]);
5607 reserves.pop_back();
5608 break;
5609 }
5610 }
5611 }
5612 if(!controller || state.world.pop_get_size(pop_backer) < 1000.0f)
5613 state.world.delete_regiment(s);
5614 else
5615 current_strength = 0.0f;
5616 }
5617 }
5618 }
5620
5621void update_land_battles(sys::state& state) {
5622 auto isize = state.world.land_battle_size();
5623 auto to_delete = ve::vectorizable_buffer<uint8_t, dcon::land_battle_id>(isize);
5624
5625 concurrency::parallel_for(0, int32_t(isize), [&](int32_t index) {
5626 dcon::land_battle_id b{dcon::land_battle_id::value_base_t(index)};
5627
5628 if(!state.world.land_battle_is_valid(b))
5629 return;
5630
5631 // fill to combat width
5632 auto combat_width = state.world.land_battle_get_combat_width(b);
5633
5634 auto& att_back = state.world.land_battle_get_attacker_back_line(b);
5635 auto& def_back = state.world.land_battle_get_defender_back_line(b);
5636 auto& att_front = state.world.land_battle_get_attacker_front_line(b);
5637 auto& def_front = state.world.land_battle_get_defender_front_line(b);
5638
5639 auto reserves = state.world.land_battle_get_reserves(b);
5640
5641 if((state.current_date.value - state.world.land_battle_get_start_date(b).value) % 5 == 4) {
5642 state.world.land_battle_set_dice_rolls(b, make_dice_rolls(state, uint32_t(index)));
5643 }
5644
5645 // calculate modifier values for each side
5646
5647 /*
5648 Combat modifiers:
5649 In addition to the dice, both sides are affected by:
5650 Attack or defend trait of the leader in charge (attack for attackers, defend for defenders)
5651 define:GAS_ATTACK_MODIFIER if the side has gas attack and the other side doesn't have gas defense
5652 terrain defense bonus (defender only)
5653 -1 for attacking across a river or -2 for attacking from the sea
5654 minus the least dig in value on the opposing side / 2 (if the side has at a fraction of units with reconnaissance at least
5655 define:RECON_UNIT_RATIO) or divided by (greatest-regiment-reconnaissance x (leader-reconnaissance + 1) x
5656 fraction-of-reconnaissance-unit-strength / total-strength)
5657 */
5658
5659 auto both_dice = state.world.land_battle_get_dice_rolls(b);
5660 auto defender_mods = state.world.land_battle_get_defender_bonus(b);
5661 auto dig_in_value = defender_mods & defender_bonus_dig_in_mask;
5662 auto crossing_value = defender_mods & defender_bonus_crossing_mask;
5663
5664 auto attacking_nation = get_land_battle_lead_attacker(state, b);
5665 auto defending_nation = get_land_battle_lead_defender(state, b);
5666
5667 bool attacker_gas =
5668 state.world.nation_get_has_gas_attack(attacking_nation) && !state.world.nation_get_has_gas_defense(defending_nation);
5669 bool defender_gas =
5670 state.world.nation_get_has_gas_attack(defending_nation) && !state.world.nation_get_has_gas_defense(attacking_nation);
5671
5672 int32_t crossing_adjustment =
5673 (crossing_value == defender_bonus_crossing_none ? 0 : (crossing_value == defender_bonus_crossing_river ? -1 : -2));
5674
5675 auto attacker_dice = both_dice & 0x0F;
5676 auto defender_dice = (both_dice >> 4) & 0x0F;
5677
5678 auto location = state.world.land_battle_get_location_from_land_battle_location(b);
5679 auto terrain_bonus = state.world.province_get_modifier_values(location, sys::provincial_mod_offsets::defense);
5680
5681 auto attacker_per = state.world.leader_get_personality(state.world.land_battle_get_general_from_attacking_general(b));
5682 auto attacker_bg = state.world.leader_get_background(state.world.land_battle_get_general_from_attacking_general(b));
5683
5684 auto attack_bonus =
5685 int32_t(state.world.leader_trait_get_attack(attacker_per) + state.world.leader_trait_get_attack(attacker_bg));
5686 auto attacker_org_bonus =
5687 (1.0f + state.world.leader_trait_get_organisation(attacker_per) + state.world.leader_trait_get_organisation(attacker_bg))
5688 * (1.0f + state.world.leader_get_prestige(state.world.land_battle_get_general_from_attacking_general(b)) * state.defines.leader_prestige_to_max_org_factor);
5689
5690 auto defender_per = state.world.leader_get_personality(state.world.land_battle_get_general_from_defending_general(b));
5691 auto defender_bg = state.world.leader_get_background(state.world.land_battle_get_general_from_defending_general(b));
5692
5693 auto atk_leader_exp_mod = 1 + attacker_per.get_experience() + attacker_bg.get_experience();
5694 auto def_leader_exp_mod = 1 + defender_per.get_experience() + defender_per.get_experience();
5695
5696 auto defence_bonus =
5697 int32_t(state.world.leader_trait_get_defense(defender_per) + state.world.leader_trait_get_defense(defender_bg));
5698 auto defender_org_bonus =
5699 (1.0f + state.world.leader_trait_get_organisation(defender_per) + state.world.leader_trait_get_organisation(defender_bg))
5700 * (1.0f + state.world.leader_get_prestige(state.world.land_battle_get_general_from_defending_general(b)) * state.defines.leader_prestige_to_max_org_factor);
5701
5702 auto attacker_mod = combat_modifier_table[std::clamp(attacker_dice + attack_bonus + crossing_adjustment + int32_t(attacker_gas ? state.defines.gas_attack_modifier : 0.0f) + 3, 0, 18)];
5703 auto defender_mod = combat_modifier_table[std::clamp(defender_dice + defence_bonus + dig_in_value + int32_t(defender_gas ? state.defines.gas_attack_modifier : 0.0f) + int32_t(terrain_bonus) + 3, 0, 18)];
5704
5705 float defender_fort = 1.0f;
5706 auto local_control = state.world.province_get_nation_from_province_control(location);
5707 if((!attacking_nation && local_control) ||
5708 (attacking_nation && (!bool(local_control) || military::are_at_war(state, attacking_nation, local_control)))) {
5709 defender_fort = 1.0f + 0.1f * state.world.province_get_building_level(location, uint8_t(economy::province_building_type::fort));
5710 }
5711
5712 // apply damage to all regiments
5713
5714 // Effective military tactics = define:BASE_MILITARY_TACTICS + tactics-from-tech
5715
5716 /*
5717 Units attack the opposing front row, and may look either left or right of their position up to `maneuver` positions.
5718 Units on the attacking side use their `attack` stat to do damage, while units on the defending side use `defense`.
5719 Strength damage dealt: unit-strength x (attack/defense x 0.1 + 1) x Modifier-Table\[modifiers + 2\] x 2 /
5720 ((effective-fort-level x 0.1 + 1) x opposing-effective-military-tactics x (experience-of-opposing-regiment x 0.1 + 1))
5721 Organization damage dealt: unit-strength x (attack/defense x 0.1 + 1) x Modifier-Table\[modifiers + 2\] x 2 /
5722 ((effective-fort-level x 0.1 + 1) x opposing-discipline (if non zero) x (experience-of-opposing-regiment x 0.1 + 1)) Units
5723 attacking from the back row have these values multiplied by the unit's support value.
5724
5725 If the unit is in a province controlled by a hostile power, we find the effective level of the fort as with a siege (We find
5726 the effective level of the fort by subtracting: (rounding this value down to to the nearest integer)
5727 greatest-siege-value-present x
5728 ((the ratio of the strength of regiments with siege present to the total strength of all regiments) ^
5729 define:ENGINEER_UNIT_RATIO) / define:ENGINEER_UNIT_RATIO, reducing it to a minimum of 0.)
5730
5731 When a regiment takes strength damage, the size of the pop backing it is reduced by: define:POP_SIZE_PER_REGIMENT x
5732 damage-amount x define:SOLDIER_TO_POP_DAMAGE / (max-strength x (solder-to-pop-loss-from-tech + 1)). Note that this applied to
5733 damage from attrition as well.
5734 */
5735
5736 state.world.land_battle_set_attacker_casualties(b, 0);
5737 state.world.land_battle_set_defender_casualties(b, 0);
5738
5739 float attacker_casualties = 0;
5740 float defender_casualties = 0;
5741
5742 for(int32_t i = 0; i < combat_width; ++i) {
5743 // Attackers backline shooting defenders frontline
5744 if(att_back[i] && def_front[i]) {
5745 assert(state.world.regiment_is_valid(att_back[i]) && state.world.regiment_is_valid(def_front[i]));
5746
5747 auto tech_att_nation = tech_nation_for_regiment(state, att_back[i]);
5748 auto tech_def_nation = tech_nation_for_regiment(state, def_front[i]);
5749
5750 auto att_str = state.world.regiment_get_strength(att_back[i]);
5751
5752 auto& att_stats = state.world.nation_get_unit_stats(tech_att_nation, state.world.regiment_get_type(att_back[i]));
5753 auto& def_stats = state.world.nation_get_unit_stats(tech_def_nation, state.world.regiment_get_type(def_front[i]));
5754
5755 auto& def_exp = state.world.regiment_get_experience(def_front[i]);
5756
5757 auto str_damage = att_str * str_dam_mul *
5758 (att_stats.attack_or_gun_power * 0.1f + 1.0f) * att_stats.support * attacker_mod /
5759 (defender_fort * (state.defines.base_military_tactics + state.world.nation_get_modifier_values(tech_def_nation, sys::national_mod_offsets::military_tactics))
5760 * (1 + def_exp));
5761 auto org_damage = att_str * org_dam_mul *
5762 (att_stats.attack_or_gun_power * 0.1f + 1.0f) * att_stats.support * attacker_mod /
5763 (defender_fort * defender_org_bonus * def_stats.discipline_or_evasion *
5764 (1.0f + state.world.nation_get_modifier_values(tech_def_nation, sys::national_mod_offsets::land_organisation))
5765 * (1.0f + def_exp));
5766
5767 auto& cstr = state.world.regiment_get_strength(def_front[i]);
5768 str_damage = std::min(str_damage, cstr);
5769 state.world.regiment_get_pending_damage(def_front[i]) += str_damage;
5770 cstr -= str_damage;
5771 defender_casualties += str_damage;
5772
5773 adjust_regiment_experience(state, attacking_nation, att_back[i], str_damage * 5.f * state.defines.exp_gain_div * atk_leader_exp_mod);
5774
5775 auto& org = state.world.regiment_get_org(def_front[i]);
5776 org = std::max(0.0f, org - org_damage);
5777 switch(state.military_definitions.unit_base_definitions[state.world.regiment_get_type(def_front[i])].type) {
5778 case unit_type::infantry:
5779 state.world.land_battle_get_defender_infantry_lost(b) += str_damage;
5780 break;
5781 case unit_type::cavalry:
5782 state.world.land_battle_get_defender_cav_lost(b) += str_damage;
5783 break;
5784 case unit_type::support:
5785 // fallthrough
5786 case unit_type::special:
5787 state.world.land_battle_get_defender_support_lost(b) += str_damage;
5788 break;
5789 default:
5790 break;
5791 }
5792 }
5793
5794 // Defence backline shooting attackers frontline
5795 if(def_back[i] && att_front[i]) {
5796 assert(state.world.regiment_is_valid(def_back[i]) && state.world.regiment_is_valid(att_front[i]));
5797
5798 auto tech_def_nation = tech_nation_for_regiment(state, def_back[i]);
5799 auto tech_att_nation = tech_nation_for_regiment(state, att_front[i]);
5800
5801 auto& def_stats = state.world.nation_get_unit_stats(tech_def_nation, state.world.regiment_get_type(def_back[i]));
5802 auto& att_stats = state.world.nation_get_unit_stats(tech_att_nation, state.world.regiment_get_type(att_front[i]));
5803
5804 auto& atk_exp = state.world.regiment_get_experience(att_front[i]);
5805
5806 auto def_str = state.world.regiment_get_strength(def_back[i]);
5807
5808 auto str_damage = def_str * str_dam_mul * (def_stats.attack_or_gun_power * 0.1f + 1.0f) * def_stats.support * defender_mod / ((state.defines.base_military_tactics + state.world.nation_get_modifier_values(tech_att_nation, sys::national_mod_offsets::military_tactics)) * (1.f + atk_exp));
5809 auto org_damage = def_str * org_dam_mul * (def_stats.attack_or_gun_power * 0.1f + 1.0f) * def_stats.support * defender_mod / (attacker_org_bonus * def_stats.discipline_or_evasion * (1.0f + state.world.nation_get_modifier_values(tech_att_nation, sys::national_mod_offsets::land_organisation)) * (1.f + atk_exp));
5810
5811 auto& cstr = state.world.regiment_get_strength(att_front[i]);
5812 str_damage = std::min(str_damage, cstr);
5813 state.world.regiment_get_pending_damage(att_front[i]) += str_damage;
5814 cstr -= str_damage;
5815 attacker_casualties += str_damage;
5816
5817 adjust_regiment_experience(state, defending_nation, def_back[i], str_damage * 5.f * state.defines.exp_gain_div * def_leader_exp_mod);
5818
5819 auto& org = state.world.regiment_get_org(att_front[i]);
5820 org = std::max(0.0f, org - org_damage);
5821 switch(state.military_definitions.unit_base_definitions[state.world.regiment_get_type(att_front[i])].type) {
5822 case unit_type::infantry:
5823 state.world.land_battle_get_attacker_infantry_lost(b) += str_damage;
5824 break;
5825 case unit_type::cavalry:
5826 state.world.land_battle_get_attacker_cav_lost(b) += str_damage;
5827 break;
5828 case unit_type::support:
5829 // fallthrough
5830 case unit_type::special:
5831 state.world.land_battle_get_attacker_support_lost(b) += str_damage;
5832 break;
5833 default:
5834 break;
5835 }
5836 }
5837
5838 // Attackers frontline attacking defenders frontline targets
5839 if(att_front[i]) {
5840 assert(state.world.regiment_is_valid(att_front[i]));
5841
5842 auto tech_att_nation = tech_nation_for_regiment(state, att_front[i]);
5843 auto& att_stats = state.world.nation_get_unit_stats(tech_att_nation, state.world.regiment_get_type(att_front[i]));
5844
5845 auto att_front_target = def_front[i];
5846 if(auto mv = state.military_definitions.unit_base_definitions[state.world.regiment_get_type(att_front[i])].maneuver; !att_front_target && mv > 0.0f) {
5847 for(int32_t cnt = 1; i - cnt * 2 >= 0 && cnt <= int32_t(mv); ++cnt) {
5848 if(def_front[i - cnt * 2]) {
5849 att_front_target = def_front[i - cnt * 2];
5850 break;
5851 }
5852 }
5853 }
5854
5855 if(att_front_target) {
5856 assert(state.world.regiment_is_valid(att_front_target));
5857
5858 auto tech_def_nation = tech_nation_for_regiment(state, att_front_target);
5859 auto& def_stats = state.world.nation_get_unit_stats(tech_def_nation, state.world.regiment_get_type(att_front_target));
5860
5861 auto& def_exp = state.world.regiment_get_experience(att_front_target);
5862
5863 auto att_str = state.world.regiment_get_strength(att_front[i]);
5864
5865 auto str_damage = att_str * str_dam_mul *
5866 (att_stats.attack_or_gun_power * 0.1f + 1.0f) * attacker_mod /
5867 (defender_fort * (state.defines.base_military_tactics + state.world.nation_get_modifier_values(tech_def_nation, sys::national_mod_offsets::military_tactics))
5868 * (1+ def_exp));
5869 auto org_damage = att_str * org_dam_mul *
5870 (att_stats.attack_or_gun_power * 0.1f + 1.0f) * attacker_mod /
5871 (defender_fort * def_stats.discipline_or_evasion * defender_org_bonus * (1.0f + state.world.nation_get_modifier_values(tech_def_nation, sys::national_mod_offsets::land_organisation))
5872 * (1 + def_exp));
5873
5874 auto& cstr = state.world.regiment_get_strength(att_front_target);
5875 str_damage = std::min(str_damage, cstr);
5876 state.world.regiment_get_pending_damage(att_front_target) += str_damage;
5877 cstr -= str_damage;
5878 defender_casualties += str_damage;
5879
5880 adjust_regiment_experience(state, attacking_nation, att_front[i], str_damage * 5.f * state.defines.exp_gain_div * atk_leader_exp_mod);
5881
5882 auto& org = state.world.regiment_get_org(att_front_target);
5883 org = std::max(0.0f, org - org_damage);
5884 switch(state.military_definitions.unit_base_definitions[state.world.regiment_get_type(att_front_target)].type) {
5885 case unit_type::infantry:
5886 state.world.land_battle_get_defender_infantry_lost(b) += str_damage;
5887 break;
5888 case unit_type::cavalry:
5889 state.world.land_battle_get_defender_cav_lost(b) += str_damage;
5890 break;
5891 case unit_type::support:
5892 // fallthrough
5893 case unit_type::special:
5894 state.world.land_battle_get_defender_support_lost(b) += str_damage;
5895 break;
5896 default:
5897 break;
5898 }
5899 }
5900 }
5901
5902 // Defenders frontline attacks attackers frontline
5903 if(def_front[i]) {
5904 assert(state.world.regiment_is_valid(def_front[i]));
5905
5906 auto tech_def_nation = tech_nation_for_regiment(state, def_front[i]);
5907 auto& def_stats = state.world.nation_get_unit_stats(tech_def_nation, state.world.regiment_get_type(def_front[i]));
5908
5909 auto def_front_target = att_front[i];
5910
5911 if(auto mv = state.military_definitions.unit_base_definitions[state.world.regiment_get_type(def_front[i])].maneuver; !def_front_target && mv > 0.0f) {
5912 for(int32_t cnt = 1; i - cnt * 2 >= 0 && cnt <= int32_t(mv); ++cnt) {
5913 if(att_front[i - cnt * 2]) {
5914 def_front_target = att_front[i - cnt * 2];
5915 break;
5916 }
5917 }
5918 }
5919
5920 if(def_front_target) {
5921 assert(state.world.regiment_is_valid(def_front_target));
5922
5923 auto tech_att_nation = tech_nation_for_regiment(state, def_front_target);
5924 auto& att_stats = state.world.nation_get_unit_stats(tech_att_nation, state.world.regiment_get_type(def_front_target));
5925
5926 auto& atk_exp = state.world.regiment_get_experience(def_front_target);
5927
5928 auto def_str = state.world.regiment_get_strength(def_front[i]);
5929
5930 auto str_damage = def_str * str_dam_mul * (def_stats.attack_or_gun_power * 0.1f + 1.0f) * defender_mod / ((state.defines.base_military_tactics + state.world.nation_get_modifier_values(tech_att_nation, sys::national_mod_offsets::military_tactics))
5931 * (1+ atk_exp));
5932 auto org_damage = def_str * org_dam_mul * (def_stats.attack_or_gun_power * 0.1f + 1.0f) * defender_mod / (attacker_org_bonus * def_stats.discipline_or_evasion * (1.0f + state.world.nation_get_modifier_values(tech_att_nation, sys::national_mod_offsets::land_organisation))
5933 * (1+ atk_exp));
5934
5935 auto& cstr = state.world.regiment_get_strength(def_front_target);
5936 str_damage = std::min(str_damage, cstr);
5937 state.world.regiment_get_pending_damage(def_front_target) += str_damage;
5938 cstr -= str_damage;
5939 attacker_casualties += str_damage;
5940
5941 adjust_regiment_experience(state, defending_nation, def_front[i], str_damage * 5.f * state.defines.exp_gain_div * def_leader_exp_mod);
5942
5943 auto& org = state.world.regiment_get_org(def_front_target);
5944 org = std::max(0.0f, org - org_damage);
5945 switch(state.military_definitions.unit_base_definitions[state.world.regiment_get_type(def_front_target)].type) {
5946 case unit_type::infantry:
5947 state.world.land_battle_get_attacker_infantry_lost(b) += str_damage;
5948 break;
5949 case unit_type::cavalry:
5950 state.world.land_battle_get_attacker_cav_lost(b) += str_damage;
5951 break;
5952 case unit_type::support:
5953 // fallthrough
5954 case unit_type::special:
5955 state.world.land_battle_get_attacker_support_lost(b) += str_damage;
5956 break;
5957 default:
5958 break;
5959 }
5960 }
5961 }
5962 }
5963
5964 state.world.land_battle_set_attacker_casualties(b, attacker_casualties);
5965 state.world.land_battle_set_defender_casualties(b, defender_casualties);
5966
5967
5968 // clear dead / retreated regiments out
5969
5970 for(int32_t i = 0; i < combat_width; ++i) {
5971 if(def_back[i]) {
5972 if(state.world.regiment_get_strength(def_back[i]) <= 0.0f) {
5973 def_back[i] = dcon::regiment_id{};
5974 } else if(state.world.regiment_get_org(def_back[i]) < 0.1f) {
5975 def_back[i] = dcon::regiment_id{};
5976 }
5977 }
5978 if(def_front[i]) {
5979 if(state.world.regiment_get_strength(def_front[i]) <= 0.0f) {
5980 def_front[i] = dcon::regiment_id{};
5981 } else if(state.world.regiment_get_org(def_front[i]) < 0.1f) {
5982 def_front[i] = dcon::regiment_id{};
5983 }
5984 }
5985 if(att_back[i]) {
5986 if(state.world.regiment_get_strength(att_back[i]) <= 0.0f) {
5987 att_back[i] = dcon::regiment_id{};
5988 } else if(state.world.regiment_get_org(att_back[i]) < 0.1f) {
5989 att_back[i] = dcon::regiment_id{};
5990 }
5991 }
5992 if(att_front[i]) {
5993 if(state.world.regiment_get_strength(att_front[i]) <= 0.0f) {
5994 att_front[i] = dcon::regiment_id{};
5995 } else if(state.world.regiment_get_org(att_front[i]) < 0.1f) {
5996 att_front[i] = dcon::regiment_id{};
5997 }
5998 }
5999 }
6000
6001
6002 auto compact = [](std::array<dcon::regiment_id, 30>& a) {
6003 int32_t low = 0;
6004 while(low < 30 && a[low]) {
6005 low += 2;
6006 }
6007 int32_t high = low + 2;
6008 while(high < 30 && !a[high])
6009 high += 2;
6010
6011 while(high < 30) {
6012 a[low] = a[high];
6013 a[high] = dcon::regiment_id{};
6014
6015 high += 2;
6016 while(high < 30 && !a[high])
6017 high += 2;
6018
6019 low += 2;
6020 }
6021
6022 low = 1;
6023 while(low < 30 && a[low]) {
6024 low += 2;
6025 }
6026 high = low + 2;
6027 while(high < 30 && !a[high])
6028 high += 2;
6029
6030 while(high < 30) {
6031 a[low] = a[high];
6032 a[high] = dcon::regiment_id{};
6033
6034 high += 2;
6035 while(high < 30 && !a[high])
6036 high += 2;
6037
6038 low += 2;
6039 }
6040 };
6041
6042 compact(att_back);
6043 compact(att_front);
6044 compact(def_back);
6045 compact(def_front);
6046
6047 // prefer slot zero
6048 if(!att_back[0]) {
6049 std::swap(att_back[0], att_back[1]);
6050 }
6051 if(!def_back[0]) {
6052 std::swap(def_back[0], def_back[1]);
6053 }
6054 if(!att_front[0]) {
6055 std::swap(att_front[0], att_front[1]);
6056 }
6057 if(!def_front[0]) {
6058 std::swap(def_front[0], def_front[1]);
6059 }
6060
6061 for(int32_t i = 0; i < combat_width; ++i) {
6062 if(!att_back[i]) {
6063 for(uint32_t j = reserves.size(); j-- > 0;) {
6064 if(reserves[j].flags == (reserve_regiment::is_attacking | reserve_regiment::type_support)) {
6065 att_back[i] = reserves[j].regiment;
6066 std::swap(reserves[j], reserves[reserves.size() - 1]);
6067 reserves.pop_back();
6068 break;
6069 }
6070 }
6071 }
6072
6073 if(!def_back[i]) {
6074 for(uint32_t j = reserves.size(); j-- > 0;) {
6075 if(reserves[j].flags == (reserve_regiment::type_support)) {
6076 def_back[i] = reserves[j].regiment;
6077 std::swap(reserves[j], reserves[reserves.size() - 1]);
6078 reserves.pop_back();
6079 break;
6080 }
6081 }
6082 }
6083 }
6084
6085 // front row
6086
6087 for(int32_t i = 0; i < combat_width; ++i) {
6088 if(!att_front[i]) {
6089
6090 for(uint32_t j = reserves.size(); j-- > 0;) {
6091 if(reserves[j].flags == (reserve_regiment::is_attacking | reserve_regiment::type_infantry)) {
6092 att_front[i] = reserves[j].regiment;
6093 std::swap(reserves[j], reserves[reserves.size() - 1]);
6094 reserves.pop_back();
6095 break;
6096 }
6097 }
6098
6099 if(!att_front[i]) {
6100 for(uint32_t j = reserves.size(); j-- > 0;) {
6101 if(reserves[j].flags == (reserve_regiment::is_attacking | reserve_regiment::type_cavalry)) {
6102 att_front[i] = reserves[j].regiment;
6103 std::swap(reserves[j], reserves[reserves.size() - 1]);
6104 reserves.pop_back();
6105 break;
6106 }
6107 }
6108 }
6109 if(!att_front[i] && att_back[i]) {
6110 std::swap(att_front[i], att_back[i]);
6111 }
6112 }
6113
6114 if(!def_front[i]) {
6115 for(uint32_t j = reserves.size(); j-- > 0;) {
6116 if(reserves[j].flags == (reserve_regiment::type_infantry)) {
6117 def_front[i] = reserves[j].regiment;
6118 std::swap(reserves[j], reserves[reserves.size() - 1]);
6119 reserves.pop_back();
6120 break;
6121 }
6122 }
6123
6124 if(!def_front[i]) {
6125 for(uint32_t j = reserves.size(); j-- > 0;) {
6126 if(reserves[j].flags == (reserve_regiment::type_cavalry)) {
6127 def_front[i] = reserves[j].regiment;
6128 std::swap(reserves[j], reserves[reserves.size() - 1]);
6129 reserves.pop_back();
6130 break;
6131 }
6132 }
6133 }
6134 if(!def_front[i] && def_back[i]) {
6135 std::swap(def_front[i], def_back[i]);
6136 }
6137 }
6138 }
6139
6140 if(!def_front[0]) {
6141 to_delete.set(b, uint8_t(1));
6142 return;
6143 } else if(!att_front[0]) {
6144 to_delete.set(b, uint8_t(2));
6145 return;
6146 }
6147 });
6148
6149 for(auto i = isize; i-- > 0;) {
6150 dcon::land_battle_id b{dcon::land_battle_id::value_base_t(i) };
6151 if(state.world.land_battle_is_valid(b) && to_delete.get(b) != 0) {
6152 end_battle(state, b, to_delete.get(b) == uint8_t(1) ? battle_result::attacker_won : battle_result::defender_won);
6153 }
6154 }
6156
6157void update_naval_battles(sys::state& state) {
6158 auto isize = state.world.naval_battle_size();
6159 auto to_delete = ve::vectorizable_buffer<uint8_t, dcon::naval_battle_id>(isize);
6160
6161 concurrency::parallel_for(0, int32_t(isize), [&](int32_t index) {
6162 dcon::naval_battle_id b{dcon::naval_battle_id::value_base_t(index)};
6163
6164 if(!state.world.naval_battle_is_valid(b))
6165 return;
6166
6167 int32_t attacker_ships = 0;
6168 int32_t defender_ships = 0;
6169
6170 auto slots = state.world.naval_battle_get_slots(b);
6171
6172 for(uint32_t j = slots.size(); j-- > 0;) {
6173 switch(slots[j].flags & ship_in_battle::mode_mask) {
6174 case ship_in_battle::mode_seeking:
6175 case ship_in_battle::mode_approaching:
6176 case ship_in_battle::mode_retreating:
6177 case ship_in_battle::mode_engaged:
6178 if((slots[j].flags & ship_in_battle::is_attacking) != 0)
6179 ++attacker_ships;
6180 else
6181 ++defender_ships;
6182 break;
6183 default:
6184 break;
6185 }
6186 }
6187
6188 if(defender_ships == 0) {
6189 to_delete.set(b, uint8_t(1));
6190 return;
6191 } else if(attacker_ships == 0) {
6192 to_delete.set(b, uint8_t(2));
6193 return;
6194 }
6195
6196 if((state.current_date.value - state.world.naval_battle_get_start_date(b).value) % 5 == 4) {
6197 state.world.naval_battle_set_dice_rolls(b, make_dice_rolls(state, uint32_t(index)));
6198 }
6199
6200 auto both_dice = state.world.naval_battle_get_dice_rolls(b);
6201 auto attacker_dice = both_dice & 0x0F;
6202 auto defender_dice = (both_dice >> 4) & 0x0F;
6203
6204 auto attacker_per = state.world.leader_get_personality(state.world.naval_battle_get_admiral_from_attacking_admiral(b));
6205 auto attacker_bg = state.world.leader_get_background(state.world.naval_battle_get_admiral_from_attacking_admiral(b));
6206
6207 auto attack_bonus =
6208 int32_t(state.world.leader_trait_get_attack(attacker_per) + state.world.leader_trait_get_attack(attacker_bg));
6209 auto attacker_org_bonus =
6210 1.0f + state.world.leader_trait_get_organisation(attacker_per) + state.world.leader_trait_get_organisation(attacker_bg);
6211
6212 auto defender_per = state.world.leader_get_personality(state.world.naval_battle_get_admiral_from_defending_admiral(b));
6213 auto defender_bg = state.world.leader_get_background(state.world.naval_battle_get_admiral_from_defending_admiral(b));
6214
6215 auto atk_leader_exp_mod = 1 + attacker_per.get_experience() + attacker_bg.get_experience();
6216 auto def_leader_exp_mod = 1 + defender_per.get_experience() + defender_per.get_experience();
6217
6218 auto defence_bonus =
6219 int32_t(state.world.leader_trait_get_defense(defender_per) + state.world.leader_trait_get_defense(defender_bg));
6220 auto defender_org_bonus =
6221 1.0f + state.world.leader_trait_get_organisation(defender_per) + state.world.leader_trait_get_organisation(defender_bg);
6222
6223 auto attacker_mod = combat_modifier_table[std::clamp(attacker_dice + attack_bonus + 3, 0, 18)];
6224 auto defender_mod = combat_modifier_table[std::clamp(defender_dice + defence_bonus + 3, 0, 18)];
6225
6226 for(uint32_t j = slots.size(); j-- > 0;) {
6227 auto ship_owner =
6228 state.world.navy_get_controller_from_navy_control(state.world.ship_get_navy_from_navy_membership(slots[j].ship));
6229 auto ship_type = state.world.ship_get_type(slots[j].ship);
6230
6231 assert((slots[j].flags & ship_in_battle::mode_mask) == ship_in_battle::mode_sunk || (slots[j].flags & ship_in_battle::mode_mask) == ship_in_battle::mode_retreated || ship_type);
6232
6233 auto& ship_stats = state.world.nation_get_unit_stats(ship_owner, ship_type);
6234
6235 auto aship = slots[j].ship;
6236 auto aship_owner =
6237 state.world.navy_get_controller_from_navy_control(state.world.ship_get_navy_from_navy_membership(aship));
6238
6239 switch(slots[j].flags & ship_in_battle::mode_mask) {
6240 case ship_in_battle::mode_approaching: {
6241 auto target_mode = slots[slots[j].target_slot].flags & ship_in_battle::mode_mask;
6242 if(target_mode == ship_in_battle::mode_retreated || target_mode == ship_in_battle::mode_sunk) {
6243 slots[j].flags &= ~ship_in_battle::mode_mask;
6244 slots[j].flags |= ship_in_battle::mode_seeking;
6245 break;
6246 }
6247
6248 /*
6249 An approaching ship:
6250 Has its distance reduced by (random-value-in-range-\[0.0 - 0.5) + 0.5) x max-speed x
6251 define:NAVAL_COMBAT_SPEED_TO_DISTANCE_FACTOR * 1000 to a minimum of 0. Switches to engaged when its distance + the
6252 target's distance is less than its fire range
6253 */
6254
6255 float speed = ship_stats.maximum_speed * 1000.0f * state.defines.naval_combat_speed_to_distance_factor *
6256 (0.5f + float(rng::get_random(state, uint32_t(slots[j].ship.value)) & 0x7FFF) / float(0xFFFF));
6257 auto old_distance = slots[j].flags & ship_in_battle::distance_mask;
6258 int32_t adjust = std::clamp(int32_t(std::ceil(speed)), 0, old_distance);
6259 slots[j].flags &= ~ship_in_battle::distance_mask;
6260 slots[j].flags |= ship_in_battle::distance_mask & (old_distance - adjust);
6261
6262 if(old_distance == adjust ||
6263 (old_distance - adjust) + (slots[slots[j].target_slot].flags & ship_in_battle::distance_mask) <
6264 int32_t(1000.0f * ship_stats.reconnaissance_or_fire_range)) {
6265
6266 slots[j].flags &= ~ship_in_battle::mode_mask;
6267 slots[j].flags |= ship_in_battle::mode_engaged;
6268 }
6269
6270 break;
6271 }
6272 case ship_in_battle::mode_engaged: {
6273 auto target_mode = slots[slots[j].target_slot].flags & ship_in_battle::mode_mask;
6274 if(target_mode == ship_in_battle::mode_retreated || target_mode == ship_in_battle::mode_sunk) {
6275 slots[j].flags &= ~ship_in_battle::mode_mask;
6276 slots[j].flags |= ship_in_battle::mode_seeking;
6277 break;
6278 }
6279 bool target_is_big = (slots[slots[j].target_slot].flags & ship_in_battle::type_mask) == ship_in_battle::type_big;
6280 bool is_attacker = (slots[j].flags & ship_in_battle::is_attacking) != 0;
6281 auto tship = slots[slots[j].target_slot].ship;
6282 assert(tship);
6283
6284 auto ship_target_owner =
6285 state.world.navy_get_controller_from_navy_control(state.world.ship_get_navy_from_navy_membership(tship));
6286 auto ttype = state.world.ship_get_type(tship);
6287 assert(ttype);
6288 auto& ship_target_stats = state.world.nation_get_unit_stats(ship_target_owner, ttype);
6289
6290 /*
6291 Torpedo attack: is treated as 0 except against big ships
6292 Damage to organization is (gun-power + torpedo-attack) x Modifier-Table\[modifiers + 2\] (see above) x target-strength x
6293 define:NAVAL_COMBAT_DAMAGE_ORG_MULT / (target-max-hull x target-experience x 0.1 + 1) Damage to strength is (gun-power +
6294 torpedo-attack) x Modifier-Table\[modifiers + 2\] (see above) x attacker-strength x define:NAVAL_COMBAT_DAMAGE_STR_MULT x
6295 define:NAVAL_COMBAT_DAMAGE_MULT_NO_ORG (if target has no org) / (target-max-hull x target-experience x 0.1 + 1)
6296 */
6297
6298 auto& targ_ship_exp = state.world.ship_get_experience(tship);
6299
6300 float org_damage = org_dam_mul * (ship_stats.attack_or_gun_power + (target_is_big ? ship_stats.siege_or_torpedo_attack : 0.0f)) *
6301 (is_attacker ? attacker_mod : defender_mod) * state.defines.naval_combat_damage_org_mult /
6302 ((ship_target_stats.defence_or_hull + 1.0f) * (is_attacker ? defender_org_bonus : attacker_org_bonus) *
6303 (1.0f + state.world.nation_get_modifier_values(ship_target_owner,
6304 sys::national_mod_offsets::naval_organisation))
6305 * (1 + targ_ship_exp));
6306 float str_damage = str_dam_mul * (ship_stats.attack_or_gun_power + (target_is_big ? ship_stats.siege_or_torpedo_attack : 0.0f)) *
6307 (is_attacker ? attacker_mod : defender_mod) * state.defines.naval_combat_damage_str_mult /
6308 ((ship_target_stats.defence_or_hull + 1.0f) * (1 + targ_ship_exp));
6309
6310 auto& torg = state.world.ship_get_org(tship);
6311 torg = std::max(0.0f, torg - org_damage);
6312 auto& tstr = state.world.ship_get_strength(tship);
6313 tstr = std::max(0.0f, tstr - str_damage);
6314
6315 auto leader_exp_mod = (is_attacker ? atk_leader_exp_mod : def_leader_exp_mod);
6316 adjust_ship_experience(state, aship_owner, aship, str_damage * 5.f * state.defines.exp_gain_div * leader_exp_mod);
6317
6318 break;
6319 }
6320 case ship_in_battle::mode_retreating: {
6321 /*
6322 A retreating ship will increase its distance by define:NAVAL_COMBAT_RETREAT_SPEED_MOD x
6323 define:NAVAL_COMBAT_SPEED_TO_DISTANCE_FACTOR x (random value in the range \[0.0 - 0.5) + 0.5) x ship-max-speed.
6324 */
6325
6326 float speed = ship_stats.maximum_speed * 1000.0f * state.defines.naval_combat_retreat_speed_mod *
6327 state.defines.naval_combat_speed_to_distance_factor *
6328 (0.5f + float(rng::get_random(state, uint32_t(slots[j].ship.value)) & 0x7FFF) / float(0xFFFF));
6329
6330 auto old_distance = slots[j].flags & ship_in_battle::distance_mask;
6331 int32_t new_distance = std::min(int32_t(std::ceil(speed)) + old_distance, 1000);
6332 slots[j].flags &= ~ship_in_battle::distance_mask;
6333 slots[j].flags |= ship_in_battle::distance_mask & (new_distance);
6334
6335 break;
6336 }
6337 case ship_in_battle::mode_seeking: {
6338 /*
6339 When a target is selected, distance is increased by random-value-in-range-\[0.0, 1.0) x (1.0 -
6340 combat-duration^define:NAVAL_COMBAT_SHIFT_BACK_DURATION_SCALE) / NAVAL_COMBAT_SHIFT_BACK_DURATION_SCALE) x
6341 NAVAL_COMBAT_SHIFT_BACK_ON_NEXT_TARGET to a maximum of 1000, and the ship switches to approaching.
6342 */
6343
6344 if((slots[j].flags & ship_in_battle::is_attacking) != 0) {
6345 auto pick = rng::get_random(state, uint32_t(slots[j].ship.value)) % defender_ships;
6346
6347 [&]() {
6348 for(uint32_t k = slots.size(); k-- > 0;) {
6349 switch(slots[k].flags & ship_in_battle::mode_mask) {
6350
6351 case ship_in_battle::mode_seeking:
6352 case ship_in_battle::mode_approaching:
6353 case ship_in_battle::mode_retreating:
6354 case ship_in_battle::mode_engaged:
6355 if((slots[k].flags & ship_in_battle::is_attacking) == 0) {
6356 if(pick == 0) {
6357 slots[j].target_slot = uint16_t(k);
6358 return;
6359 } else {
6360 --pick;
6361 }
6362 }
6363 break;
6364 default:
6365 break;
6366 }
6367 }
6368 }();
6369
6370 auto old_distance = slots[j].flags & ship_in_battle::distance_mask;
6371 int32_t new_distance = std::min(old_distance + 400, 1000);
6372
6373 slots[j].flags &= ~ship_in_battle::mode_mask;
6374 slots[j].flags |= ship_in_battle::mode_approaching;
6375 slots[j].flags &= ~ship_in_battle::distance_mask;
6376 slots[j].flags |= ship_in_battle::distance_mask & new_distance;
6377 } else {
6378 auto pick = rng::get_random(state, uint32_t(slots[j].ship.value)) % attacker_ships;
6379
6380 [&]() {
6381 for(uint32_t k = slots.size(); k-- > 0;) {
6382 switch(slots[k].flags & ship_in_battle::mode_mask) {
6383
6384 case ship_in_battle::mode_seeking:
6385 case ship_in_battle::mode_approaching:
6386 case ship_in_battle::mode_retreating:
6387 case ship_in_battle::mode_engaged:
6388 if((slots[k].flags & ship_in_battle::is_attacking) != 0) {
6389 if(pick == 0) {
6390 slots[j].target_slot = uint16_t(k);
6391 return;
6392 } else {
6393 --pick;
6394 }
6395 }
6396 break;
6397 default:
6398 break;
6399 }
6400 }
6401 }();
6402
6403 auto old_distance = slots[j].flags & ship_in_battle::distance_mask;
6404 int32_t new_distance = std::min(old_distance + 400, 1000);
6405
6406 slots[j].flags &= ~ship_in_battle::mode_mask;
6407 slots[j].flags |= ship_in_battle::mode_approaching;
6408 slots[j].flags &= ~ship_in_battle::distance_mask;
6409 slots[j].flags |= ship_in_battle::distance_mask & new_distance;
6410 }
6411
6412 break;
6413 }
6414 default:
6415 break;
6416 }
6417 }
6418
6419 for(uint32_t j = slots.size(); j-- > 0;) { // test health, retreat and/or sink
6420 auto ship_owner =
6421 state.world.navy_get_controller_from_navy_control(state.world.ship_get_navy_from_navy_membership(slots[j].ship));
6422 auto type = state.world.ship_get_type(slots[j].ship);
6423
6424 switch(slots[j].flags & ship_in_battle::mode_mask) {
6425 case ship_in_battle::mode_seeking:
6426 case ship_in_battle::mode_approaching:
6427 case ship_in_battle::mode_engaged:
6428 if(state.world.ship_get_strength(slots[j].ship) <= 0) {
6429 if((slots[j].flags & ship_in_battle::is_attacking) != 0) {
6430 if((slots[j].flags & ship_in_battle::type_mask) == ship_in_battle::type_big) {
6431 state.world.naval_battle_get_attacker_big_ships_lost(b)++;
6432 } else if((slots[j].flags & ship_in_battle::type_mask) == ship_in_battle::type_small) {
6433 state.world.naval_battle_get_attacker_small_ships_lost(b)++;
6434 } else if((slots[j].flags & ship_in_battle::type_mask) == ship_in_battle::type_transport) {
6435 state.world.naval_battle_get_attacker_transport_ships_lost(b)++;
6436 }
6437 state.world.naval_battle_get_attacker_loss_value(b) += state.military_definitions.unit_base_definitions[type].supply_consumption_score;
6438 } else {
6439 if((slots[j].flags & ship_in_battle::type_mask) == ship_in_battle::type_big) {
6440 state.world.naval_battle_get_defender_big_ships_lost(b)++;
6441 } else if((slots[j].flags & ship_in_battle::type_mask) == ship_in_battle::type_small) {
6442 state.world.naval_battle_get_defender_small_ships_lost(b)++;
6443 } else if((slots[j].flags & ship_in_battle::type_mask) == ship_in_battle::type_transport) {
6444 state.world.naval_battle_get_defender_transport_ships_lost(b)++;
6445 }
6446 state.world.naval_battle_get_defender_loss_value(b) += state.military_definitions.unit_base_definitions[type].supply_consumption_score;
6447 }
6448 slots[j].flags &= ~ship_in_battle::mode_mask;
6449 slots[j].flags |= ship_in_battle::mode_sunk;
6450 break;
6451 }
6452 if(state.world.ship_get_strength(slots[j].ship) <= state.defines.naval_combat_retreat_str_org_level ||
6453 state.world.ship_get_org(slots[j].ship) <= state.defines.naval_combat_retreat_str_org_level) {
6454
6455 slots[j].flags &= ~ship_in_battle::mode_mask;
6456 slots[j].flags |= ship_in_battle::mode_retreating;
6457 }
6458 break;
6459 case ship_in_battle::mode_retreating:
6460 if(state.world.ship_get_strength(slots[j].ship) <= 0) {
6461 if((slots[j].flags & ship_in_battle::is_attacking) != 0) {
6462 if((slots[j].flags & ship_in_battle::type_mask) == ship_in_battle::type_big) {
6463 state.world.naval_battle_get_attacker_big_ships_lost(b)++;
6464 } else if((slots[j].flags & ship_in_battle::type_mask) == ship_in_battle::type_small) {
6465 state.world.naval_battle_get_attacker_small_ships_lost(b)++;
6466 } else if((slots[j].flags & ship_in_battle::type_mask) == ship_in_battle::type_transport) {
6467 state.world.naval_battle_get_attacker_transport_ships_lost(b)++;
6468 }
6469 } else {
6470 if((slots[j].flags & ship_in_battle::type_mask) == ship_in_battle::type_big) {
6471 state.world.naval_battle_get_defender_big_ships_lost(b)++;
6472 } else if((slots[j].flags & ship_in_battle::type_mask) == ship_in_battle::type_small) {
6473 state.world.naval_battle_get_defender_small_ships_lost(b)++;
6474 } else if((slots[j].flags & ship_in_battle::type_mask) == ship_in_battle::type_transport) {
6475 state.world.naval_battle_get_defender_transport_ships_lost(b)++;
6476 }
6477 }
6478 slots[j].flags &= ~ship_in_battle::mode_mask;
6479 slots[j].flags |= ship_in_battle::mode_sunk;
6480 break;
6481 }
6482 if((slots[j].flags & ship_in_battle::distance_mask) >= 1000) {
6483 slots[j].flags &= ~ship_in_battle::mode_mask;
6484 slots[j].flags |= ship_in_battle::mode_retreated;
6485 }
6486 break;
6487 default:
6488 break;
6489 }
6490 }
6491 });
6492
6493 for(auto i = isize; i-- > 0;) {
6494 dcon::naval_battle_id b{ dcon::naval_battle_id::value_base_t(i) };
6495 if(state.world.naval_battle_is_valid(b) && to_delete.get(b) != 0) {
6496 end_battle(state, b, to_delete.get(b) == uint8_t(1) ? battle_result::attacker_won : battle_result::defender_won);
6497 }
6498 }
6499
6500 for(uint32_t i = state.world.ship_size(); i-- > 0;) {
6501 dcon::ship_id s{dcon::ship_id::value_base_t(i)};
6502 if(state.world.ship_is_valid(s)) {
6503 if(state.world.ship_get_strength(s) <= 0.0f) {
6504 state.world.delete_ship(s);
6505 }
6506 }
6507 }
6509
6510uint8_t make_dice_rolls(sys::state& state, uint32_t seed) {
6511 auto rvals = rng::get_random_pair(state, seed);
6512 auto low_roll = rvals.low % 10;
6513 auto high_roll = rvals.high % 10;
6514 return uint8_t((high_roll << 4) | low_roll);
6516
6517void navy_arrives_in_province(sys::state& state, dcon::navy_id n, dcon::province_id p, dcon::naval_battle_id from) {
6518 assert(state.world.navy_is_valid(n));
6519 assert(!state.world.navy_get_battle_from_navy_battle_participation(n));
6520
6521 state.world.navy_set_location_from_navy_location(n, p);
6522 auto ships = state.world.navy_get_navy_membership(n);
6523 if(!state.world.navy_get_is_retreating(n) && p.index() >= state.province_definitions.first_sea_province.index() && ships.begin() != ships.end()) {
6524 auto owner_nation = state.world.navy_get_controller_from_navy_control(n);
6525
6526 // look for existing battle
6527 for(auto b : state.world.province_get_naval_battle_location(p)) {
6528 if(b.get_battle() != from) {
6529 auto battle_war = b.get_battle().get_war_from_naval_battle_in_war();
6530 auto owner_role = get_role(state, battle_war, owner_nation);
6531 if(owner_role != war_role::none) {
6532 add_navy_to_battle(state, n, b.get_battle(), owner_role);
6533 return; // done -- only one battle per
6534 }
6535 }
6536 }
6537
6538 // start battle
6539 dcon::naval_battle_id gather_to_battle;
6540 dcon::war_id battle_in_war;
6541
6542 for(auto o : state.world.province_get_navy_location(p)) {
6543 if(o.get_navy() == n)
6544 continue;
6545 if(o.get_navy().get_is_retreating() || o.get_navy().get_battle_from_navy_battle_participation())
6546 continue;
6547
6548 auto other_nation = o.get_navy().get_controller_from_navy_control();
6549
6550 if(auto par = internal_find_war_between(state, owner_nation, other_nation); par.role != war_role::none) {
6551 auto new_battle = fatten(state.world, state.world.create_naval_battle());
6552 new_battle.set_war_attacker_is_attacker(par.role == war_role::attacker);
6553 new_battle.set_start_date(state.current_date);
6554 new_battle.set_war_from_naval_battle_in_war(par.w);
6555 new_battle.set_location_from_naval_battle_location(p);
6556 new_battle.set_dice_rolls(make_dice_rolls(state, uint32_t(new_battle.id.value)));
6557
6558 add_navy_to_battle(state, n, new_battle, par.role);
6559 add_navy_to_battle(state, o.get_navy(), new_battle,
6560 par.role == war_role::attacker ? war_role::defender : war_role::attacker);
6561
6562 gather_to_battle = new_battle.id;
6563 battle_in_war = par.w;
6564 break;
6565 }
6566 }
6567
6568 if(gather_to_battle) {
6569 for(auto o : state.world.province_get_navy_location(p)) {
6570 if(o.get_navy() == n)
6571 continue;
6572 if(o.get_navy().get_is_retreating() || o.get_navy().get_battle_from_navy_battle_participation())
6573 continue;
6574
6575 auto other_nation = o.get_navy().get_controller_from_navy_control();
6576
6577 if(auto role = get_role(state, battle_in_war, other_nation); role != war_role::none) {
6578 add_navy_to_battle(state, o.get_navy(), gather_to_battle, role);
6579 }
6580 }
6581 }
6582
6583 // TODO: notify on new battle
6584 }
6586
6587void update_movement(sys::state& state) {
6588 for(auto a : state.world.in_army) {
6589 auto arrival = a.get_arrival_time();
6590 assert(!arrival || arrival >= state.current_date);
6591 if(auto path = a.get_path(); arrival == state.current_date) {
6592 assert(path.size() > 0);
6593 auto dest = path.at(path.size() - 1);
6594 path.pop_back();
6595 auto from = state.world.army_get_location_from_army_location(a);
6596
6597 if(dest.index() >= state.province_definitions.first_sea_province.index()) { // sea province
6598 // check for embarkation possibility, then embark
6599 auto to_navy = find_embark_target(state, a.get_controller_from_army_control(), dest, a);
6600 if(to_navy) {
6601 a.set_location_from_army_location(dest);
6602 a.set_navy_from_army_transport(to_navy);
6603 a.set_black_flag(false);
6604 } else {
6605 path.clear();
6606 }
6607 } else { // land province
6608 if(a.get_black_flag()) {
6609 auto n = state.world.province_get_nation_from_province_ownership(dest);
6610 // Since AI and pathfinding can lead armies into unowned provinces that are completely locked by other nations,
6611 // make armies go back to home territories for black flag removal
6612 if(n == a.get_controller_from_army_control().id) {
6613 a.set_black_flag(false);
6614 }
6615 army_arrives_in_province(state, a, dest,
6616 (state.world.province_adjacency_get_type(state.world.get_province_adjacency_by_province_pair(dest, from)) &
6619 : military::crossing_type::none, dcon::land_battle_id{});
6620 a.set_navy_from_army_transport(dcon::navy_id{});
6621 } else if(province::has_access_to_province(state, a.get_controller_from_army_control(), dest)) {
6622 if(auto n = a.get_navy_from_army_transport()) {
6623 if(!n.get_battle_from_navy_battle_participation()) {
6624 army_arrives_in_province(state, a, dest, military::crossing_type::sea, dcon::land_battle_id{});
6625 a.set_navy_from_army_transport(dcon::navy_id{});
6626 } else {
6627 path.clear();
6628 }
6629 } else {
6630 auto path_bits = state.world.province_adjacency_get_type(state.world.get_province_adjacency_by_province_pair(dest, from));
6631 if((path_bits & province::border::non_adjacent_bit) != 0) { // strait crossing
6632 auto port = state.world.province_get_port_to(from);
6633 bool hostile_in_port = false;
6634 auto controller = a.get_controller_from_army_control();
6635 for(auto v : state.world.province_get_navy_location(port)) {
6636 if(military::are_at_war(state, controller, v.get_navy().get_controller_from_navy_control())) {
6637 hostile_in_port = true;
6638 break;
6639 }
6640 }
6641 if(!hostile_in_port) {
6642 army_arrives_in_province(state, a, dest, military::crossing_type::sea, dcon::land_battle_id{});
6643 } else {
6644 path.clear();
6645 }
6646 } else {
6647 army_arrives_in_province(state, a, dest,
6648 (path_bits & province::border::river_crossing_bit) != 0
6650 : military::crossing_type::none, dcon::land_battle_id{});
6651 }
6652 }
6653 } else {
6654 path.clear();
6655 }
6656 }
6657
6658 if(a.get_battle_from_army_battle_participation()) {
6659 // nothing -- movement paused
6660 } else if(path.size() > 0) {
6661 auto next_dest = path.at(path.size() - 1);
6662 a.set_arrival_time(arrival_time_to(state, a, next_dest));
6663 } else {
6664 a.set_arrival_time(sys::date{});
6665 if(a.get_is_retreating()) {
6666 a.set_is_retreating(false);
6667 army_arrives_in_province(state, a, dest,
6668 (state.world.province_adjacency_get_type(state.world.get_province_adjacency_by_province_pair(dest, from)) &
6671 : military::crossing_type::none, dcon::land_battle_id{});
6672 }
6673 if(a.get_moving_to_merge()) {
6674 a.set_moving_to_merge(false);
6675 [&]() {
6676 for(auto ar : state.world.province_get_army_location(dest)) {
6677 if(ar.get_army().get_controller_from_army_control() == a.get_controller_from_army_control() && ar.get_army() != a && !ar.get_army().get_moving_to_merge()) {
6678 auto regs = state.world.army_get_army_membership(a);
6679 while(regs.begin() != regs.end()) {
6680 (*regs.begin()).set_army(ar.get_army());
6681 }
6682 return;
6683 }
6684 }
6685 }();
6686 }
6687 if(state.world.army_get_is_rebel_hunter(a)
6688 && state.world.province_get_nation_from_province_control(dest)
6689 && state.world.nation_get_is_player_controlled(state.world.army_get_controller_from_army_control(a))
6690 && !state.world.army_get_battle_from_army_battle_participation(a)
6691 && !state.world.army_get_navy_from_army_transport(a)) {
6692
6693 military::send_rebel_hunter_to_next_province(state, a, state.world.army_get_location_from_army_location(a));
6694 }
6695 }
6696 }
6697 }
6698
6699 for(auto n : state.world.in_navy) {
6700 auto arrival = n.get_arrival_time();
6701 assert(!arrival || arrival >= state.current_date);
6702 if(auto path = n.get_path(); arrival == state.current_date) {
6703 assert(path.size() > 0);
6704 auto dest = path.at(path.size() - 1);
6705 path.pop_back();
6706
6707 if(dest.index() < state.province_definitions.first_sea_province.index()) { // land province
6708 if(province::has_naval_access_to_province(state, n.get_controller_from_navy_control(), dest)) {
6709
6710 n.set_location_from_navy_location(dest);
6711
6712 // check for whether there are troops to disembark
6713 auto attached = state.world.navy_get_army_transport(n);
6714 while(attached.begin() != attached.end()) {
6715 auto a = (*attached.begin()).get_army();
6716
6717 a.set_navy_from_army_transport(dcon::navy_id{});
6718 a.get_path().clear();
6719 a.set_arrival_time(sys::date{});
6720 auto acontroller = a.get_controller_from_army_control();
6721
6722 if(acontroller && !acontroller.get_is_player_controlled()) {
6723 auto army_dest = a.get_ai_province();
6724 a.set_location_from_army_location(dest);
6725 if(army_dest && army_dest != dest) {
6726 auto apath = province::make_land_path(state, dest, army_dest, acontroller, a);
6727 if(apath.size() > 0) {
6728 auto existing_path = a.get_path();
6729 auto new_size = uint32_t(apath.size());
6730 existing_path.resize(new_size);
6731
6732 for(uint32_t i = 0; i < new_size; ++i) {
6733 existing_path[i] = apath[i];
6734 }
6735 a.set_arrival_time(military::arrival_time_to(state, a, apath.back()));
6736 a.set_dig_in(0);
6737 auto activity = ai::army_activity(a.get_ai_activity());
6738 if(activity == ai::army_activity::transport_guard) {
6739 a.set_ai_activity(uint8_t(ai::army_activity::on_guard));
6740 } else if(activity == ai::army_activity::transport_attack) {
6741 a.set_ai_activity(uint8_t(ai::army_activity::attack_gathered));
6742 }
6743 } else {
6744 a.set_ai_activity(uint8_t(ai::army_activity::on_guard));
6745 }
6746 } else {
6747 a.set_ai_activity(uint8_t(ai::army_activity::on_guard));
6748 }
6749 }
6750 army_arrives_in_province(state, a, dest, military::crossing_type::none, dcon::land_battle_id{});
6751 }
6752 } else {
6753 path.clear();
6754 }
6755 } else { // sea province
6756
6757 navy_arrives_in_province(state, n, dest, dcon::naval_battle_id{});
6758
6759 // take embarked units along with
6760 for(auto a : state.world.navy_get_army_transport(n)) {
6761 a.get_army().set_location_from_army_location(dest);
6762 a.get_army().get_path().clear();
6763 a.get_army().set_arrival_time(sys::date{});
6764 }
6765 }
6766
6767 if(n.get_battle_from_navy_battle_participation()) {
6768 // nothing, movement paused
6769 } else if(path.size() > 0) {
6770 auto next_dest = path.at(path.size() - 1);
6771 n.set_arrival_time(arrival_time_to(state, n, next_dest));
6772 } else {
6773 n.set_arrival_time(sys::date{});
6774 if(n.get_is_retreating()) {
6775 if(dest.index() >= state.province_definitions.first_sea_province.index())
6776 navy_arrives_in_province(state, n, dest, dcon::naval_battle_id{});
6777 n.set_is_retreating(false);
6778 }
6779 if(n.get_moving_to_merge()) {
6780 n.set_moving_to_merge(false);
6781 [&]() {
6782 for(auto ar : state.world.province_get_navy_location(dest)) {
6783 if(ar.get_navy().get_controller_from_navy_control() == n.get_controller_from_navy_control() && ar.get_navy() != n && !ar.get_navy().get_moving_to_merge()) {
6784 auto regs = state.world.navy_get_navy_membership(n);
6785 while(regs.begin() != regs.end()) {
6786 (*regs.begin()).set_navy(ar.get_navy());
6787 }
6788 auto a = state.world.navy_get_army_transport(n);
6789 while(a.begin() != a.end()) {
6790 (*a.begin()).set_navy(ar.get_navy());
6791 }
6792 return;
6793 }
6794 }
6795 }();
6796 }
6797 }
6798 }
6799 }
6801
6802float fractional_distance_covered(sys::state& state, dcon::army_id a) {
6803 auto date = state.world.army_get_arrival_time(a);
6804 if(!date)
6805 return 0.0f;
6806 auto p = state.world.army_get_path(a);
6807 if(p.size() == 0)
6808 return 0.0f;
6809 if(state.world.army_get_battle_from_army_battle_participation(a))
6810 return 0.0f;
6811
6812 auto dest = *(p.end() - 1);
6813 auto full_time = arrival_time_to(state, a, dest);
6814
6815 auto difference = full_time.value - state.current_date.value;
6816 auto covered = date.value - state.current_date.value;
6817
6818 if(difference <= 0)
6819 return 1.0f;
6820
6821 return 1.0f - float(covered) / float(difference);
6822}
6823float fractional_distance_covered(sys::state& state, dcon::navy_id a) {
6824 auto date = state.world.navy_get_arrival_time(a);
6825 if(!date)
6826 return 0.0f;
6827 auto p = state.world.navy_get_path(a);
6828 if(p.size() == 0)
6829 return 0.0f;
6830 if(state.world.navy_get_battle_from_navy_battle_participation(a))
6831 return 0.0f;
6832
6833 auto dest = *(p.end() - 1);
6834 auto full_time = arrival_time_to(state, a, dest);
6835
6836 auto difference = full_time.value - state.current_date.value;
6837 auto covered = date.value - state.current_date.value;
6838
6839 if(difference <= 0)
6840 return 1.0f;
6841
6842 return 1.0f - float(covered) / float(difference);
6844
6845int32_t transport_capacity(sys::state& state, dcon::navy_id n) {
6846 int32_t total = 0;
6847 for(auto s : state.world.navy_get_navy_membership(n)) {
6848 if(state.military_definitions.unit_base_definitions[s.get_ship().get_type()].type == unit_type::transport)
6849 ++total;
6850 }
6851 return total;
6852}
6853int32_t free_transport_capacity(sys::state& state, dcon::navy_id n) {
6854 int32_t used_total = 0;
6855 for(auto a : state.world.navy_get_army_transport(n)) {
6856 auto regs = a.get_army().get_army_membership();
6857 used_total += int32_t(regs.end() - regs.begin());
6858 }
6859 return transport_capacity(state, n) - used_total;
6861
6862constexpr inline float siege_speed_mul = 1.0f / 50.0f;
6863
6864void send_rebel_hunter_to_next_province(sys::state& state, dcon::army_id ar, dcon::province_id prov) {
6865 auto a = fatten(state.world, ar);
6866 auto controller = a.get_controller_from_army_control();
6867 static std::vector<rebel::impl::prov_str> rebel_provs;
6868 rebel_provs.clear();
6869 rebel::get_hunting_targets(state, controller, rebel_provs);
6871
6872 for(auto& next_prov : rebel_provs) {
6873 if(prov == next_prov.p)
6874 continue;
6875
6876 auto path = province::make_land_path(state, prov, next_prov.p, controller, a);
6877 if(path.size() > 0) {
6878 auto existing_path = state.world.army_get_path(a);
6879
6880 auto new_size = uint32_t(path.size());
6881 existing_path.resize(new_size);
6882
6883 for(uint32_t i = new_size; i-- > 0; ) {
6884 existing_path.at(i) = path[i];
6885 }
6886
6887 state.world.army_set_arrival_time(a, military::arrival_time_to(state, a, path.back()));
6888 state.world.army_set_dig_in(a, 0);
6889
6890 break;
6891 }
6892 }
6893 if(!a.get_arrival_time()) {
6894 auto home = a.get_ai_province();
6895 if(home == prov)
6896 return;
6897
6898 auto path = province::make_land_path(state, prov, home, controller, a);
6899 if(path.size() > 0) {
6900 auto existing_path = state.world.army_get_path(a);
6901
6902 auto new_size = uint32_t(path.size());
6903 existing_path.resize(new_size);
6904
6905 for(uint32_t i = new_size; i-- > 0; ) {
6906 existing_path.at(i) = path[i];
6907 }
6908
6909 state.world.army_set_arrival_time(a, military::arrival_time_to(state, a, path.back()));
6910 state.world.army_set_dig_in(a, 0);
6911 }
6912 }
6914
6915bool siege_potential(sys::state& state, dcon::nation_id army_controller, dcon::nation_id province_controller) {
6916 bool will_siege = false;
6917 if(!army_controller) { // rebel army
6918 will_siege = bool(province_controller); // siege anything not rebel controlled
6919 } else {
6920 if(!province_controller) {
6921 will_siege = true; // siege anything rebel controlled
6922 } else if(are_at_war(state, province_controller, army_controller)) {
6923 will_siege = true;
6924 }
6925 }
6926
6927 return will_siege;
6929
6930void update_siege_progress(sys::state& state) {
6931 static auto new_nation_controller = ve::vectorizable_buffer<dcon::nation_id, dcon::province_id>(state.world.province_size());
6932 static auto new_rebel_controller = ve::vectorizable_buffer<dcon::rebel_faction_id, dcon::province_id>(state.world.province_size());
6933 province::ve_for_each_land_province(state, [&](auto ids) {
6934 new_nation_controller.set(ids, dcon::nation_id{});
6935 new_rebel_controller.set(ids, dcon::rebel_faction_id{});
6936 });
6937
6938 concurrency::parallel_for(0, state.province_definitions.first_sea_province.index(), [&](int32_t id) {
6939 dcon::province_id prov{dcon::province_id::value_base_t(id)};
6940
6941 auto controller = state.world.province_get_nation_from_province_control(prov);
6942 auto owner = state.world.province_get_nation_from_province_ownership(prov);
6943
6944 if(!owner) // skip unowned provinces
6945 return;
6946
6947 float max_siege_value = 0.0f;
6948 float strength_siege_units = 0.0f;
6949 float max_recon_value = 0.0f;
6950 float strength_recon_units = 0.0f;
6951 float total_sieging_strength = 0.0f;
6952 bool owner_involved = false;
6953 bool core_owner_involved = false;
6954
6955 dcon::army_id first_army;
6956
6957 for(auto ar : state.world.province_get_army_location(prov)) {
6958 // Only stationary, non black flagged regiments with at least 0.001 strength contribute to a siege.
6959
6960 if(ar.get_army().get_battle_from_army_battle_participation() || ar.get_army().get_black_flag() ||
6961 ar.get_army().get_navy_from_army_transport() || ar.get_army().get_arrival_time()) {
6962
6963 // skip -- blackflag or embarked or moving or fighting
6964 } else {
6965 auto army_controller = ar.get_army().get_controller_from_army_control();
6966
6967 if(siege_potential(state, army_controller, controller)) {
6968 if(!first_army)
6969 first_army = ar.get_army();
6970
6971 auto army_stats = army_controller ? army_controller : ar.get_army().get_army_rebel_control().get_controller().get_ruler_from_rebellion_within();
6972
6973 owner_involved = owner_involved || owner == army_controller;
6974 core_owner_involved =
6975 core_owner_involved || bool(state.world.get_core_by_prov_tag_key(prov, state.world.nation_get_identity_from_identity_holder(army_controller)));
6976
6977 for(auto r : ar.get_army().get_army_membership()) {
6978 auto reg_str = r.get_regiment().get_strength();
6979 if(reg_str > 0.001f) {
6980 auto type = r.get_regiment().get_type();
6981 auto& stats = state.world.nation_get_unit_stats(army_stats, type);
6982
6983 total_sieging_strength += reg_str;
6984
6985 if(stats.siege_or_torpedo_attack > 0.0f) {
6986 strength_siege_units += reg_str;
6987 max_siege_value = std::max(max_siege_value, stats.siege_or_torpedo_attack);
6988 }
6989 if(stats.reconnaissance_or_fire_range > 0.0f) {
6990 strength_recon_units += reg_str;
6991 max_recon_value = std::max(max_recon_value, stats.reconnaissance_or_fire_range);
6992 }
6993 }
6994 }
6995 }
6996 }
6997 }
6998
6999 if(total_sieging_strength == 0.0f) {
7000 // Garrison recovers at 10% per day when not being sieged (to 100%)
7001
7002 auto local_battles = state.world.province_get_land_battle_location(prov);
7003 if(local_battles.begin() != local_battles.end()) {
7004 // ongoing battle: do nothing
7005 } else {
7006 auto& progress = state.world.province_get_siege_progress(prov);
7007 progress = std::max(0.0f, progress - 0.1f);
7008 }
7009 } else {
7010 assert(bool(first_army));
7011
7012 /*
7013 We find the effective level of the fort by subtracting: (rounding this value down to to the nearest integer)
7014 greatest-siege-value-present x
7015 ((the ratio of the strength of regiments with siege present to the total strength of all regiments) ^
7016 define:ENGINEER_UNIT_RATIO) / define:ENGINEER_UNIT_RATIO, reducing it to a minimum of 0.
7017 */
7018
7019 int32_t effective_fort_level =
7020 std::clamp(state.world.province_get_building_level(prov, uint8_t(economy::province_building_type::fort)) -
7021 int32_t(max_siege_value *
7022 std::min(strength_siege_units / total_sieging_strength, state.defines.engineer_unit_ratio) /
7023 state.defines.engineer_unit_ratio),
7024 0, 9);
7025
7026 /*
7027 We calculate the siege speed modifier as: 1 + define:RECON_SIEGE_EFFECT x greatest-reconnaissance-value-present x ((the
7028 ratio of the strength of regiments with reconnaissance present to the total strength of all regiments) ^
7029 define:RECON_UNIT_RATIO) / define:RECON_UNIT_RATIO.
7030 */
7031
7032 float siege_speed_modifier =
7033 1.0f + state.defines.recon_siege_effect * max_recon_value *
7034 std::min(strength_recon_units / total_sieging_strength, state.defines.recon_unit_ratio) /
7035 state.defines.recon_unit_ratio;
7036
7037 /*
7038 We calculate the modifier for number of brigades: first we get the "number of brigades" as total-strength-of-regiments x
7039 1000 / define:POP_SIZE_PER_REGIMENT, and capping it to at most define:SIEGE_BRIGADES_MAX. Then we calculate the bonus as
7040 (number-of-brigades - define:SIEGE_BRIGADES_MIN) x define:SIEGE_BRIGADES_BONUS if number-of-brigades is greater the minimum,
7041 and as number-of-brigades / define:SIEGE_BRIGADES_MIN otherwise.
7042 */
7043
7044 float num_brigades =
7045 std::min(state.defines.siege_brigades_max, total_sieging_strength * 1000.0f / state.defines.pop_size_per_regiment);
7046 float num_brigades_modifier =
7047 num_brigades > state.defines.siege_brigades_min
7048 ? 1.0f + (num_brigades - state.defines.siege_brigades_min) * state.defines.siege_brigades_bonus
7049 : num_brigades / state.defines.siege_brigades_min;
7050
7051 /*
7052 Finally, the amount subtracted from the garrison each day is:
7053 siege-speed-modifier x number-of-brigades-modifier x Progress-Table\[random-int-from-0-to-9\] x (1.25 if the owner is
7054 sieging it back) x (1.1 if the sieger is not the owner but does have a core) / Siege-Table\[effective-fort-level\]
7055 */
7056
7057 static constexpr float siege_table[] = {0.25f, 1.0f, 2.0f, 2.8f, 3.4f, 3.8f, 4.2f, 4.5f, 4.8f, 5.0f, 5.2f};
7058 static constexpr float progress_table[] = {0.0f, 0.2f, 0.5f, 0.75f, 0.75f, 1, 1.1f, 1.1f, 1.25f, 1.25f};
7059
7060 float added_progress = siege_speed_modifier * num_brigades_modifier *
7061 progress_table[rng::get_random(state, uint32_t(prov.value)) % 10] *
7062 (owner_involved ? 1.25f : (core_owner_involved ? 1.1f : 1.0f)) / siege_table[effective_fort_level];
7063
7064 auto& progress = state.world.province_get_siege_progress(prov);
7065 progress += siege_speed_mul * added_progress;
7066
7067 if(progress >= 1.0f) {
7068 progress = 0.0f;
7069
7070 /*
7071 The garrison returns to 100% immediately after the siege is complete and the controller changes. If your siege returns a
7072 province to its owner's control without the owner participating, you get +2.5 relations with the owner.
7073 */
7074
7075 // if siege won from rebels : treat as rebel defeat
7076 auto old_rf = state.world.province_get_rebel_faction_from_province_rebel_control(prov);
7077 if(old_rf) {
7078 for(auto pop : state.world.province_get_pop_location(prov)) {
7079 //if(pop.get_pop().get_rebel_faction_from_pop_rebellion_membership() == old_rf) {
7080 auto mil = pop_demographics::get_militancy(state, pop.get_pop()) / state.defines.reduction_after_defeat;
7081 pop_demographics::set_militancy(state, pop.get_pop().id, mil);
7082 //}
7083 }
7084 }
7085
7086 state.world.province_set_former_controller(prov, controller);
7087 state.world.province_set_former_rebel_controller(prov, old_rf);
7088
7089 auto new_controller = state.world.army_get_controller_from_army_control(first_army);
7090 auto rebel_controller = state.world.army_get_controller_from_army_rebel_control(first_army);
7091
7092 if((!new_controller && !rebel_controller) || (new_controller && !are_at_war(state, new_controller, owner))) {
7093 new_controller = owner;
7094 }
7095
7096 new_nation_controller.set(prov, new_controller);
7097 new_rebel_controller.set(prov, rebel_controller);
7098 }
7099 }
7100 });
7101
7102 province::for_each_land_province(state, [&](dcon::province_id prov) {
7103 if(auto nc = new_nation_controller.get(prov); nc) {
7104 province::set_province_controller(state, prov, nc);
7105 eject_ships(state, prov);
7106
7107 auto cc = state.world.province_get_nation_from_province_control(prov);
7108 auto oc = state.world.province_get_former_controller(prov);
7109
7110 if(cc || oc) {
7111 notification::post(state, notification::message{
7112 [prov, cc, oc,
7113 crc = state.world.province_get_rebel_faction_from_province_rebel_control(prov),
7114 orc = state.world.province_get_former_rebel_controller(prov)](sys::state& state, text::layout_base& contents) {
7115
7116 text::add_line(state, contents, "msg_siegeover_1", text::variable_type::x, prov);
7117 if(oc)
7118 text::add_line(state, contents, "msg_siegeover_2", text::variable_type::x, oc);
7119 else
7120 text::add_line(state, contents, "msg_siegeover_3", text::variable_type::x, state.world.rebel_type_get_title(state.world.rebel_faction_get_type(orc)));
7121 if(cc)
7122 text::add_line(state, contents, "msg_siegeover_4", text::variable_type::x, cc);
7123 else
7124 text::add_line(state, contents, "msg_siegeover_5", text::variable_type::x, state.world.rebel_type_get_title(state.world.rebel_faction_get_type(crc)));
7125 },
7126 "msg_siegeover_title",
7127 cc, oc, dcon::nation_id{},
7128 sys::message_base_type::siegeover
7129 });
7130 }
7131
7132 for(auto ar : state.world.province_get_army_location(prov)) {
7133 auto a = ar.get_army();
7134
7135 if(a.get_is_rebel_hunter()
7136 && a.get_controller_from_army_control().get_is_player_controlled()
7137 && !a.get_battle_from_army_battle_participation()
7138 && !a.get_navy_from_army_transport()
7139 && !a.get_arrival_time()) {
7140
7141 send_rebel_hunter_to_next_province(state, a, prov);
7142
7143 }
7144 }
7145
7146 /*
7147 TODO: When a province controller changes as the result of a siege, and it does not go back to the owner a random,
7148 `on_siege_win` event is fired, subject to the conditions of the events being met.
7149 */
7150 // is controler != owner ...
7151 // event::fire_fixed_event(state, );
7152 }
7153 if(auto nr = new_rebel_controller.get(prov); nr) {
7154 province::set_province_controller(state, prov, nr);
7155 eject_ships(state, prov);
7156
7157 auto cc = state.world.province_get_nation_from_province_control(prov);
7158 auto oc = state.world.province_get_former_controller(prov);
7159 auto within = state.world.rebel_faction_get_ruler_from_rebellion_within(nr);
7160 auto t = state.world.rebel_faction_get_type(nr);
7161 if(trigger::evaluate(state, state.world.rebel_type_get_siege_won_trigger(t), trigger::to_generic(prov), trigger::to_generic(prov), trigger::to_generic(nr))) {
7162 effect::execute(state, state.world.rebel_type_get_siege_won_effect(t), trigger::to_generic(prov), trigger::to_generic(prov), trigger::to_generic(nr), uint32_t(state.current_date.value), uint32_t(within.index() ^ (nr.index() << 4)));
7163 }
7164 }
7165 });
7167
7168void update_blackflag_status(sys::state& state, dcon::province_id p) {
7169 for(auto ar : state.world.province_get_army_location(p)) {
7170 if(!ar.get_army().get_battle_from_army_battle_participation() && !ar.get_army().get_navy_from_army_transport()) {
7171 auto controller = ar.get_army().get_controller_from_army_control();
7172 ar.get_army().set_black_flag(!province::has_access_to_province(state, controller, p));
7173 }
7174 }
7176
7177void eject_ships(sys::state& state, dcon::province_id p) {
7178 auto sea_zone = state.world.province_get_port_to(p);
7179
7180 if(!sea_zone)
7181 return;
7182
7183 static std::vector<dcon::navy_id> to_eject;
7184 to_eject.clear();
7185
7186 for(auto n : state.world.province_get_navy_location(p)) {
7187 if(!province::has_naval_access_to_province(state, n.get_navy().get_controller_from_navy_control(), p)) {
7188 to_eject.push_back(n.get_navy().id);
7189 }
7190 }
7191 for(auto n : to_eject) {
7192 navy_arrives_in_province(state, n, sea_zone, dcon::naval_battle_id{});
7193
7194 for(auto a : state.world.navy_get_army_transport(n)) {
7195 a.get_army().set_location_from_army_location(sea_zone);
7196 a.get_army().get_path().clear();
7197 a.get_army().set_arrival_time(sys::date{});
7198 }
7199 }
7201
7202void increase_dig_in(sys::state& state) {
7203 if(state.current_date.value % int32_t(state.defines.dig_in_increase_each_days) == 0) {
7204 for(auto ar : state.world.in_army) {
7205 if(ar.get_is_retreating() || ar.get_black_flag() || bool(ar.get_battle_from_army_battle_participation()) ||
7206 bool(ar.get_navy_from_army_transport()) || bool(ar.get_arrival_time())) {
7207
7208 continue;
7209 }
7210 auto& current_dig_in = ar.get_dig_in();
7211 if(current_dig_in <
7212 int32_t(ar.get_controller_from_army_control().get_modifier_values(sys::national_mod_offsets::dig_in_cap))) {
7213 ++current_dig_in;
7214 }
7215 }
7216 }
7218
7219economy::commodity_set get_required_supply(sys::state& state, dcon::nation_id owner, dcon::army_id army) {
7220 uint32_t total_commodities = state.world.commodity_size();
7221
7222 economy::commodity_set commodities;
7223 for (uint32_t i = 0; i < economy::commodity_set::set_size; ++i) {
7224 commodities.commodity_amounts[i] = 0.0f;
7225 }
7226
7227 for(auto r : state.world.army_get_army_membership(army)) {
7228 auto reg = fatten(state.world, r);
7229 auto type = state.world.regiment_get_type(r.get_regiment());
7230
7231 auto o_sc_mod = std::max(0.01f, state.world.nation_get_modifier_values(owner, sys::national_mod_offsets::supply_consumption) + 1.0f);
7232 auto& supply_cost = state.military_definitions.unit_base_definitions[type].supply_cost;
7233 for(uint32_t i = 0; i < economy::commodity_set::set_size; ++i) {
7234 if(supply_cost.commodity_type[i]) {
7235 commodities.commodity_amounts[i] += supply_cost.commodity_amounts[i] * state.world.nation_get_unit_stats(owner, type).supply_consumption * o_sc_mod;
7236 commodities.commodity_type[i] = supply_cost.commodity_type[i];
7237 } else {
7238 break;
7239 }
7240 }
7241
7242 }
7243
7244 return commodities;
7246
7247economy::commodity_set get_required_supply(sys::state& state, dcon::nation_id owner, dcon::navy_id navy) {
7248 // supply amount = type_consumption * (2 - admin_eff)*[(type_consumption_mod^0.01)*land_spending]
7249 float supply_amount = .0f;
7250 int32_t amount_of_units = 0;
7251
7252 economy::commodity_set commodities;
7253 for(uint32_t i = 0; i < economy::commodity_set::set_size; ++i) {
7254 commodities.commodity_amounts[i] = 0.0f;
7255 }
7256
7257 for(auto sh : state.world.navy_get_navy_membership(navy)) {
7258 auto shp = fatten(state.world, sh.get_ship());
7259 auto type = state.world.ship_get_type(sh.get_ship());
7260
7261 if(owner) {
7262 auto o_sc_mod = std::max(0.01f, state.world.nation_get_modifier_values(owner, sys::national_mod_offsets::supply_consumption) + 1.0f);
7263 auto& supply_cost = state.military_definitions.unit_base_definitions[type].supply_cost;
7264 for(uint32_t i = 0; i < economy::commodity_set::set_size; ++i) {
7265 if(supply_cost.commodity_type[i]) {
7266 commodities.commodity_amounts[i] +=
7267 supply_cost.commodity_amounts[i] * state.world.nation_get_unit_stats(owner, type).supply_consumption *
7268 o_sc_mod;
7269 commodities.commodity_type[i] = supply_cost.commodity_type[i];
7270 } else {
7271 break;
7272 }
7273 }
7274 }
7275 };
7276
7277 return commodities;
7279
7280void recover_org(sys::state& state) {
7281 /*
7282 - Units that are not in combat and not embarked recover organization daily at: (national-organization-regeneration-modifier
7283 + morale-from-tech + leader-morale-trait + 1) x the-unit's-supply-factor / 5 up to the maximum organization possible
7284 for the unit times (0.25 + 0.75 x effective land or naval spending).
7285 - Additionally, the prestige of the leader factors in morale as unit-morale
7286 + (leader-prestige x defines:LEADER_PRESTIGE_TO_MORALE_FACTOR).
7287 - Similarly, unit-max-org + (leader-prestige x defines:LEADER_PRESTIGE_TO_MAX_ORG_FACTOR) allows for maximum org.
7288 */
7289
7290 for(auto ar : state.world.in_army) {
7291 if(ar.get_army_battle_participation().get_battle() || ar.get_navy_from_army_transport())
7292 continue;
7293
7294 auto in_nation = ar.get_controller_from_army_control();
7295 auto tech_nation = in_nation ? in_nation : ar.get_controller_from_army_rebel_control().get_ruler_from_rebellion_within();
7296
7297 auto leader = ar.get_general_from_army_leadership();
7298 auto regen_mod = tech_nation.get_modifier_values(sys::national_mod_offsets::org_regain)
7299 + leader.get_personality().get_morale() + leader.get_background().get_morale() + 1.0f
7300 + leader.get_prestige() * state.defines.leader_prestige_to_max_org_factor;
7301 auto spending_level = (in_nation ? in_nation.get_effective_land_spending() : 1.0f);
7302 auto modified_regen = regen_mod * spending_level / 150.f;
7303 auto max_org = 0.25f + 0.75f * spending_level;
7304 for(auto reg : ar.get_army_membership()) {
7305 auto c_org = reg.get_regiment().get_org();
7306 reg.get_regiment().set_org(std::min(c_org + modified_regen, max_org));
7307 }
7308 }
7309
7310 for(auto ar : state.world.in_navy) {
7311 if(ar.get_navy_battle_participation().get_battle())
7312 continue;
7313
7314 auto in_nation = ar.get_controller_from_navy_control();
7315
7316 auto leader = ar.get_admiral_from_navy_leadership();
7317 auto regen_mod = in_nation.get_modifier_values(sys::national_mod_offsets::org_regain)
7318 + leader.get_personality().get_morale() + leader.get_background().get_morale() + 1.0f
7319 + leader.get_prestige() * state.defines.leader_prestige_to_morale_factor;
7320 float oversize_amount =
7321 in_nation.get_naval_supply_points() > 0
7322 ? std::min(float(in_nation.get_used_naval_supply_points()) / float(in_nation.get_naval_supply_points()), 1.75f)
7323 : 1.75f;
7324 float over_size_penalty = oversize_amount > 1.0f ? 2.0f - oversize_amount : 1.0f;
7325 auto spending_level = in_nation.get_effective_naval_spending() * over_size_penalty;
7326 auto modified_regen = regen_mod * spending_level / 150.0f;
7327 auto max_org = 0.25f + 0.75f * spending_level;
7328 for(auto reg : ar.get_navy_membership()) {
7329 auto c_org = reg.get_ship().get_org();
7330 reg.get_ship().set_org(std::min(c_org + modified_regen, max_org));
7331 }
7332 }
7333}
7335// Just a wrapper for regiment_get_strength and ship_get_strength where unit is unknown
7336float unit_get_strength(sys::state& state, dcon::regiment_id regiment_id) {
7337 return state.world.regiment_get_strength(regiment_id);
7339// Just a wrapper for regiment_get_strength and ship_get_strength where unit is unknown
7340float unit_get_strength(sys::state& state, dcon::ship_id ship_id) {
7341 return state.world.ship_get_strength(ship_id);
7342}
7343/* === Army reinforcement === */
7344// Calculates max reinforcement for units in the army
7345float calculate_army_combined_reinforce(sys::state& state, dcon::army_id a) {
7346 auto ar = fatten(state.world, a);
7347 if(ar.get_battle_from_army_battle_participation() || ar.get_navy_from_army_transport() || ar.get_is_retreating())
7348 return 0.0f;
7349
7350 auto in_nation = ar.get_controller_from_army_control();
7351 auto tech_nation = in_nation ? in_nation : ar.get_controller_from_army_rebel_control().get_ruler_from_rebellion_within();
7352
7353 auto spending_level = (in_nation ? in_nation.get_effective_land_spending() : 1.0f);
7354
7355 float location_modifier = 1.0f;
7356 if(ar.get_location_from_army_location().get_nation_from_province_ownership() == in_nation) {
7357 location_modifier = 2.0f;
7358 } else if(ar.get_location_from_army_location().get_nation_from_province_control() == in_nation) {
7359 location_modifier = 1.0f;
7360 } else {
7361 location_modifier = 0.1f;
7362 }
7363
7364 auto combined = state.defines.reinforce_speed * spending_level * location_modifier *
7365 (1.0f + tech_nation.get_modifier_values(sys::national_mod_offsets::reinforce_speed)) *
7366 (1.0f + tech_nation.get_modifier_values(sys::national_mod_offsets::reinforce_rate));
7367
7368 return combined;
7369}
7370
7371// Calculates reinforcement for a particular regiment
7372// Combined = max reinforcement for units in the army from calculate_army_combined_reinforce
7373float regiment_calculate_reinforcement(sys::state& state, dcon::regiment_fat_id reg, float combined) {
7374 auto pop = reg.get_pop_from_regiment_source();
7375 auto pop_size = pop.get_size();
7376 auto limit_fraction = std::max(state.defines.alice_full_reinforce, std::min(1.0f, pop_size / state.defines.pop_size_per_regiment));
7377 auto curstr = reg.get_strength();
7378 auto newstr = std::min(curstr + combined, limit_fraction);
7379
7380 return newstr - curstr;
7381}
7383// Calculates reinforcement for a particular unit from scratch, unit type is unknown
7384float unit_calculate_reinforcement(sys::state& state, dcon::regiment_id reg_id) {
7385 auto reg = dcon::fatten(state.world, reg_id);
7386 auto ar = reg.get_army_from_army_membership();
7387 auto combined = calculate_army_combined_reinforce(state, ar);
7388
7389 return regiment_calculate_reinforcement(state, reg, combined);
7391
7392void reinforce_regiments(sys::state& state) {
7393 /*
7394 A unit that is not retreating, not embarked, not in combat is reinforced (has its strength increased) by:
7395define:REINFORCE_SPEED x (technology-reinforcement-modifier + 1.0) x (2 if in owned province, 0.1 in an unowned port province, 1
7396in a controlled province, 0.5 if in a province adjacent to a province with military access, 0.25 in a hostile, unblockaded port,
7397and 0.1 in any other hostile province) x (national-reinforce-speed-modifier + 1) x army-supplies x (number of actual regiments /
7398max possible regiments (feels like a bug to me) or 0.5 if mobilized)
7399 */
7400
7401 for(auto ar : state.world.in_army) {
7402 if(ar.get_battle_from_army_battle_participation() || ar.get_navy_from_army_transport() || ar.get_is_retreating())
7403 continue;
7404
7405 auto in_nation = ar.get_controller_from_army_control();
7406 auto combined = calculate_army_combined_reinforce(state, ar);
7407
7408 for(auto reg : ar.get_army_membership()) {
7409 auto reinforcement = regiment_calculate_reinforcement(state, reg.get_regiment(), combined);
7410 reg.get_regiment().get_strength() += reinforcement;
7411 adjust_regiment_experience(state, in_nation.id, reg.get_regiment(), reinforcement * 5.f * state.defines.exp_gain_div);
7412 }
7413 }
7414}
7415
7416/* === Navy reinforcement === */
7417// Calculates max reinforcement for units in the navy
7418float calculate_navy_combined_reinforce(sys::state& state, dcon::navy_id navy_id) {
7419 auto n = dcon::fatten(state.world, navy_id);
7420 auto in_nation = n.get_controller_from_navy_control();
7421
7422 float oversize_amount =
7423 in_nation.get_naval_supply_points() > 0
7424 ? std::min(float(in_nation.get_used_naval_supply_points()) / float(in_nation.get_naval_supply_points()), 1.75f)
7425 : 1.75f;
7426 float over_size_penalty = oversize_amount > 1.0f ? 2.0f - oversize_amount : 1.0f;
7427 auto spending_level = in_nation.get_effective_naval_spending() * over_size_penalty;
7428
7429 auto rr_mod = n.get_location_from_navy_location().get_modifier_values(sys::provincial_mod_offsets::local_repair) + 1.0f;
7430 auto reinf_mod = in_nation.get_modifier_values(sys::national_mod_offsets::reinforce_speed) + 1.0f;
7431 auto combined = rr_mod * reinf_mod * spending_level;
7432
7433 return combined;
7435// Calculates reinforcement for a particular unit from scratch, unit type is unknown
7436float unit_calculate_reinforcement(sys::state& state, dcon::ship_id ship_id) {
7437 auto combined = calculate_navy_combined_reinforce(state, state.world.ship_get_navy_from_navy_membership(ship_id));
7438 auto curstr = state.world.ship_get_strength(ship_id);
7439 auto newstr = std::min(curstr + combined, 1.0f);
7440 return newstr - curstr;
7441}
7442
7443// Calculates reinforcement for a particular ship
7444// Combined = max reinforcement for units in the navy from calculate_navy_combined_reinforce
7445float ship_calculate_reinforcement(sys::state& state, dcon::ship_id ship_id, float combined) {
7446 auto curstr = state.world.ship_get_strength(ship_id);
7447 auto newstr = std::min(curstr + combined, 1.0f);
7448 return newstr - curstr;
7450
7451void repair_ships(sys::state& state) {
7452 /*
7453 A ship that is docked at a naval base is repaired (has its strength increase) by:
7454maximum-strength x (technology-repair-rate + provincial-modifier-to-repair-rate + 1) x ship-supplies x
7455(national-reinforce-speed-modifier + 1) x navy-supplies
7456 */
7457 for(auto n : state.world.in_navy) {
7458 auto nb_level = n.get_location_from_navy_location().get_building_level(uint8_t(economy::province_building_type::naval_base));
7459 if(!n.get_arrival_time() && nb_level > 0) {
7460 auto in_nation = n.get_controller_from_navy_control();
7461 auto combined = calculate_navy_combined_reinforce(state, n);
7462
7463 for(auto reg : n.get_navy_membership()) {
7464 auto ship = reg.get_ship();
7465 auto reinforcement = ship_calculate_reinforcement(state, ship, combined);
7466 ship.get_strength() += reinforcement;
7467 adjust_ship_experience(state, in_nation.id, reg.get_ship(), std::min(0.f, reinforcement * 5.f * state.defines.exp_gain_div));
7468 }
7469 }
7470 }
7471}
7473/* === Mobilization === */
7474void start_mobilization(sys::state& state, dcon::nation_id n) {
7475 if(state.world.nation_get_is_mobilized(n))
7476 return;
7477
7478 state.world.nation_set_is_mobilized(n, true);
7479 /*
7480 At most, national-mobilization-impact-modifier x (define:MIN_MOBILIZE_LIMIT v nation's-number-of-regiments regiments may be
7481 created by mobilization).
7482 */
7483 auto real_regs = std::max(int32_t(state.world.nation_get_active_regiments(n)), int32_t(state.defines.min_mobilize_limit));
7484 state.world.nation_set_mobilization_remaining(n,
7485 uint16_t(real_regs * state.world.nation_get_modifier_values(n, sys::national_mod_offsets::mobilization_impact)));
7486
7487 auto schedule_array = state.world.nation_get_mobilization_schedule(n);
7488 schedule_array.clear();
7489
7490 for(auto pr : state.world.nation_get_province_ownership(n)) {
7491 if(pr.get_province().get_is_colonial())
7492 continue;
7493 if(pr.get_province().get_nation_from_province_control() != n)
7494 continue;
7495 if(mobilized_regiments_possible_from_province(state, pr.get_province()) <= 0)
7496 continue;
7497
7498 schedule_array.push_back(mobilization_order{ sys::date{}, pr.get_province().id });
7499 }
7500
7501 std::sort(schedule_array.begin(), schedule_array.end(),
7502 [&, cap = state.world.nation_get_capital(n)](mobilization_order const& a, mobilization_order const& b) {
7503 auto a_dist = province::direct_distance(state, a.where, cap);
7504 auto b_dist = province::direct_distance(state, b.where, cap);
7505 if(a_dist != b_dist)
7506 return a_dist > b_dist;
7507 return a.where.value < b.where.value;
7508 });
7509
7510 int32_t delay = 0;
7511
7512 for(uint32_t count = schedule_array.size(); count-- > 0;) {
7513 /*
7514 Province by province, mobilization advances by define:MOBILIZATION_SPEED_BASE x (1 + define:MOBILIZATION_SPEED_RAILS_MULT x
7515 average-railroad-level-in-state / 5) until it reaches 1
7516 */
7517 auto province_speed = state.defines.mobilization_speed_base *
7518 float(1.0f + state.defines.mobilization_speed_rails_mult *
7519 (state.world.province_get_building_level(schedule_array[count].where, uint8_t(economy::province_building_type::railroad))) / 5.0f);
7520 auto days = std::max(1, int32_t(1.0f / province_speed));
7521 delay += days;
7522 schedule_array[count].when = state.current_date + delay;
7523 }
7524
7525 /*
7526 Mobilizing increases crisis tension by define:CRISIS_TEMPERATURE_ON_MOBILIZE
7527 */
7528 if(state.current_crisis_state == sys::crisis_state::heating_up) {
7529 for(auto& par : state.crisis_participants) {
7530 if(!par.id)
7531 break;
7532 if(par.id == n)
7533 state.crisis_temperature += state.defines.crisis_temperature_on_mobilize;
7534 }
7535 }
7536
7537 notification::post(state, notification::message{ [n = n](sys::state& state, text::layout_base& contents) {
7538 text::add_line(state, contents, "msg_mobilize_start_1", text::variable_type::x, n);
7539 },
7540 "msg_mobilize_start_title",
7541 n, dcon::nation_id{}, dcon::nation_id{},
7543 });
7544}
7545void end_mobilization(sys::state& state, dcon::nation_id n) {
7546 if(!state.world.nation_get_is_mobilized(n))
7547 return;
7548
7549 state.world.nation_set_is_mobilized(n, false);
7550 state.world.nation_set_mobilization_remaining(n, 0);
7551 auto schedule_array = state.world.nation_get_mobilization_schedule(n);
7552 schedule_array.clear();
7553
7554 for(auto ar : state.world.nation_get_army_control(n)) {
7555 for(auto rg : ar.get_army().get_army_membership()) {
7556 auto pop = rg.get_regiment().get_pop_from_regiment_source();
7557 if(!pop || pop.get_poptype() != state.culture_definitions.soldiers) {
7558 rg.get_regiment().set_strength(0.0f);
7559 rg.get_regiment().set_pop_from_regiment_source(dcon::pop_id{});
7560 }
7561 }
7562 }
7563
7565 text::add_line(state, contents, "msg_mobilize_end_1", text::variable_type::x, n);
7566 },
7567 "msg_mobilize_end_title",
7568 n, dcon::nation_id{}, dcon::nation_id{},
7570 });
7571}
7572void advance_mobilizations(sys::state& state) {
7573 for(auto n : state.world.in_nation) {
7574 auto& to_mobilize = n.get_mobilization_remaining();
7575 if(to_mobilize > 0) {
7576 auto schedule = n.get_mobilization_schedule();
7577 auto s_size = schedule.size();
7578 if(s_size > 0) {
7579 auto back = schedule[s_size - 1];
7580 if(state.current_date == back.when) {
7581 schedule.pop_back();
7582 auto mob_infantry = military::get_best_infantry(state, n);
7583
7584 // mobilize the province
7585
7586 if(state.world.province_get_nation_from_province_control(back.where) ==
7587 state.world.province_get_nation_from_province_ownership(back.where)) { // only if un occupied
7588 /*
7589 In those provinces, mobilized regiments come from non-soldier, non-slave, poor-strata pops with a culture that is
7590 either the primary culture of the nation or an accepted culture.
7591 */
7592 for(auto pop : state.world.province_get_pop_location(back.where)) {
7593 if(pop_eligible_for_mobilization(state, pop.get_pop())) {
7594 /*
7595 The number of regiments these pops can provide is determined by pop-size x mobilization-size /
7596 define:POP_SIZE_PER_REGIMENT.
7597 */
7598 auto available = int32_t(pop.get_pop().get_size() * mobilization_size(state, n) / state.defines.pop_size_per_regiment);
7599 if(available > 0) {
7600
7601 bool army_is_new = false;
7602
7603 auto a = [&]() {
7604 for(auto ar : state.world.province_get_army_location(back.where)) {
7605 if(ar.get_army().get_controller_from_army_control() == n)
7606 return ar.get_army().id;
7607 }
7608 auto new_army = fatten(state.world, state.world.create_army());
7609 new_army.set_controller_from_army_control(n);
7610 new_army.set_is_ai_controlled(n.get_mobilized_is_ai_controlled()); //toggle
7611
7612 army_is_new = true;
7613 return new_army.id;
7614 }();
7615
7616 while(available > 0 && to_mobilize > 0 && mob_infantry) {
7617 auto new_reg = military::create_new_regiment(state, dcon::nation_id{}, mob_infantry);
7618 state.world.regiment_set_org(new_reg, 0.1f);
7619 state.world.regiment_set_experience(new_reg, std::clamp(state.world.nation_get_modifier_values(n, sys::national_mod_offsets::land_unit_start_experience), 0.f, 1.f));
7620 state.world.try_create_army_membership(new_reg, a);
7621 auto p = pop.get_pop();
7622 assert(p);
7623 state.world.try_create_regiment_source(new_reg, p);
7624
7625 --available;
7626 --to_mobilize;
7627 }
7628 if(army_is_new) {
7629 military::army_arrives_in_province(state, a, back.where, military::crossing_type::none, dcon::land_battle_id{});
7630 military::move_land_to_merge(state, n, a, back.where, dcon::province_id{});
7631 }
7632 }
7633 }
7634
7635 if(to_mobilize == 0)
7636 break;
7637 }
7638 }
7639 }
7640 } else {
7641 to_mobilize = 0;
7642 }
7643 }
7644 }
7646
7647bool can_retreat_from_battle(sys::state& state, dcon::naval_battle_id battle) {
7648 return (state.world.naval_battle_get_start_date(battle) + days_before_retreat < state.current_date);
7649}
7650bool can_retreat_from_battle(sys::state& state, dcon::land_battle_id battle) {
7651 return (state.world.land_battle_get_start_date(battle) + days_before_retreat < state.current_date);
7653
7654bool state_claimed_in_war(sys::state& state, dcon::war_id w, dcon::nation_id from, dcon::nation_id target, dcon::state_definition_id cb_state) {
7655 for(auto wg : state.world.war_get_wargoals_attached(w)) {
7656 if(wg.get_wargoal().get_target_nation() == target) {
7657 if(auto bits = wg.get_wargoal().get_type().get_type_bits(); (bits & (cb_flag::po_transfer_provinces | cb_flag::po_demand_state)) != 0) {
7658 if((bits & cb_flag::all_allowed_states) != 0) {
7659 auto state_filter = wg.get_wargoal().get_type().get_allowed_states();
7660
7661 if((bits & cb_flag::po_transfer_provinces) != 0) {
7662 auto holder = state.world.national_identity_get_nation_from_identity_holder(wg.get_wargoal().get_associated_tag());
7663
7664 if(state_filter) {
7665 for(auto si : state.world.nation_get_state_ownership(target)) {
7666 if(si.get_state().get_definition() == cb_state && trigger::evaluate(state, state_filter, trigger::to_generic(si.get_state().id), trigger::to_generic(from), trigger::to_generic(holder))) {
7667 return true;
7668 }
7669 }
7670 }
7671 } else if((bits & cb_flag::po_demand_state) != 0) {
7672
7673 if(state_filter) {
7674 for(auto si : state.world.nation_get_state_ownership(target)) {
7675 if(si.get_state().get_definition() == cb_state && trigger::evaluate(state, state_filter, trigger::to_generic(si.get_state().id), trigger::to_generic(from), trigger::to_generic(from))) {
7676 return true;
7677 }
7678 }
7679 }
7680 }
7681 } else {
7682 if(wg.get_wargoal().get_associated_state() == cb_state)
7683 return true;
7684 }
7685 }
7686 if((wg.get_wargoal().get_type().get_type_bits() & cb_flag::po_annex) != 0) {
7687 return true;
7688 }
7689 }
7690 }
7691
7692 return false;
7694
7695bool war_goal_would_be_duplicate(sys::state& state, dcon::nation_id source, dcon::war_id w, dcon::nation_id target, dcon::cb_type_id cb_type, dcon::state_definition_id cb_state, dcon::national_identity_id cb_tag, dcon::nation_id cb_secondary_nation) {
7696
7697 if(cb_state) { // ensure that the state will not be annexed, transferred, or liberated
7698 if(state_claimed_in_war(state, w, source, target, cb_state))
7699 return true;
7700 }
7701
7702 // ensure no exact duplicate
7703 for(auto wg : state.world.war_get_wargoals_attached(w)) {
7704 if(wg.get_wargoal().get_type() == cb_type && wg.get_wargoal().get_associated_state() == cb_state && wg.get_wargoal().get_associated_tag() == cb_tag && wg.get_wargoal().get_secondary_nation() == cb_secondary_nation && wg.get_wargoal().get_target_nation() == target) {
7705 return true;
7706 }
7707 }
7708
7709 // annexing a state... but we already added for annexing the whole nation
7710 if((state.world.cb_type_get_type_bits(cb_type) & cb_flag::po_demand_state) != 0) {
7711 for(auto wg : state.world.war_get_wargoals_attached(w)) {
7712 if((wg.get_wargoal().get_type().get_type_bits() & cb_flag::po_annex) != 0 && wg.get_wargoal().get_target_nation() == target) {
7713 return true;
7714 }
7715 }
7716 }
7717
7718 // if all applicable states: ensure that at least one state affected will not be transferred, annexed, or liberated
7719 auto bits = state.world.cb_type_get_type_bits(cb_type);
7720 if((bits & cb_flag::po_annex) != 0) {
7721 for(auto si : state.world.nation_get_state_ownership(target)) {
7722 if(!state_claimed_in_war(state, w, source, target, si.get_state().get_definition()))
7723 return false;
7724 }
7725 return true;
7726 }
7727 if((bits & cb_flag::all_allowed_states) != 0) {
7728 auto state_filter = state.world.cb_type_get_allowed_states(cb_type);
7729 if((bits & cb_flag::po_transfer_provinces) != 0) {
7730 auto target_tag = state.world.nation_get_identity_from_identity_holder(target);
7731 auto holder = state.world.national_identity_get_nation_from_identity_holder(cb_tag);
7732
7733 if(state_filter) {
7734 for(auto si : state.world.nation_get_state_ownership(target)) {
7735 if(trigger::evaluate(state, state_filter, trigger::to_generic(si.get_state().id), trigger::to_generic(source), trigger::to_generic(holder))
7736 && !state_claimed_in_war(state, w, source, target, si.get_state().get_definition())) {
7737
7738 return false;
7739 }
7740 }
7741 return true;
7742 }
7743 } else if((bits & cb_flag::po_demand_state) != 0) {
7744 auto target_tag = state.world.nation_get_identity_from_identity_holder(target);
7745
7746 if(state_filter) {
7747 for(auto si : state.world.nation_get_state_ownership(target)) {
7748 if(trigger::evaluate(state, state_filter, trigger::to_generic(si.get_state().id), trigger::to_generic(source), trigger::to_generic(source))
7749 && !state_claimed_in_war(state, w, source, target, si.get_state().get_definition())) {
7750
7751 return false;
7752 }
7753 }
7754 return true;
7755 }
7756 }
7757 }
7758
7759 return false;
7760}
7762
7764 if(state.military_definitions.pending_blackflag_update) {
7765 state.military_definitions.pending_blackflag_update = false;
7766
7767 for(auto a : state.world.in_army) {
7768 if(a.get_controller_from_army_control() && !a.get_navy_from_army_transport() && !province::has_access_to_province(state, a.get_controller_from_army_control(), a.get_location_from_army_location())) {
7769 a.set_black_flag(true);
7770 } else {
7771 a.set_black_flag(false);
7772 }
7773 }
7774 }
7776
7777bool rebel_army_in_province(sys::state& state, dcon::province_id p) {
7778 for(auto ar : state.world.province_get_army_location(p)) {
7779 if(ar.get_army().get_controller_from_army_rebel_control())
7780 return true;
7781 }
7782 return false;
7783}
7784dcon::province_id find_land_rally_pt(sys::state& state, dcon::nation_id by, dcon::province_id start) {
7785 float distance = 2.0f;
7786 dcon::province_id closest;
7787 auto region = state.world.province_get_connected_region_id(start);
7788
7789 for(auto p : state.world.nation_get_province_ownership(by)) {
7790 if(!p.get_province().get_land_rally_point())
7791 continue;
7792 if(p.get_province().get_connected_region_id() != region)
7793 continue;
7794 if(p.get_province().get_nation_from_province_control() != by)
7795 continue;
7796 if(auto dist = province::sorting_distance(state, start, p.get_province()); !closest || dist < distance) {
7797 distance = dist;
7798 closest = p.get_province();
7799 }
7800 }
7801
7802 return closest;
7803}
7804dcon::province_id find_naval_rally_pt(sys::state& state, dcon::nation_id by, dcon::province_id start) {
7805 float distance = 2.0f;
7806 dcon::province_id closest;
7807
7808 for(auto p : state.world.nation_get_province_ownership(by)) {
7809 if(!p.get_province().get_naval_rally_point())
7810 continue;
7811 if(p.get_province().get_nation_from_province_control() != by)
7812 continue;
7813 if(auto dist = province::sorting_distance(state, start, p.get_province()); !closest || dist < distance) {
7814 distance = dist;
7815 closest = p.get_province();
7816 }
7817 }
7818
7819 return closest;
7820}
7821void move_land_to_merge(sys::state& state, dcon::nation_id by, dcon::army_id a, dcon::province_id start, dcon::province_id dest) {
7822 if(state.world.nation_get_is_player_controlled(by) == false)
7823 return; // AI doesn't use rally points or templates
7824 if(!dest)
7825 dest = find_land_rally_pt(state, by, start);
7826 if(!dest || state.world.province_get_nation_from_province_control(dest) != by)
7827 return;
7828 if(state.world.army_get_battle_from_army_battle_participation(a))
7829 return;
7830
7831 if(dest == start) { // merge in place
7832 for(auto ar : state.world.province_get_army_location(start)) {
7833 if(ar.get_army().get_controller_from_army_control() == by && ar.get_army() != a) {
7834 auto regs = state.world.army_get_army_membership(a);
7835 while(regs.begin() != regs.end()) {
7836 (*regs.begin()).set_army(ar.get_army());
7837 }
7838 return;
7839 }
7840 }
7841 } else {
7842 auto path = province::make_land_path(state, start, dest, by, a);
7843 if(path.empty())
7844 return;
7845
7846 auto existing_path = state.world.army_get_path(a);
7847 auto new_size = uint32_t(path.size());
7848 existing_path.resize(new_size);
7849
7850 for(uint32_t k = 0; k < new_size; ++k) {
7851 assert(path[k]);
7852 existing_path[k] = path[k];
7853 }
7854 state.world.army_set_arrival_time(a, military::arrival_time_to(state, a, path.back()));
7855 state.world.army_set_moving_to_merge(a, true);
7857}
7858void move_navy_to_merge(sys::state& state, dcon::nation_id by, dcon::navy_id a, dcon::province_id start, dcon::province_id dest) {
7859 if(state.world.nation_get_is_player_controlled(by) == false)
7860 return; // AI doesn't use rally points or templates
7861 if(!dest)
7862 dest = find_naval_rally_pt(state, by, start);
7863 if(!dest || state.world.province_get_nation_from_province_control(dest) != by)
7864 return;
7865
7866 if(dest == start) { // merge in place
7867 for(auto ar : state.world.province_get_navy_location(start)) {
7868 if(ar.get_navy().get_controller_from_navy_control() == by && ar.get_navy() != a) {
7869 auto regs = state.world.navy_get_navy_membership(a);
7870 while(regs.begin() != regs.end()) {
7871 (*regs.begin()).set_navy(ar.get_navy());
7872 }
7873 return;
7874 }
7875 }
7876 } else {
7877 auto path = province::make_naval_path(state, start, dest);
7878 if(path.empty())
7879 return;
7880
7881 auto existing_path = state.world.navy_get_path(a);
7882 auto new_size = uint32_t(path.size());
7883 existing_path.resize(new_size);
7884
7885 for(uint32_t k = 0; k < new_size; ++k) {
7886 assert(path[k]);
7887 existing_path[k] = path[k];
7888 }
7889 state.world.navy_set_arrival_time(a, military::arrival_time_to(state, a, path.back()));
7890 state.world.navy_set_moving_to_merge(a, true);
7891 }
7893
7894bool pop_eligible_for_mobilization(sys::state& state, dcon::pop_id p) {
7895 auto const pop = dcon::fatten(state.world, p);
7896 return pop.get_poptype() != state.culture_definitions.soldiers
7897 && pop.get_poptype() != state.culture_definitions.slaves
7898 && pop.get_is_primary_or_accepted_culture()
7899 && pop.get_poptype().get_strata() == uint8_t(culture::pop_strata::poor);
7901
7902void disband_regiment_w_pop_death(sys::state& state, dcon::regiment_id reg_id) {
7903 auto base_pop = state.world.regiment_get_pop_from_regiment_source(reg_id);
7904 demographics::reduce_pop_size_safe(state, base_pop, int32_t(state.world.regiment_get_strength(reg_id) * state.defines.pop_size_per_regiment * state.defines.soldier_to_pop_damage));
7905 state.world.delete_regiment(reg_id);
7906}
7907
7908} // namespace military
dcon::text_key get_name() noexcept
#define assert(condition)
Definition: debug.h:74
void add_free_ai_cbs_to_war(sys::state &state, dcon::nation_id n, dcon::war_id w)
Definition: ai.cpp:3207
float estimate_army_offensive_strength(sys::state &state, dcon::army_id a)
Definition: ai.cpp:5164
army_activity
Definition: constants.hpp:622
void gather_to_battle(sys::state &state, dcon::nation_id n, dcon::province_id p)
Definition: ai.cpp:5049
pop_satisfaction_wrapper_fat fatten(data_container const &c, pop_satisfaction_wrapper_id id) noexcept
void regenerate_jingoism_support(sys::state &state, dcon::nation_id n)
constexpr dcon::demographics_key total(0)
void reduce_pop_size_safe(sys::state &state, dcon::pop_id pop_id, int32_t amount)
@ value
the parser finished reading a JSON value
void post(sys::state &state, message const &m)
Pushes a diplomatic message to the list of pending diplomatic requests for the specified recipient (m...
@ primary
Definition: economy.hpp:10
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
float sqrt(float x) noexcept
Definition: math_fns.hpp:70
constexpr uint32_t all_allowed_states
Definition: military.hpp:14
constexpr uint32_t po_status_quo
Definition: military.hpp:27
constexpr uint32_t is_not_constructing_cb
Definition: military.hpp:12
constexpr uint32_t is_civil_war
Definition: military.hpp:9
constexpr uint32_t always
Definition: military.hpp:10
void execute_cb_discovery(sys::state &state, dcon::nation_id n)
Definition: military.cpp:2071
bool province_is_under_siege(sys::state const &state, dcon::province_id ids)
Definition: military.cpp:603
bool are_allied_in_war(sys::state const &state, dcon::nation_id a, dcon::nation_id b)
Definition: military.cpp:662
float truce_break_cb_prestige_cost(sys::state &state, dcon::cb_type_id t)
Definition: military.cpp:1587
bool cb_conditions_satisfied(sys::state &state, dcon::nation_id actor, dcon::nation_id target, dcon::cb_type_id cb)
Definition: military.cpp:404
int32_t naval_supply_points_used(sys::state &state, dcon::nation_id n)
Definition: military.cpp:1252
float primary_warscore(sys::state &state, dcon::war_id w)
Definition: military.cpp:3997
int32_t province_point_cost(sys::state &state, dcon::province_id p, dcon::nation_id n)
Definition: military.cpp:1771
float unit_calculate_reinforcement(sys::state &state, dcon::regiment_id reg_id)
Definition: military.cpp:7382
float mobilization_size(sys::state const &state, dcon::nation_id n)
Definition: military.cpp:1334
void run_gc(sys::state &state)
Definition: military.cpp:3314
void update_naval_supply_points(sys::state &state)
Definition: military.cpp:1274
void send_rebel_hunter_to_next_province(sys::state &state, dcon::army_id ar, dcon::province_id prov)
Definition: military.cpp:6862
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
float crisis_cb_addition_infamy_cost(sys::state &state, dcon::cb_type_id type, dcon::nation_id from, dcon::nation_id target, dcon::state_definition_id cb_state)
Definition: military.cpp:2030
void take_from_sphere(sys::state &state, dcon::nation_id member, dcon::nation_id new_gp)
Definition: military.cpp:2902
int32_t free_transport_capacity(sys::state &state, dcon::navy_id n)
Definition: military.cpp:6851
void add_to_war(sys::state &state, dcon::war_id w, dcon::nation_id n, bool as_attacker, bool on_war_creation)
Definition: military.cpp:2368
float recruited_pop_fraction(sys::state const &state, dcon::nation_id n)
Definition: military.cpp:607
bool is_artillery_better(sys::state &state, dcon::nation_id n, dcon::unit_type_id best, dcon::unit_type_id given)
Definition: military.cpp:57
int32_t total_regiments(sys::state &state, dcon::nation_id n)
Definition: military.cpp:25
float calculate_navy_combined_reinforce(sys::state &state, dcon::navy_id navy_id)
Definition: military.cpp:7416
void eject_ships(sys::state &state, dcon::province_id p)
Definition: military.cpp:7175
int32_t defender_peace_cost(sys::state &state, dcon::war_id war)
Definition: military.cpp:1952
void end_battle(sys::state &state, dcon::land_battle_id b, battle_result result)
Definition: military.cpp:4918
bool can_use_cb_against(sys::state &state, dcon::nation_id from, dcon::nation_id target)
Definition: military.cpp:245
dcon::unit_type_id get_best_artillery(sys::state &state, dcon::nation_id n, bool primary_culture)
Definition: military.cpp:130
void remove_from_common_allied_wars(sys::state &state, dcon::nation_id a, dcon::nation_id b)
Definition: military.cpp:683
void navy_arrives_in_province(sys::state &state, dcon::navy_id n, dcon::province_id p, dcon::naval_battle_id from)
Definition: military.cpp:6515
int32_t peace_offer_truce_months(sys::state &state, dcon::peace_offer_id offer)
Definition: military.cpp:1934
dcon::pop_id find_available_soldier(sys::state &state, dcon::province_id p, dcon::culture_id pop_culture)
Definition: military.cpp:1009
void apply_base_unit_stat_modifiers(sys::state &state)
Definition: military.cpp:195
float calculate_army_combined_reinforce(sys::state &state, dcon::army_id a)
Definition: military.cpp:7343
void update_blockade_status(sys::state &state)
Definition: military.cpp:597
void end_wars_between(sys::state &state, dcon::nation_id a, dcon::nation_id b)
Definition: military.cpp:2343
bool is_cavalry_better(sys::state &state, dcon::nation_id n, dcon::unit_type_id best, dcon::unit_type_id given)
Definition: military.cpp:74
bool has_truce_with(sys::state &state, dcon::nation_id attacker, dcon::nation_id target)
Definition: military.cpp:2296
void populate_war_text_subsitutions(sys::state &state, dcon::war_id w, text::substitution_map &sub)
Definition: military.cpp:2352
int32_t regiments_under_construction_in_province(sys::state &state, dcon::province_id p)
Definition: military.cpp:986
float mobilization_impact(sys::state const &state, dcon::nation_id n)
Definition: military.cpp:1344
void reject_peace_offer(sys::state &state, dcon::peace_offer_id offer)
Definition: military.cpp:3802
void update_ticking_war_score(sys::state &state)
Definition: military.cpp:3842
float cb_addition_infamy_cost(sys::state &state, dcon::war_id war, dcon::cb_type_id type, dcon::nation_id from, dcon::nation_id target, dcon::state_definition_id cb_state)
Definition: military.cpp:2038
bool standard_war_joining_is_possible(sys::state &state, dcon::war_id wfor, dcon::nation_id n, bool as_attacker)
Definition: military.cpp:2603
dcon::nation_id get_naval_battle_lead_defender(sys::state &state, dcon::naval_battle_id b)
Definition: military.cpp:4654
war_role get_role(sys::state const &state, dcon::war_id w, dcon::nation_id n)
Definition: military.cpp:2501
constexpr uint8_t defender_bonus_dig_in_mask
Definition: military.hpp:91
int32_t main_culture_regiments_under_construction_in_province(sys::state &state, dcon::province_id p)
Definition: military.cpp:996
bool can_retreat_from_battle(sys::state &state, dcon::naval_battle_id battle)
Definition: military.cpp:7645
uint32_t state_naval_base_level(sys::state const &state, dcon::state_instance_id si)
Definition: military.cpp:625
void reset_unit_stats(sys::state &state)
Definition: military.cpp:186
int32_t attacker_peace_cost(sys::state &state, dcon::war_id war)
Definition: military.cpp:1941
void remove_from_war(sys::state &state, dcon::war_id w, dcon::nation_id n, bool as_loss)
Definition: military.cpp:2759
void update_cbs(sys::state &state)
Definition: military.cpp:1361
void cleanup_war(sys::state &state, dcon::war_id w, war_result result)
Definition: military.cpp:2848
bool is_attacker(sys::state &state, dcon::war_id w, dcon::nation_id n)
Definition: military.cpp:2493
uint32_t state_railroad_level(sys::state const &state, dcon::state_instance_id si)
Definition: military.cpp:637
dcon::ship_id create_new_ship(sys::state &state, dcon::nation_id n, dcon::unit_type_id t)
Definition: military.cpp:2317
float truce_break_cb_infamy(sys::state &state, dcon::cb_type_id t, dcon::nation_id target, dcon::state_definition_id cb_state)
Definition: military.cpp:1709
void cleanup_navy(sys::state &state, dcon::navy_id n)
Definition: military.cpp:4862
int32_t count_navies(sys::state &state, dcon::nation_id n)
Definition: military.cpp:2199
constexpr float org_dam_mul
Definition: military.cpp:22
bool joining_as_attacker_would_break_truce(sys::state &state, dcon::nation_id a, dcon::war_id w)
Definition: military.cpp:783
bool is_infantry_better(sys::state &state, dcon::nation_id n, dcon::unit_type_id best, dcon::unit_type_id given)
Definition: military.cpp:37
bool is_defender_wargoal(sys::state const &state, dcon::war_id w, dcon::wargoal_id wg)
Definition: military.cpp:751
void regenerate_land_unit_average(sys::state &state)
Definition: military.cpp:1202
dcon::unit_type_id get_best_cavalry(sys::state &state, dcon::nation_id n, bool primary_culture)
Definition: military.cpp:158
dcon::nation_id get_land_battle_lead_attacker(sys::state &state, dcon::land_battle_id b)
Definition: military.cpp:4674
void update_recruitable_regiments(sys::state &state, dcon::nation_id n)
Definition: military.cpp:1175
constexpr float siege_speed_mul
Definition: military.cpp:6860
void add_wargoal(sys::state &state, dcon::war_id wfor, dcon::nation_id added_by, dcon::nation_id target, dcon::cb_type_id type, dcon::state_definition_id sd, dcon::national_identity_id tag, dcon::nation_id secondary_nation)
Definition: military.cpp:2668
bool cb_requires_selection_of_a_valid_nation(sys::state const &state, dcon::cb_type_id t)
Definition: military.cpp:2057
constexpr float str_dam_mul
Definition: military.cpp:23
dcon::unit_type_id get_best_infantry(sys::state &state, dcon::nation_id n, bool primary_culture)
Definition: military.cpp:96
int32_t mobilized_regiments_created_from_province(sys::state &state, dcon::province_id p)
Definition: military.cpp:833
bool are_in_common_war(sys::state const &state, dcon::nation_id a, dcon::nation_id b)
Definition: military.cpp:673
sys::date arrival_time_to(sys::state &state, dcon::army_id a, dcon::province_id p)
Definition: military.cpp:4284
dcon::war_id find_war_between(sys::state const &state, dcon::nation_id a, dcon::nation_id b)
Definition: military.cpp:718
void advance_mobilizations(sys::state &state)
Definition: military.cpp:7570
void add_cb(sys::state &state, dcon::nation_id n, dcon::cb_type_id cb, dcon::nation_id target, dcon::state_definition_id target_state=dcon::state_definition_id{})
Definition: military.cpp:1456
float ship_calculate_reinforcement(sys::state &state, dcon::ship_id ship_id, float combined)
Definition: military.cpp:7443
uint32_t naval_supply_from_naval_base(sys::state &state, dcon::province_id prov, dcon::nation_id nation)
Definition: military.cpp:1256
int32_t regiments_possible_from_pop(sys::state &state, dcon::pop_id p)
Definition: military.cpp:851
leader_counts count_leaders(sys::state &state, dcon::nation_id n)
Definition: military.cpp:2185
dcon::war_id create_war(sys::state &state, dcon::nation_id primary_attacker, dcon::nation_id primary_defender, dcon::cb_type_id primary_wargoal, dcon::state_definition_id primary_wargoal_state, dcon::national_identity_id primary_wargoal_tag, dcon::nation_id primary_wargoal_secondary)
Definition: military.cpp:2509
bool state_has_naval_base(sys::state const &state, dcon::state_instance_id si)
Definition: military.cpp:613
void move_land_to_merge(sys::state &state, dcon::nation_id by, dcon::army_id a, dcon::province_id start, dcon::province_id dest)
Definition: military.cpp:7819
void add_truce(sys::state &state, dcon::nation_id a, dcon::nation_id b, int32_t days)
Definition: military.cpp:3493
dcon::nation_id get_naval_battle_lead_attacker(sys::state &state, dcon::naval_battle_id b)
Definition: military.cpp:4634
ve::fp_vector ve_mobilization_impact(sys::state const &state, ve::tagged_vector< dcon::nation_id > nations)
Definition: military.cpp:1355
int32_t regiments_created_from_province(sys::state &state, dcon::province_id p)
Definition: military.cpp:823
void monthly_leaders_update(sys::state &state)
Definition: military.cpp:2204
bool cb_instance_conditions_satisfied(sys::state &state, dcon::nation_id actor, dcon::nation_id target, dcon::cb_type_id cb, dcon::state_definition_id st, dcon::national_identity_id tag, dcon::nation_id secondary)
Definition: military.cpp:471
float truce_break_cb_militancy(sys::state &state, dcon::cb_type_id t)
Definition: military.cpp:1648
float cb_infamy(sys::state &state, dcon::cb_type_id t, dcon::nation_id target, dcon::state_definition_id cb_state)
Definition: military.cpp:1519
float cb_infamy_country_modifier(sys::state &state, dcon::nation_id target)
Definition: military.cpp:1461
dcon::regiment_id create_new_regiment(sys::state &state, dcon::nation_id n, dcon::unit_type_id t)
Definition: military.cpp:2306
void adjust_leader_prestige(sys::state &state, dcon::leader_id l, float value)
Definition: military.cpp:4895
void add_truce_between_sides(sys::state &state, dcon::war_id w, int32_t months)
Definition: military.cpp:3443
void move_navy_to_merge(sys::state &state, dcon::nation_id by, dcon::navy_id a, dcon::province_id start, dcon::province_id dest)
Definition: military.cpp:7856
void start_mobilization(sys::state &state, dcon::nation_id n)
Definition: military.cpp:7472
void update_all_recruitable_regiments(sys::state &state)
Definition: military.cpp:1181
void call_attacker_allies(sys::state &state, dcon::war_id wfor)
Definition: military.cpp:2648
bool leader_is_in_combat(sys::state &state, dcon::leader_id l)
Definition: military.cpp:2106
float unit_get_strength(sys::state &state, dcon::regiment_id regiment_id)
Definition: military.cpp:7334
participation internal_find_war_between(sys::state const &state, dcon::nation_id a, dcon::nation_id b)
Definition: military.cpp:703
int32_t mobilized_regiments_possible_from_province(sys::state &state, dcon::province_id p)
Definition: military.cpp:1137
void remove_military_access(sys::state &state, dcon::nation_id accessing_nation, dcon::nation_id target)
Definition: military.cpp:2336
bool are_at_war(sys::state const &state, dcon::nation_id a, dcon::nation_id b)
Definition: military.cpp:649
void set_initial_leaders(sys::state &state)
Definition: military.cpp:2877
bool defenders_have_non_status_quo_wargoal(sys::state const &state, dcon::war_id w)
Definition: military.cpp:760
void invalidate_unowned_wargoals(sys::state &state)
Definition: military.cpp:213
int32_t main_culture_regiments_created_from_province(sys::state &state, dcon::province_id p)
Definition: military.cpp:929
ve::fp_vector ve_mobilization_size(sys::state const &state, ve::tagged_vector< dcon::nation_id > nations)
Definition: military.cpp:1339
void regenerate_total_regiment_counts(sys::state &state)
Definition: military.cpp:1190
int32_t supply_limit_in_province(sys::state &state, dcon::nation_id n, dcon::province_id p)
Definition: military.cpp:794
void end_mobilization(sys::state &state, dcon::nation_id n)
Definition: military.cpp:7543
bool attackers_have_status_quo_wargoal(sys::state const &state, dcon::war_id w)
Definition: military.cpp:775
int32_t transport_capacity(sys::state &state, dcon::navy_id n)
Definition: military.cpp:6843
dcon::leader_id make_new_leader(sys::state &state, dcon::nation_id n, bool is_general)
Definition: military.cpp:2116
float cb_infamy_state_modifier(sys::state &state, dcon::nation_id target, dcon::state_definition_id cb_state)
Definition: military.cpp:1485
void cleanup_army(sys::state &state, dcon::army_id n)
Definition: military.cpp:4812
int32_t total_ships(sys::state &state, dcon::nation_id n)
Definition: military.cpp:28
void daily_leaders_update(sys::state &state)
Definition: military.cpp:2258
void kill_leader(sys::state &state, dcon::leader_id l)
Definition: military.cpp:2150
float regiment_calculate_reinforcement(sys::state &state, dcon::regiment_fat_id reg, float combined)
Definition: military.cpp:7371
float relative_attrition_amount(sys::state &state, dcon::navy_id a, dcon::province_id prov)
Definition: military.cpp:5389
bool joining_war_does_not_violate_constraints(sys::state const &state, dcon::nation_id a, dcon::war_id w, bool as_attacker)
Definition: military.cpp:722
void restore_unsaved_values(sys::state &state)
Definition: military.cpp:233
void implement_peace_offer(sys::state &state, dcon::peace_offer_id offer)
Definition: military.cpp:3505
void call_defender_allies(sys::state &state, dcon::war_id wfor)
Definition: military.cpp:2617
void implement_war_goal(sys::state &state, dcon::war_id war, dcon::cb_type_id wargoal, dcon::nation_id from, dcon::nation_id target, dcon::nation_id secondary_nation, dcon::state_definition_id wargoal_state, dcon::national_identity_id wargoal_t)
Definition: military.cpp:2937
bool is_civil_war(sys::state const &state, dcon::war_id w)
Definition: military.cpp:743
int32_t count_armies(sys::state &state, dcon::nation_id n)
Definition: military.cpp:2195
bool pop_eligible_for_mobilization(sys::state &state, dcon::pop_id p)
Definition: military.cpp:7892
bool retreat(sys::state &state, dcon::navy_id n)
Definition: military.cpp:4589
int32_t regiments_max_possible_from_province(sys::state &state, dcon::province_id p)
Definition: military.cpp:885
int32_t naval_supply_points(sys::state &state, dcon::nation_id n)
Definition: military.cpp:1249
bool cb_requires_selection_of_a_liberatable_tag(sys::state const &state, dcon::cb_type_id t)
Definition: military.cpp:2061
bool compute_blockade_status(sys::state &state, dcon::province_id p)
Definition: military.cpp:576
bool state_claimed_in_war(sys::state &state, dcon::war_id w, dcon::nation_id from, dcon::nation_id target, dcon::state_definition_id cb_state)
Definition: military.cpp:7652
dcon::nation_id get_land_battle_lead_defender(sys::state &state, dcon::land_battle_id b)
Definition: military.cpp:4705
dcon::province_id find_naval_rally_pt(sys::state &state, dcon::nation_id by, dcon::province_id start)
Definition: military.cpp:7802
void regenerate_ship_scores(sys::state &state)
Definition: military.cpp:1227
void update_blackflag_status(sys::state &state, dcon::province_id p)
Definition: military.cpp:7166
int32_t peace_cost(sys::state &state, dcon::war_id war, dcon::cb_type_id wargoal, dcon::nation_id from, dcon::nation_id target, dcon::nation_id secondary_nation, dcon::state_definition_id wargoal_state, dcon::national_identity_id wargoal_tag)
Definition: military.cpp:1799
std::string get_war_name(sys::state &, dcon::war_id)
Definition: military.cpp:2575
int32_t main_culture_regiments_max_possible_from_province(sys::state &state, dcon::province_id p)
Definition: military.cpp:939
bool can_add_always_cb_to_war(sys::state &state, dcon::nation_id actor, dcon::nation_id target, dcon::cb_type_id cb, dcon::war_id w)
Definition: military.cpp:260
bool defenders_have_status_quo_wargoal(sys::state const &state, dcon::war_id w)
Definition: military.cpp:768
float successful_cb_prestige(sys::state &state, dcon::cb_type_id t, dcon::nation_id actor)
Definition: military.cpp:1964
int32_t cost_of_peace_offer(sys::state &state, dcon::peace_offer_id offer)
Definition: military.cpp:1924
bool province_is_blockaded(sys::state const &state, dcon::province_id ids)
Definition: military.cpp:572
void add_truce_from_nation(sys::state &state, dcon::war_id w, dcon::nation_id n, int32_t months)
Definition: military.cpp:3473
bool cb_requires_selection_of_a_state(sys::state const &state, dcon::cb_type_id t)
Definition: military.cpp:2065
int32_t mobilized_regiments_pop_limit(sys::state &state, dcon::nation_id n)
Definition: military.cpp:1166
constexpr uint8_t level_hostile
Definition: nations.hpp:170
constexpr uint8_t level_in_sphere
Definition: nations.hpp:173
constexpr uint8_t level_mask
Definition: nations.hpp:167
bool is_great_power(sys::state const &state, dcon::nation_id id)
Definition: nations.cpp:1068
void break_alliance(sys::state &state, dcon::diplomatic_relation_id rel)
Definition: nations.cpp:2012
void adjust_prestige(sys::state &state, dcon::nation_id n, float delta)
Definition: nations.cpp:1900
bool is_involved_in_crisis(sys::state const &state, dcon::nation_id n)
Definition: nations.cpp:1632
void make_vassal(sys::state &state, dcon::nation_id subject, dcon::nation_id overlord)
Definition: nations.cpp:1963
void release_vassal(sys::state &state, dcon::overlord_id rel)
Definition: nations.cpp:1948
bool are_allied(sys::state &state, dcon::nation_id a, dcon::nation_id b)
Definition: nations.cpp:685
float prestige_score(sys::state const &state, dcon::nation_id n)
Definition: nations.cpp:961
void adjust_relationship(sys::state &state, dcon::nation_id a, dcon::nation_id b, float delta)
Definition: nations.cpp:1660
void create_nation_based_on_template(sys::state &state, dcon::nation_id n, dcon::nation_id base)
Definition: nations.cpp:1672
void cleanup_crisis(sys::state &state)
Definition: nations.cpp:2440
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_ruling_party_ideology(sys::state &state, dcon::nation_id n, dcon::ideology_id id)
Definition: politics.cpp:350
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)
float get_militancy(sys::state const &state, dcon::pop_id p)
constexpr uint8_t impassible_bit
Definition: constants.hpp:595
constexpr uint8_t non_adjacent_bit
Definition: constants.hpp:596
constexpr uint8_t river_crossing_bit
Definition: constants.hpp:597
constexpr uint8_t coastal_bit
Definition: constants.hpp:594
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
float sorting_distance(sys::state &state, dcon::province_id a, dcon::province_id b)
Definition: province.cpp:1752
std::vector< dcon::province_id > make_naval_retreat_path(sys::state &state, dcon::nation_id nation_as, dcon::province_id start)
Definition: province.cpp:2178
bool has_access_to_province(sys::state &state, dcon::nation_id nation_as, dcon::province_id prov)
Definition: province.cpp:1787
bool is_overseas(sys::state const &state, dcon::province_id ids)
Definition: province.cpp:19
void ve_for_each_land_province(sys::state &state, F const &func)
void for_each_province_in_state_instance(sys::state &state, dcon::state_instance_id s, F const &func)
float distance(sys::state &state, dcon::province_adjacency_id pair)
Definition: province.cpp:1740
std::vector< dcon::province_id > make_land_retreat_path(sys::state &state, dcon::nation_id nation_as, dcon::province_id start)
Definition: province.cpp:2232
void for_each_land_province(sys::state &state, F const &func)
std::vector< dcon::province_id > make_naval_path(sys::state &state, dcon::province_id start, dcon::province_id end)
Definition: province.cpp:2094
void set_province_controller(sys::state &state, dcon::province_id p, dcon::nation_id n)
Definition: province.cpp:157
bool has_naval_access_to_province(sys::state &state, dcon::nation_id nation_as, dcon::province_id prov)
Definition: province.cpp:1760
void conquer_province(sys::state &state, dcon::province_id id, dcon::nation_id new_owner)
Definition: province.cpp:1064
void get_hunting_targets(sys::state &state, dcon::nation_id n, std::vector< impl::prov_str > &rebel_provs)
Definition: rebels.cpp:785
void sort_hunting_targets(sys::state &state, impl::arm_str const &ar, std::vector< impl::prov_str > &rebel_provs)
Definition: rebels.cpp:795
Definition: prng.cpp:6
random_pair get_random_pair(sys::state const &state, uint32_t value_in)
Definition: prng.cpp:26
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
NLOHMANN_BASIC_JSON_TPL_DECLARATION void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL &j1, nlohmann::NLOHMANN_BASIC_JSON_TPL &j2) noexcept(//NOLINT(readability-inconsistent-declaration-parameter-name, cert-dcl58-cpp) is_nothrow_move_constructible< nlohmann::NLOHMANN_BASIC_JSON_TPL >::value &&//NOLINT(misc-redundant-expression, cppcoreguidelines-noexcept-swap, performance-noexcept-swap) is_nothrow_move_assignable< nlohmann::NLOHMANN_BASIC_JSON_TPL >::value)
exchanges the values of two JSON objects
Definition: json.hpp:24635
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
std::string produce_simple_string(sys::state const &state, dcon::text_key id)
Definition: text.cpp:617
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
uint uint32_t
uchar uint8_t
Holds data regarding a diplomatic message between two specified nations at a certain date,...
static constexpr uint32_t set_size
Holds important data about the game world, state, and other data regarding windowing,...