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