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.get_pop().get_militancy();
2552 mil = 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 *
3360 state.world.cb_type_get_penalty_factor(par.joined_with_offer.wargoal_type);
3361 if(pop_militancy > 0) {
3362 for(auto prv : state.world.nation_get_province_ownership(par.id)) {
3363 for(auto pop : prv.get_province().get_pop_location()) {
3364 auto& mil = pop.get_pop().get_militancy();
3365 mil = std::min(mil + pop_militancy, 10.0f);
3366 }
3367 }
3368 }
3369
3370 if(par.supports_attacker) {
3371 nations::adjust_relationship(state, par.id, state.primary_crisis_attacker,
3372 -state.defines.crisis_winner_relations_impact);
3373 } else {
3374 nations::adjust_relationship(state, par.id, state.primary_crisis_defender,
3375 -state.defines.crisis_winner_relations_impact);
3376 }
3377 } else {
3378 if(par.supports_attacker) {
3379 nations::adjust_relationship(state, par.id, state.primary_crisis_attacker,
3380 state.defines.crisis_winner_relations_impact);
3381 } else {
3382 nations::adjust_relationship(state, par.id, state.primary_crisis_defender,
3383 state.defines.crisis_winner_relations_impact);
3384 }
3385 }
3386 } else {
3387 if(crisis_attackers_won != par.supports_attacker) {
3388 if(par.supports_attacker) {
3389 nations::adjust_relationship(state, par.id, state.primary_crisis_attacker,
3390 -state.defines.crisis_winner_relations_impact);
3391 } else {
3392 nations::adjust_relationship(state, par.id, state.primary_crisis_defender,
3393 -state.defines.crisis_winner_relations_impact);
3394 }
3395 } else {
3396 if(par.supports_attacker) {
3397 nations::adjust_relationship(state, par.id, state.primary_crisis_attacker,
3398 state.defines.crisis_winner_relations_impact);
3399 } else {
3400 nations::adjust_relationship(state, par.id, state.primary_crisis_defender,
3401 state.defines.crisis_winner_relations_impact);
3402 }
3403 }
3404 }
3405 }
3406 }
3407
3408 if(crisis_attackers_won) {
3409
3410 float p_factor = 0.05f * (state.defines.crisis_winner_prestige_factor_base +
3411 state.defines.crisis_winner_prestige_factor_year * float(state.current_date.value) / float(365));
3412
3413 nations::adjust_prestige(state, state.primary_crisis_defender,
3414 -p_factor * nations::prestige_score(state, state.primary_crisis_attacker));
3415 nations::adjust_prestige(state, state.primary_crisis_attacker,
3416 p_factor * nations::prestige_score(state, state.primary_crisis_attacker));
3417
3418 auto rp_ideology = state.world.nation_get_ruling_party(state.primary_crisis_defender).get_ideology();
3419 if(rp_ideology) {
3420 for(auto prv : state.world.nation_get_province_ownership(state.primary_crisis_defender)) {
3421 prv.get_province().get_party_loyalty(rp_ideology) *= (1.0f - state.defines.party_loyalty_hit_on_war_loss);
3422 }
3423 }
3424 } else {
3425
3426 float p_factor = 0.05f * (state.defines.crisis_winner_prestige_factor_base +
3427 state.defines.crisis_winner_prestige_factor_year * float(state.current_date.value) / float(365));
3428
3429 nations::adjust_prestige(state, state.primary_crisis_attacker,
3430 -p_factor * nations::prestige_score(state, state.primary_crisis_attacker));
3431 nations::adjust_prestige(state, state.primary_crisis_defender,
3432 p_factor * nations::prestige_score(state, state.primary_crisis_attacker));
3433
3434 auto rp_ideology = state.world.nation_get_ruling_party(state.primary_crisis_attacker).get_ideology();
3435 if(rp_ideology) {
3436 for(auto prv : state.world.nation_get_province_ownership(state.primary_crisis_attacker)) {
3437 prv.get_province().get_party_loyalty(rp_ideology) *= (1.0f - state.defines.party_loyalty_hit_on_war_loss);
3438 }
3439 }
3440 }
3441 }
3442
3443 state.world.peace_offer_set_war_from_war_settlement(offer, dcon::war_id{});
3444 state.world.peace_offer_set_is_crisis_offer(offer, false);
3445}
3446
3447void reject_peace_offer(sys::state& state, dcon::peace_offer_id offer) {
3448 dcon::nation_id from = state.world.peace_offer_get_nation_from_pending_peace_offer(offer);
3449 dcon::nation_id target = state.world.peace_offer_get_target(offer);
3451
3452 auto war = state.world.peace_offer_get_war_from_war_settlement(offer);
3453
3454 if(war) {
3456 [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) {
3457 text::substitution_map sub;
3458 text::add_to_substitution_map(sub, text::variable_type::order, std::string_view(""));
3459 text::add_to_substitution_map(sub, text::variable_type::second, text::get_adjective(state, pd));
3460 text::add_to_substitution_map(sub, text::variable_type::second_country, pd);
3461 text::add_to_substitution_map(sub, text::variable_type::first, text::get_adjective(state, pa));
3462 text::add_to_substitution_map(sub, text::variable_type::third, tag);
3463 text::add_to_substitution_map(sub, text::variable_type::state, st);
3464
3465 std::string resolved_war_name = text::resolve_string_substitution(state, name, sub);
3466 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});
3467 },
3468 "msg_peace_offer_rejected_title",
3469 target, from, dcon::nation_id{},
3471 });
3472 }
3473
3474 // TODO: detect
3475 /*
3476 If a "good" peace offer is refused, the refusing nation gains define:GOOD_PEACE_REFUSAL_WAREXH war exhaustion and all of its
3477 pops gain define:GOOD_PEACE_REFUSAL_MILITANCY. What counts as a good offer, well if the peace offer is considered "better"
3478 than expected. This seems to be a complicated thing to calculate involving: the direction the war is going in (sign of the
3479 latest war score change), the overall quantity of forces on each side (with navies counting for less), time since the war
3480 began, war exhaustion, war score, the peace cost of the offer, and whether the recipient will be annexed as a result.
3481 */
3482
3483 state.world.peace_offer_set_war_from_war_settlement(offer, dcon::war_id{});
3484 state.world.peace_offer_set_is_crisis_offer(offer, false);
3485}
3486
3488 for(auto wg : state.world.in_wargoal) {
3489 auto war = wg.get_war_from_wargoals_attached();
3490 if(!war)
3491 continue;
3492
3493 auto attacker_goal = military::is_attacker(state, war, wg.get_added_by());
3494 auto role = attacker_goal ? war_role::attacker : war_role::defender;
3495
3496 /*
3497 #### Occupation score
3498
3499 Increases by occupation-percentage x define:TWS_FULFILLED_SPEED (up to define:TWS_CB_LIMIT_DEFAULT) when the percentage
3500 occupied is >= define:TWS_FULFILLED_IDLE_SPACE or when the occupation percentage is > 0 and the current occupation score
3501 is negative. If there is no occupation, the score decreases by define:TWS_NOT_FULFILLED_SPEED. This can only take the
3502 score into negative after define:TWS_GRACE_PERIOD_DAYS, at which point it can go to -define:TWS_CB_LIMIT_DEFAULT.
3503 */
3504
3505 auto bits = wg.get_type().get_type_bits();
3506 if((bits & (cb_flag::po_annex | cb_flag::po_transfer_provinces | cb_flag::po_demand_state)) != 0) {
3507 float total_count = 0.0f;
3508 float occupied = 0.0f;
3509 if(wg.get_associated_state()) {
3510 for(auto prv : wg.get_associated_state().get_abstract_state_membership()) {
3511 if(prv.get_province().get_nation_from_province_ownership() == wg.get_target_nation()) {
3512 ++total_count;
3513 if(get_role(state, war, prv.get_province().get_nation_from_province_control()) == role) {
3514 ++occupied;
3515 }
3516 }
3517 }
3518 } else if((bits & cb_flag::po_annex) != 0) {
3519 for(auto prv : wg.get_target_nation().get_province_ownership()) {
3520 ++total_count;
3521 if(get_role(state, war, prv.get_province().get_nation_from_province_control()) == role) {
3522 ++occupied;
3523 }
3524 }
3525 } else if(auto allowed_states = wg.get_type().get_allowed_states(); allowed_states) {
3526 auto from_slot = wg.get_secondary_nation().id ? wg.get_secondary_nation().id : wg.get_associated_tag().get_nation_from_identity_holder().id;
3527 bool is_lib = (bits & cb_flag::po_transfer_provinces) != 0;
3528 for(auto st : wg.get_target_nation().get_state_ownership()) {
3529 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))) {
3530
3531 province::for_each_province_in_state_instance(state, st.get_state(), [&](dcon::province_id prv) {
3532 ++total_count;
3533 if(get_role(state, war, state.world.province_get_nation_from_province_control(prv)) == role) {
3534 ++occupied;
3535 }
3536 });
3537 }
3538 }
3539 }
3540
3541 if(total_count > 0.0f) {
3542 float fraction = occupied / total_count;
3543 if(fraction >= state.defines.tws_fulfilled_idle_space || (wg.get_ticking_war_score() < 0 && occupied > 0.0f)) {
3544 wg.get_ticking_war_score() += state.defines.tws_fulfilled_speed * fraction;
3545 } else if(occupied == 0.0f) {
3546 if(wg.get_ticking_war_score() > 0.0f || war.get_start_date() + int32_t(state.defines.tws_grace_period_days) <= state.current_date) {
3547 wg.get_ticking_war_score() -= state.defines.tws_not_fulfilled_speed;
3548 }
3549 }
3550 }
3551 }
3552
3553 if(wg.get_type().get_tws_battle_factor() > 0) {
3554
3555 /*
3556 #### Battle score
3557
3558 - zero if fewer than define:TWS_BATTLE_MIN_COUNT have been fought
3559 - only if the war goal has tws_battle_factor > 0
3560 - calculate relative losses for each side (something on the order of the difference in losses / 10,000 for land combat
3561 or the difference in losses / 10 for sea combat) with the points going to the winner, and then take the total of the
3562 relative loss scores for both sides and divide by the relative loss score for the defender.
3563 - subtract from tws_battle_factor and then divide by define:TWS_BATTLE_MAX_ASPECT (limited to -1 to +1). This then
3564 works is the occupied percentage described below.
3565 */
3566 if(war.get_number_of_battles() >= uint16_t(state.defines.tws_battle_min_count)) {
3567
3568 float ratio = 0.0f;
3569 if(attacker_goal) {
3570 ratio = war.get_defender_battle_score() > 0.0f ? war.get_attacker_battle_score() / war.get_defender_battle_score() : 10.0f;
3571 } else {
3572 ratio = war.get_attacker_battle_score() > 0.0f ? war.get_defender_battle_score() / war.get_attacker_battle_score() : 10.0f;
3573 }
3574 if(ratio >= wg.get_type().get_tws_battle_factor()) {
3575 auto effective_percentage = std::min(ratio / state.defines.tws_battle_max_aspect, 1.0f);
3576 wg.get_ticking_war_score() += state.defines.tws_fulfilled_speed * effective_percentage;
3577 } else if(ratio <= 1.0f / wg.get_type().get_tws_battle_factor() && ratio > 0.0f) {
3578 auto effective_percentage = std::min(1.0f / (ratio * state.defines.tws_battle_max_aspect), 1.0f);
3579 wg.get_ticking_war_score() -= state.defines.tws_fulfilled_speed * effective_percentage;
3580 } else if(ratio == 0.0f) {
3581 wg.get_ticking_war_score() -= state.defines.tws_fulfilled_speed;
3582 }
3583 }
3584 }
3585 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());
3586
3587 wg.get_ticking_war_score() =
3588 std::clamp(wg.get_ticking_war_score(), -float(max_score), float(max_score));
3589 }
3591
3592float primary_warscore_from_blockades(sys::state& state, dcon::war_id w) {
3593 auto pattacker = state.world.war_get_primary_attacker(w);
3594 auto pdefender = state.world.war_get_primary_defender(w);
3595
3596 auto d_cpc = state.world.nation_get_central_ports(pdefender);
3597 int32_t d_blockaded_in_war = 0;
3598 for(auto p : state.world.nation_get_province_ownership(pdefender)) {
3599 if(military::province_is_blockaded(state, p.get_province()) && !province::is_overseas(state, p.get_province().id)) {
3600 for(auto v : state.world.province_get_navy_location(p.get_province().get_port_to())) {
3601 if(!v.get_navy().get_is_retreating() && !v.get_navy().get_battle_from_navy_battle_participation()) {
3602 if(military::get_role(state, w, v.get_navy().get_controller_from_navy_control()) == military::war_role::attacker) {
3603 ++d_blockaded_in_war;
3604 break; // out of inner loop
3605 }
3606 }
3607 }
3608 }
3609 }
3610 auto def_b_frac = std::clamp(d_cpc > 0 ? float(d_blockaded_in_war) / float(d_cpc) : 0.0f, 0.0f, 1.0f);
3611
3612 int32_t a_blockaded_in_war = 0;
3613 for(auto p : state.world.nation_get_province_ownership(pattacker)) {
3614 if(military::province_is_blockaded(state, p.get_province()) && !province::is_overseas(state, p.get_province().id)) {
3615 for(auto v : state.world.province_get_navy_location(p.get_province().get_port_to())) {
3616 if(!v.get_navy().get_is_retreating() && !v.get_navy().get_battle_from_navy_battle_participation()) {
3617 if(military::get_role(state, w, v.get_navy().get_controller_from_navy_control()) == military::war_role::defender) {
3618 ++a_blockaded_in_war;
3619 break; // out of inner loop
3620 }
3621 }
3622 }
3623 }
3624 }
3625 auto a_cpc = state.world.nation_get_central_ports(pattacker);
3626 auto att_b_frac = std::clamp(a_cpc > 0 ? float(a_blockaded_in_war) / float(a_cpc) : 0.0f, 0.0f, 1.0f);
3627
3628 return 25.0f * (def_b_frac - att_b_frac);
3630
3631float primary_warscore(sys::state& state, dcon::war_id w) {
3632 return std::clamp(
3633 primary_warscore_from_occupation(state, w)
3634 + primary_warscore_from_battles(state, w)
3635 + primary_warscore_from_blockades(state, w)
3636 + primary_warscore_from_war_goals(state, w), -100.0f, 100.0f);
3638
3639float primary_warscore_from_occupation(sys::state& state, dcon::war_id w) {
3640 float total = 0.0f;
3641
3642 auto pattacker = state.world.war_get_primary_attacker(w);
3643 auto pdefender = state.world.war_get_primary_defender(w);
3644
3645 int32_t sum_attacker_prov_values = 0;
3646 int32_t sum_attacker_occupied_values = 0;
3647 for(auto prv : state.world.nation_get_province_ownership(pattacker)) {
3648 auto v = province_point_cost(state, prv.get_province(), pattacker);
3649 sum_attacker_prov_values += v;
3650 if(get_role(state, w, prv.get_province().get_nation_from_province_control()) == war_role::defender)
3651 sum_attacker_occupied_values += v;
3652 }
3653
3654 int32_t sum_defender_prov_values = 0;
3655 int32_t sum_defender_occupied_values = 0;
3656 for(auto prv : state.world.nation_get_province_ownership(pdefender)) {
3657 auto v = province_point_cost(state, prv.get_province(), pdefender);
3658 sum_defender_prov_values += v;
3659 if(get_role(state, w, prv.get_province().get_nation_from_province_control()) == war_role::attacker)
3660 sum_defender_occupied_values += v;
3661 }
3662
3663 if(sum_defender_prov_values > 0)
3664 total += (float(sum_defender_occupied_values) * 100.0f) / float(sum_defender_prov_values);
3665 if(sum_attacker_prov_values > 0)
3666 total -= (float(sum_attacker_occupied_values) * 100.0f) / float(sum_attacker_prov_values);
3667
3668 return total;
3669}
3670float primary_warscore_from_battles(sys::state& state, dcon::war_id w) {
3671 return std::clamp(state.world.war_get_attacker_battle_score(w) - state.world.war_get_defender_battle_score(w),
3672 -state.defines.max_warscore_from_battles, state.defines.max_warscore_from_battles);
3673}
3674float primary_warscore_from_war_goals(sys::state& state, dcon::war_id w) {
3675 float total = 0.0f;
3676
3677 for(auto wg : state.world.war_get_wargoals_attached(w)) {
3678 if(is_attacker(state, w, wg.get_wargoal().get_added_by())) {
3679 total += wg.get_wargoal().get_ticking_war_score();
3680 } else {
3681 total -= wg.get_wargoal().get_ticking_war_score();
3682 }
3683 }
3684
3685 return total;
3687
3688float directed_warscore(sys::state& state, dcon::war_id w, dcon::nation_id primary, dcon::nation_id secondary) {
3689 float total = 0.0f;
3690
3691 auto is_pattacker = state.world.war_get_primary_attacker(w) == primary;
3692 auto is_pdefender = state.world.war_get_primary_defender(w) == primary;
3693
3694 auto is_tpattacker = state.world.war_get_primary_attacker(w) == secondary;
3695 auto is_tpdefender = state.world.war_get_primary_defender(w) == secondary;
3696
3697 if(is_pattacker && is_tpdefender)
3698 return primary_warscore(state, w);
3699 if(is_pdefender && is_tpattacker)
3700 return -primary_warscore(state, w);
3701
3702 int32_t sum_attacker_prov_values = 0;
3703 int32_t sum_attacker_occupied_values = 0;
3704 for(auto prv : state.world.nation_get_province_ownership(primary)) {
3705 auto v = province_point_cost(state, prv.get_province(), primary);
3706 sum_attacker_prov_values += v;
3707
3708 if(is_tpattacker) {
3709 if(get_role(state, w, prv.get_province().get_nation_from_province_control()) == war_role::attacker)
3710 sum_attacker_occupied_values += v;
3711 } else if(is_tpdefender) {
3712 if(get_role(state, w, prv.get_province().get_nation_from_province_control()) == war_role::defender)
3713 sum_attacker_occupied_values += v;
3714 } else {
3715 if(prv.get_province().get_nation_from_province_control() == secondary)
3716 sum_attacker_occupied_values += v;
3717 }
3718 }
3719
3720 int32_t sum_defender_prov_values = 0;
3721 int32_t sum_defender_occupied_values = 0;
3722 for(auto prv : state.world.nation_get_province_ownership(secondary)) {
3723 auto v = province_point_cost(state, prv.get_province(), secondary);
3724 sum_defender_prov_values += v;
3725
3726 if(is_pattacker) {
3727 if(get_role(state, w, prv.get_province().get_nation_from_province_control()) == war_role::attacker)
3728 sum_defender_occupied_values += v;
3729 } else if(is_pdefender) {
3730 if(get_role(state, w, prv.get_province().get_nation_from_province_control()) == war_role::defender)
3731 sum_defender_occupied_values += v;
3732 } else {
3733 if(prv.get_province().get_nation_from_province_control() == primary)
3734 sum_defender_occupied_values += v;
3735 }
3736 }
3737
3738 if(sum_defender_prov_values > 0)
3739 total += (float(sum_defender_occupied_values) * 100.0f) / float(sum_defender_prov_values);
3740 if(sum_attacker_prov_values > 0)
3741 total -= (float(sum_attacker_occupied_values) * 100.0f) / float(sum_attacker_prov_values);
3742
3743 for(auto wg : state.world.war_get_wargoals_attached(w)) {
3744 if((wg.get_wargoal().get_added_by() == primary || is_pattacker || is_pdefender)
3745 && wg.get_wargoal().get_target_nation() == secondary) {
3746
3747 total += wg.get_wargoal().get_ticking_war_score();
3748 } else if(wg.get_wargoal().get_added_by() == secondary
3749 && (wg.get_wargoal().get_target_nation() == primary || is_pattacker || is_pdefender)) {
3750
3751 total -= wg.get_wargoal().get_ticking_war_score();
3752 } else if(wg.get_wargoal().get_added_by() == primary
3753 && (wg.get_wargoal().get_target_nation() == secondary || is_tpattacker || is_tpdefender)) {
3754
3755 total += wg.get_wargoal().get_ticking_war_score();
3756 } else if((wg.get_wargoal().get_added_by() == secondary || is_tpattacker || is_tpdefender)
3757 && (wg.get_wargoal().get_target_nation() == primary)) {
3758
3759 total -= wg.get_wargoal().get_ticking_war_score();
3760 }
3761 }
3762
3763 auto d_cpc = state.world.nation_get_central_ports(secondary);
3764 int32_t d_blockaded_in_war = 0;
3765 for(auto p : state.world.nation_get_province_ownership(secondary)) {
3766 if(military::province_is_blockaded(state, p.get_province()) && !province::is_overseas(state, p.get_province().id)) {
3767 for(auto v : state.world.province_get_navy_location(p.get_province().get_port_to())) {
3768 if(!v.get_navy().get_is_retreating() && !v.get_navy().get_battle_from_navy_battle_participation()) {
3769
3770 if(is_pattacker) {
3771 if(get_role(state, w, v.get_navy().get_controller_from_navy_control()) == war_role::attacker) {
3772 ++d_blockaded_in_war;
3773 break; // out of inner loop
3774 }
3775 } else if(is_pdefender) {
3776 if(get_role(state, w, v.get_navy().get_controller_from_navy_control()) == war_role::defender) {
3777 ++d_blockaded_in_war;
3778 break; // out of inner loop
3779 }
3780 } else {
3781 if(v.get_navy().get_controller_from_navy_control() == primary) {
3782 ++d_blockaded_in_war;
3783 break; // out of inner loop
3784 }
3785 }
3786 }
3787 }
3788 }
3789 }
3790 auto def_b_frac = std::clamp(d_cpc > 0 ? float(d_blockaded_in_war) / float(d_cpc) : 0.0f, 0.0f, 1.0f);
3791
3792 int32_t a_blockaded_in_war = 0;
3793 for(auto p : state.world.nation_get_province_ownership(primary)) {
3794 if(military::province_is_blockaded(state, p.get_province()) && !province::is_overseas(state, p.get_province().id)) {
3795 for(auto v : state.world.province_get_navy_location(p.get_province().get_port_to())) {
3796 if(!v.get_navy().get_is_retreating() && !v.get_navy().get_battle_from_navy_battle_participation()) {
3797 if(is_tpattacker) {
3798 if(get_role(state, w, v.get_navy().get_controller_from_navy_control()) == war_role::attacker) {
3799 ++a_blockaded_in_war;
3800 break; // out of inner loop
3801 }
3802 } else if(is_tpdefender) {
3803 if(get_role(state, w, v.get_navy().get_controller_from_navy_control()) == war_role::defender) {
3804 ++a_blockaded_in_war;
3805 break; // out of inner loop
3806 }
3807 } else {
3808 if(v.get_navy().get_controller_from_navy_control() == secondary) {
3809 ++a_blockaded_in_war;
3810 break; // out of inner loop
3811 }
3812 }
3813 }
3814 }
3815 }
3816 }
3817 auto a_cpc = state.world.nation_get_central_ports(primary);
3818 auto att_b_frac = std::clamp(a_cpc > 0 ? float(a_blockaded_in_war) / float(a_cpc) : 0.0f, 0.0f, 1.0f);
3819
3820 total += 25.0f * (def_b_frac - att_b_frac);
3821
3822 return std::clamp(total, 0.0f, 100.0f);
3824
3825bool can_embark_onto_sea_tile(sys::state& state, dcon::nation_id from, dcon::province_id p, dcon::army_id a) {
3826 int32_t max_cap = 0;
3827 for(auto n : state.world.province_get_navy_location(p)) {
3828 if(n.get_navy().get_controller_from_navy_control() == from &&
3829 !bool(n.get_navy().get_battle_from_navy_battle_participation())) {
3830 max_cap = std::max(free_transport_capacity(state, n.get_navy()), max_cap);
3831 }
3832 }
3833 auto regs = state.world.army_get_army_membership(a);
3834 return int32_t(regs.end() - regs.begin()) <= max_cap;
3836
3837dcon::navy_id find_embark_target(sys::state& state, dcon::nation_id from, dcon::province_id p, dcon::army_id a) {
3838 auto regs = state.world.army_get_army_membership(a);
3839 int32_t count = int32_t(regs.end() - regs.begin());
3840
3841 int32_t max_cap = 0;
3842 for(auto n : state.world.province_get_navy_location(p)) {
3843 if(n.get_navy().get_controller_from_navy_control() == from) {
3844 if(free_transport_capacity(state, n.get_navy()) >= count)
3845 return n.get_navy();
3846 }
3847 }
3848 return dcon::navy_id{};
3850
3851float effective_army_speed(sys::state& state, dcon::army_id a) {
3852 auto owner = state.world.army_get_controller_from_army_control(a);
3853 if(!owner)
3854 owner = state.world.rebel_faction_get_ruler_from_rebellion_within(state.world.army_get_controller_from_army_rebel_control(a));
3855
3856 float min_speed = 10000.0f;
3857 for(auto reg : state.world.army_get_army_membership(a)) {
3858 auto reg_speed = state.world.nation_get_unit_stats(owner, reg.get_regiment().get_type()).maximum_speed;
3859 min_speed = std::min(min_speed, reg_speed);
3860 }
3861
3862 /*
3863 slowest ship or regiment x (1 + infrastructure-provided-by-railroads x railroad-level-of-origin) x
3864 (possibly-some-modifier-for-crossing-water) x (define:LAND_SPEED_MODIFIER or define:NAVAL_SPEED_MODIFIER) x (leader-speed-trait
3865 + 1)
3866 */
3867 auto leader = state.world.army_get_general_from_army_leadership(a);
3868 auto bg = state.world.leader_get_background(leader);
3869 auto per = state.world.leader_get_personality(leader);
3870 auto leader_move = state.world.leader_trait_get_speed(bg) + state.world.leader_trait_get_speed(per);
3871 return min_speed * (state.world.army_get_is_retreating(a) ? 2.0f : 1.0f) *
3872 (1.0f + state.world.province_get_building_level(state.world.army_get_location_from_army_location(a), uint8_t(economy::province_building_type::railroad)) *
3873 state.economy_definitions.building_definitions[int32_t(economy::province_building_type::railroad)].infrastructure) *
3874 (leader_move + 1.0f);
3875}
3876float effective_navy_speed(sys::state& state, dcon::navy_id n) {
3877 auto owner = state.world.navy_get_controller_from_navy_control(n);
3878
3879 float min_speed = 10000.0f;
3880 for(auto reg : state.world.navy_get_navy_membership(n)) {
3881 auto reg_speed = state.world.nation_get_unit_stats(owner, reg.get_ship().get_type()).maximum_speed;
3882 min_speed = std::min(min_speed, reg_speed);
3883 }
3884
3885 auto leader = state.world.navy_get_admiral_from_navy_leadership(n);
3886 auto bg = state.world.leader_get_background(leader);
3887 auto per = state.world.leader_get_personality(leader);
3888 auto leader_move = state.world.leader_trait_get_speed(bg) + state.world.leader_trait_get_speed(per);
3889 return min_speed * (state.world.navy_get_is_retreating(n) ? 2.0f : 1.0f) * (leader_move + 1.0f);
3891
3892sys::date arrival_time_to(sys::state& state, dcon::army_id a, dcon::province_id p) {
3893 auto current_location = state.world.army_get_location_from_army_location(a);
3894 auto adj = state.world.get_province_adjacency_by_province_pair(current_location, p);
3895 float distance = province::distance(state, adj);
3896 float sum_mods = state.world.province_get_modifier_values(p, sys::provincial_mod_offsets::movement_cost) +
3897 state.world.province_get_modifier_values(p, sys::provincial_mod_offsets::movement_cost);
3898 float effective_distance = std::max(0.1f, distance * (sum_mods + 1.0f));
3899
3900 float effective_speed = effective_army_speed(state, a);
3901
3902 int32_t days = effective_speed > 0.0f ? int32_t(std::ceil(effective_distance / effective_speed)) : 50;
3903 assert(days > 0);
3904 return state.current_date + days;
3905}
3906sys::date arrival_time_to(sys::state& state, dcon::navy_id n, dcon::province_id p) {
3907 auto current_location = state.world.navy_get_location_from_navy_location(n);
3908 auto adj = state.world.get_province_adjacency_by_province_pair(current_location, p);
3909 float distance = province::distance(state, adj);
3910 float sum_mods = state.world.province_get_modifier_values(p, sys::provincial_mod_offsets::movement_cost) +
3911 state.world.province_get_modifier_values(p, sys::provincial_mod_offsets::movement_cost);
3912 float effective_distance = std::max(0.1f, distance * (sum_mods + 1.0f));
3913
3914 float effective_speed = effective_navy_speed(state, n);
3915
3916 int32_t days = effective_speed > 0.0f ? int32_t(std::ceil(effective_distance / effective_speed)) : 50;
3917 return state.current_date + days;
3919
3920void add_army_to_battle(sys::state& state, dcon::army_id a, dcon::land_battle_id b, war_role r) {
3921 assert(state.world.army_is_valid(a));
3922 bool battle_attacker = (r == war_role::attacker) == state.world.land_battle_get_war_attacker_is_attacker(b);
3923 if(battle_attacker) {
3924 if(!state.world.land_battle_get_general_from_attacking_general(b)) {
3925 state.world.land_battle_set_general_from_attacking_general(b, state.world.army_get_general_from_army_leadership(a));
3926 }
3927
3928 auto reserves = state.world.land_battle_get_reserves(b);
3929 for(auto reg : state.world.army_get_army_membership(a)) {
3930 if(reg.get_regiment().get_strength() <= 0.0f)
3931 continue;
3932
3933 auto type = state.military_definitions.unit_base_definitions[reg.get_regiment().get_type()].type;
3934 switch(type) {
3935 case unit_type::infantry:
3936 reserves.push_back(
3937 reserve_regiment{reg.get_regiment().id, reserve_regiment::is_attacking | reserve_regiment::type_infantry});
3938 state.world.land_battle_get_attacker_infantry(b) += reg.get_regiment().get_strength();
3939 break;
3940 case unit_type::cavalry:
3941 reserves.push_back(
3942 reserve_regiment{reg.get_regiment().id, reserve_regiment::is_attacking | reserve_regiment::type_cavalry});
3943 state.world.land_battle_get_attacker_cav(b) += reg.get_regiment().get_strength();
3944 break;
3945 case unit_type::special:
3946 case unit_type::support:
3947 reserves.push_back(
3948 reserve_regiment{reg.get_regiment().id, reserve_regiment::is_attacking | reserve_regiment::type_support});
3949 state.world.land_battle_get_attacker_support(b) += reg.get_regiment().get_strength();
3950 break;
3951 default:
3952 assert(false);
3953 }
3954 }
3955 } else {
3956 auto& def_bonus = state.world.land_battle_get_defender_bonus(b);
3957 auto prev_dig_in = def_bonus | defender_bonus_dig_in_mask;
3958 auto new_dig_in = std::min(prev_dig_in, state.world.army_get_dig_in(a) & defender_bonus_dig_in_mask);
3959 def_bonus &= ~defender_bonus_dig_in_mask;
3960 def_bonus |= new_dig_in;
3961
3962 if(!state.world.land_battle_get_general_from_defending_general(b)) {
3963 state.world.land_battle_set_general_from_defending_general(b, state.world.army_get_general_from_army_leadership(a));
3964 }
3965 auto reserves = state.world.land_battle_get_reserves(b);
3966 for(auto reg : state.world.army_get_army_membership(a)) {
3967 if(reg.get_regiment().get_strength() <= 0.0f)
3968 continue;
3969
3970 auto type = state.military_definitions.unit_base_definitions[reg.get_regiment().get_type()].type;
3971 switch(type) {
3972 case unit_type::infantry:
3973 reserves.push_back(reserve_regiment{reg.get_regiment().id, reserve_regiment::type_infantry});
3974 state.world.land_battle_get_defender_infantry(b) += reg.get_regiment().get_strength();
3975 break;
3976 case unit_type::cavalry:
3977 reserves.push_back(reserve_regiment{reg.get_regiment().id, reserve_regiment::type_cavalry});
3978 state.world.land_battle_get_defender_cav(b) += reg.get_regiment().get_strength();
3979 break;
3980 case unit_type::special:
3981 case unit_type::support:
3982 reserves.push_back(reserve_regiment{reg.get_regiment().id, reserve_regiment::type_support});
3983 state.world.land_battle_get_defender_support(b) += reg.get_regiment().get_strength();
3984 break;
3985 default:
3986 assert(false);
3987 }
3988 }
3989 }
3990
3991 state.world.army_set_battle_from_army_battle_participation(a, b);
3992 state.world.army_set_arrival_time(a, sys::date{}); // pause movement
3994
3995void army_arrives_in_province(sys::state& state, dcon::army_id a, dcon::province_id p, crossing_type crossing, dcon::land_battle_id from) {
3996 assert(state.world.army_is_valid(a));
3997 assert(!state.world.army_get_battle_from_army_battle_participation(a));
3998
3999 state.world.army_set_location_from_army_location(a, p);
4000 auto regs = state.world.army_get_army_membership(a);
4001 if(!state.world.army_get_black_flag(a) && !state.world.army_get_is_retreating(a) && regs.begin() != regs.end()) {
4002 auto owner_nation = state.world.army_get_controller_from_army_control(a);
4003
4004 // look for existing battle
4005 for(auto b : state.world.province_get_land_battle_location(p)) {
4006 if(b.get_battle() != from) {
4007 auto battle_war = b.get_battle().get_war_from_land_battle_in_war();
4008 if(battle_war) {
4009 auto owner_role = get_role(state, battle_war, owner_nation);
4010 if(owner_role != war_role::none) {
4011 add_army_to_battle(state, a, b.get_battle(), owner_role);
4012 return; // done -- only one battle per
4013 }
4014 } else { // rebels
4015 add_army_to_battle(state, a, b.get_battle(), bool(owner_nation) ? war_role::defender : war_role::attacker);
4016 return;
4017 }
4018 }
4019 }
4020
4021 // start battle
4022 dcon::land_battle_id gather_to_battle;
4023 dcon::war_id battle_in_war;
4024
4025 for(auto o : state.world.province_get_army_location(p)) {
4026 if(o.get_army() == a)
4027 continue;
4028 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())
4029 continue;
4030
4031 auto other_nation = o.get_army().get_controller_from_army_control();
4032
4033 if(bool(owner_nation) != bool(other_nation)) { // battle vs. rebels
4034 auto new_battle = fatten(state.world, state.world.create_land_battle());
4035 new_battle.set_war_attacker_is_attacker(!bool(owner_nation));
4036 new_battle.set_start_date(state.current_date);
4037 new_battle.set_location_from_land_battle_location(p);
4038 new_battle.set_dice_rolls(make_dice_rolls(state, uint32_t(new_battle.id.value)));
4039
4040 uint8_t flags = defender_bonus_dig_in_mask;
4041 if(crossing == crossing_type::river)
4042 flags |= defender_bonus_crossing_river;
4043 if(crossing == crossing_type::sea)
4044 flags |= defender_bonus_crossing_sea;
4045 new_battle.set_defender_bonus(flags);
4046
4047 auto cw_a = state.defines.base_combat_width +
4048 state.world.nation_get_modifier_values(owner_nation, sys::national_mod_offsets::combat_width);
4049 auto cw_b = state.defines.base_combat_width +
4050 state.world.nation_get_modifier_values(other_nation, sys::national_mod_offsets::combat_width);
4051 new_battle.set_combat_width(uint8_t(
4052 std::clamp(int32_t(std::min(cw_a, cw_b) *
4053 (state.world.province_get_modifier_values(p, sys::provincial_mod_offsets::combat_width) + 1.0f)),
4054 2, 30)));
4055
4056 add_army_to_battle(state, a, new_battle, !bool(owner_nation) ? war_role::attacker : war_role::defender);
4057 add_army_to_battle(state, o.get_army(), new_battle, bool(owner_nation) ? war_role::attacker : war_role::defender);
4058
4059 gather_to_battle = new_battle.id;
4060 break;
4061 } else if(auto par = internal_find_war_between(state, owner_nation, other_nation); par.role != war_role::none) {
4062 auto new_battle = fatten(state.world, state.world.create_land_battle());
4063 new_battle.set_war_attacker_is_attacker(par.role == war_role::attacker);
4064 new_battle.set_start_date(state.current_date);
4065 new_battle.set_war_from_land_battle_in_war(par.w);
4066 new_battle.set_location_from_land_battle_location(p);
4067 new_battle.set_dice_rolls(make_dice_rolls(state, uint32_t(new_battle.id.value)));
4068
4069 uint8_t flags = defender_bonus_dig_in_mask;
4070 if(crossing == crossing_type::river)
4071 flags |= defender_bonus_crossing_river;
4072 if(crossing == crossing_type::sea)
4073 flags |= defender_bonus_crossing_sea;
4074 new_battle.set_defender_bonus(flags);
4075
4076 auto cw_a = state.defines.base_combat_width +
4077 state.world.nation_get_modifier_values(owner_nation, sys::national_mod_offsets::combat_width);
4078 auto cw_b = state.defines.base_combat_width +
4079 state.world.nation_get_modifier_values(other_nation, sys::national_mod_offsets::combat_width);
4080 new_battle.set_combat_width(uint8_t(
4081 std::clamp(int32_t(std::min(cw_a, cw_b) *
4082 (state.world.province_get_modifier_values(p, sys::provincial_mod_offsets::combat_width) + 1.0f)),
4083 2, 30)));
4084
4085 add_army_to_battle(state, a, new_battle, par.role);
4086 add_army_to_battle(state, o.get_army(), new_battle, par.role == war_role::attacker ? war_role::defender : war_role::attacker);
4087
4088 gather_to_battle = new_battle.id;
4089 battle_in_war = par.w;
4090 break;
4091 }
4092
4093 }
4094
4095 if(gather_to_battle) {
4096 for(auto o : state.world.province_get_army_location(p)) {
4097 if(o.get_army() == a)
4098 continue;
4099 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())
4100 continue;
4101
4102 auto other_nation = o.get_army().get_controller_from_army_control();
4103 if(battle_in_war) {
4104 if(auto role = get_role(state, battle_in_war, other_nation); role != war_role::none) {
4105 add_army_to_battle(state, o.get_army(), gather_to_battle, role);
4106 }
4107 } else { // battle vs. rebels
4108 add_army_to_battle(state, o.get_army(), gather_to_battle, !bool(other_nation) ? war_role::attacker : war_role::defender);
4109 }
4110 }
4111
4112 if(battle_in_war) { // gather as part of war
4113 for(auto par : state.world.war_get_war_participant(battle_in_war)) {
4114 if(par.get_nation().get_is_player_controlled() == false)
4115 ai::gather_to_battle(state, par.get_nation(), p);
4116 }
4117 } else if(state.world.nation_get_is_player_controlled(owner_nation) == false) { // gather vs. rebels
4118 ai::gather_to_battle(state, owner_nation, p);
4119 }
4120 }
4121 }
4123
4124void add_navy_to_battle(sys::state& state, dcon::navy_id n, dcon::naval_battle_id b, war_role r) {
4125 assert(state.world.navy_is_valid(n));
4126 bool battle_attacker = (r == war_role::attacker) == state.world.naval_battle_get_war_attacker_is_attacker(b);
4127 if(battle_attacker) {
4128 // try add admiral as leader
4129 if(!state.world.naval_battle_get_admiral_from_attacking_admiral(b)) {
4130 state.world.naval_battle_set_admiral_from_attacking_admiral(b, state.world.navy_get_admiral_from_navy_leadership(n));
4131 }
4132 // put ships in slots
4133 auto slots = state.world.naval_battle_get_slots(b);
4134 for(auto ship : state.world.navy_get_navy_membership(n)) {
4135 if(ship.get_ship().get_strength() <= 0.0f)
4136 continue;
4137
4138 auto type = state.military_definitions.unit_base_definitions[ship.get_ship().get_type()].type;
4139 switch(type) {
4140 case unit_type::big_ship:
4141 slots.push_back(ship_in_battle{ship.get_ship().id, 0,
4142 1000 | ship_in_battle::mode_seeking | ship_in_battle::is_attacking | ship_in_battle::type_big});
4143 state.world.naval_battle_get_attacker_big_ships(b)++;
4144 break;
4145 case unit_type::light_ship:
4146 slots.push_back(ship_in_battle{ship.get_ship().id, 0,
4147 1000 | ship_in_battle::mode_seeking | ship_in_battle::is_attacking | ship_in_battle::type_small});
4148 state.world.naval_battle_get_attacker_small_ships(b)++;
4149 break;
4150 case unit_type::transport:
4151 slots.push_back(ship_in_battle{ship.get_ship().id, 0,
4152 1000 | ship_in_battle::mode_seeking | ship_in_battle::is_attacking | ship_in_battle::type_transport});
4153 state.world.naval_battle_get_attacker_transport_ships(b)++;
4154 break;
4155 default:
4156 assert(false);
4157 }
4158 }
4159 } else {
4160 if(!state.world.naval_battle_get_admiral_from_defending_admiral(b)) {
4161 state.world.naval_battle_set_admiral_from_defending_admiral(b, state.world.navy_get_admiral_from_navy_leadership(n));
4162 }
4163 auto slots = state.world.naval_battle_get_slots(b);
4164 for(auto ship : state.world.navy_get_navy_membership(n)) {
4165 if(ship.get_ship().get_strength() <= 0.0f)
4166 continue;
4167
4168 auto type = state.military_definitions.unit_base_definitions[ship.get_ship().get_type()].type;
4169 switch(type) {
4170 case unit_type::big_ship:
4171 slots.push_back(ship_in_battle{ship.get_ship().id, 0, 1000 | ship_in_battle::mode_seeking | ship_in_battle::type_big});
4172 state.world.naval_battle_get_defender_big_ships(b)++;
4173 break;
4174 case unit_type::light_ship:
4175 slots.push_back(ship_in_battle{ship.get_ship().id, 0, 1000 | ship_in_battle::mode_seeking | ship_in_battle::type_small});
4176 state.world.naval_battle_get_defender_small_ships(b)++;
4177 break;
4178 case unit_type::transport:
4179 slots.push_back(
4180 ship_in_battle{ship.get_ship().id, 0, 1000 | ship_in_battle::mode_seeking | ship_in_battle::type_transport});
4181 state.world.naval_battle_get_defender_transport_ships(b)++;
4182 break;
4183 default:
4184 assert(false);
4185 }
4186 }
4187 }
4188
4189 state.world.navy_set_battle_from_navy_battle_participation(n, b);
4190 state.world.navy_set_arrival_time(n, sys::date{}); // pause movement
4191
4192 for(auto em : state.world.navy_get_army_transport(n)) {
4193 em.get_army().set_arrival_time(sys::date{});
4194 }
4196
4197bool retreat(sys::state& state, dcon::navy_id n) {
4198 auto province_start = state.world.navy_get_location_from_navy_location(n);
4199 auto nation_controller = state.world.navy_get_controller_from_navy_control(n);
4200
4201 if(!nation_controller)
4202 return false;
4203
4204 auto retreat_path = province::make_naval_retreat_path(state, nation_controller, province_start);
4205 if(retreat_path.size() > 0) {
4206 state.world.navy_set_is_retreating(n, true);
4207 auto existing_path = state.world.navy_get_path(n);
4208 existing_path.load_range(retreat_path.data(), retreat_path.data() + retreat_path.size());
4209
4210 state.world.navy_set_arrival_time(n, arrival_time_to(state, n, retreat_path.back()));
4211
4212 for(auto em : state.world.navy_get_army_transport(n)) {
4213 em.get_army().get_path().clear();
4214 }
4215 return true;
4216 } else {
4217 return false;
4218 }
4220
4221bool retreat(sys::state& state, dcon::army_id n) {
4222 auto province_start = state.world.army_get_location_from_army_location(n);
4223 auto nation_controller = state.world.army_get_controller_from_army_control(n);
4224
4225 if(!nation_controller)
4226 return false; // rebels don't retreat
4227
4228 auto retreat_path = province::make_land_retreat_path(state, nation_controller, province_start);
4229 if(retreat_path.size() > 0) {
4230 state.world.army_set_is_retreating(n, true);
4231 auto existing_path = state.world.army_get_path(n);
4232 existing_path.load_range(retreat_path.data(), retreat_path.data() + retreat_path.size());
4233
4234 state.world.army_set_arrival_time(n, arrival_time_to(state, n, retreat_path.back()));
4235 state.world.army_set_dig_in(n, 0);
4236 return true;
4237 } else {
4238 return false;
4239 }
4241
4242dcon::nation_id get_naval_battle_lead_attacker(sys::state& state, dcon::naval_battle_id b) {
4243 auto by_admiral =
4244 state.world.leader_get_nation_from_leader_loyalty(state.world.naval_battle_get_admiral_from_attacking_admiral(b));
4245 if(by_admiral)
4246 return by_admiral;
4247
4248 auto war = state.world.naval_battle_get_war_from_naval_battle_in_war(b);
4249 bool war_attackers = state.world.naval_battle_get_war_attacker_is_attacker(b);
4250
4251 for(auto nbp : state.world.naval_battle_get_navy_battle_participation(b)) {
4252 if(war_attackers && is_attacker(state, war, nbp.get_navy().get_controller_from_navy_control())) {
4253 return nbp.get_navy().get_controller_from_navy_control();
4254 } else if(!war_attackers && !is_attacker(state, war, nbp.get_navy().get_controller_from_navy_control())) {
4255 return nbp.get_navy().get_controller_from_navy_control();
4256 }
4257 }
4258
4259 return dcon::nation_id{};
4261
4262dcon::nation_id get_naval_battle_lead_defender(sys::state& state, dcon::naval_battle_id b) {
4263 auto by_admiral =
4264 state.world.leader_get_nation_from_leader_loyalty(state.world.naval_battle_get_admiral_from_defending_admiral(b));
4265 if(by_admiral)
4266 return by_admiral;
4267
4268 auto war = state.world.naval_battle_get_war_from_naval_battle_in_war(b);
4269 bool war_attackers = state.world.naval_battle_get_war_attacker_is_attacker(b);
4270
4271 for(auto nbp : state.world.naval_battle_get_navy_battle_participation(b)) {
4272 if(!war_attackers && is_attacker(state, war, nbp.get_navy().get_controller_from_navy_control())) {
4273 return nbp.get_navy().get_controller_from_navy_control();
4274 } else if(war_attackers && !is_attacker(state, war, nbp.get_navy().get_controller_from_navy_control())) {
4275 return nbp.get_navy().get_controller_from_navy_control();
4276 }
4277 }
4278
4279 return dcon::nation_id{};
4281
4282dcon::nation_id get_land_battle_lead_attacker(sys::state& state, dcon::land_battle_id b) {
4283 auto by_general =
4284 state.world.leader_get_nation_from_leader_loyalty(state.world.land_battle_get_general_from_attacking_general(b));
4285 if(by_general)
4286 return by_general;
4287
4288 auto war = state.world.land_battle_get_war_from_land_battle_in_war(b);
4289 bool war_attackers = state.world.land_battle_get_war_attacker_is_attacker(b);
4290
4291 if(!war) {
4292 if(war_attackers)
4293 return dcon::nation_id{};
4294
4295 for(auto nbp : state.world.land_battle_get_army_battle_participation(b)) {
4296 auto c = nbp.get_army().get_controller_from_army_control();
4297 if(c)
4298 return c;
4299 }
4300 }
4301
4302 for(auto nbp : state.world.land_battle_get_army_battle_participation(b)) {
4303 if(war_attackers && is_attacker(state, war, nbp.get_army().get_controller_from_army_control())) {
4304 return nbp.get_army().get_controller_from_army_control();
4305 } else if(!war_attackers && !is_attacker(state, war, nbp.get_army().get_controller_from_army_control())) {
4306 return nbp.get_army().get_controller_from_army_control();
4307 }
4308 }
4309
4310 return dcon::nation_id{};
4312
4313dcon::nation_id get_land_battle_lead_defender(sys::state& state, dcon::land_battle_id b) {
4314 auto by_general =
4315 state.world.leader_get_nation_from_leader_loyalty(state.world.land_battle_get_general_from_defending_general(b));
4316 if(by_general)
4317 return by_general;
4318
4319 auto war = state.world.land_battle_get_war_from_land_battle_in_war(b);
4320 bool war_attackers = state.world.land_battle_get_war_attacker_is_attacker(b);
4321
4322 if(!war) {
4323 if(!war_attackers)
4324 return dcon::nation_id{};
4325
4326 for(auto nbp : state.world.land_battle_get_army_battle_participation(b)) {
4327 auto c = nbp.get_army().get_controller_from_army_control();
4328 if(c)
4329 return c;
4330 }
4331 }
4332
4333 for(auto nbp : state.world.land_battle_get_army_battle_participation(b)) {
4334 if(!war_attackers && is_attacker(state, war, nbp.get_army().get_controller_from_army_control())) {
4335 return nbp.get_army().get_controller_from_army_control();
4336 } else if(war_attackers && !is_attacker(state, war, nbp.get_army().get_controller_from_army_control())) {
4337 return nbp.get_army().get_controller_from_army_control();
4338 }
4339 }
4340
4341 return dcon::nation_id{};
4343
4344float get_leader_select_score(sys::state& state, dcon::leader_id l) {
4345 /*
4346 - Each side has a leader that is in charge of the combat, which is the leader with the greatest
4347 value as determined by the following formula: (organization x 5 + attack + defend + morale +
4348 speed + attrition + experience / 2 + reconnaissance / 5 + reliability / 5) x (prestige + 1)
4349 */
4350 auto per = state.world.leader_get_personality(l);
4351 auto bak = state.world.leader_get_background(l);
4352 //
4353 auto org = state.world.leader_trait_get_organisation(per) + state.world.leader_trait_get_organisation(bak);
4354 auto atk = state.world.leader_trait_get_attack(per) + state.world.leader_trait_get_attack(bak);
4355 auto def = state.world.leader_trait_get_defense(per) + state.world.leader_trait_get_defense(bak);
4356 auto spd = state.world.leader_trait_get_speed(per) + state.world.leader_trait_get_speed(bak);
4357 auto mor = state.world.leader_trait_get_morale(per) + state.world.leader_trait_get_morale(bak);
4358 auto att = state.world.leader_trait_get_experience(per) + state.world.leader_trait_get_experience(bak);
4359 auto rel = state.world.leader_trait_get_reliability(per) + state.world.leader_trait_get_reliability(bak);
4360 auto exp = state.world.leader_trait_get_experience(per) + state.world.leader_trait_get_experience(bak);
4361 auto rec = state.world.leader_trait_get_reconnaissance(per) + state.world.leader_trait_get_reconnaissance(bak);
4362 auto lp = state.world.leader_get_prestige(l);
4363 return (org * 5.f + atk + def + mor + spd + att + exp / 2.f + rec / 5.f + rel / 5.f) * (lp + 1.f);
4364}
4365void update_battle_leaders(sys::state& state, dcon::land_battle_id b) {
4366 auto la = get_land_battle_lead_attacker(state, b);
4367 dcon::leader_id a_lid;
4368 float a_score = 0.f;
4369 auto ld = get_land_battle_lead_defender(state, b);
4370 dcon::leader_id d_lid;
4371 float d_score = 0.f;
4372 for(const auto a : state.world.land_battle_get_army_battle_participation(b)) {
4373 auto l = a.get_army().get_general_from_army_leadership();
4374 auto score = get_leader_select_score(state, l);
4375 if(a.get_army().get_controller_from_army_control() == la) {
4376 if(score > a_score) {
4377 a_lid = l;
4378 a_score = score;
4379 }
4380 } else if(a.get_army().get_controller_from_army_control() == ld) {
4381 if(score > d_score) {
4382 d_lid = l;
4383 d_score = score;
4384 }
4385 }
4386 }
4387 auto aa = state.world.land_battle_get_attacking_general(b);
4388 state.world.attacking_general_set_general(aa, a_lid);
4389 auto ab = state.world.land_battle_get_defending_general(b);
4390 state.world.defending_general_set_general(ab, d_lid);
4391}
4392void update_battle_leaders(sys::state& state, dcon::naval_battle_id b) {
4393 auto la = get_naval_battle_lead_attacker(state, b);
4394 dcon::leader_id a_lid;
4395 float a_score = 0.f;
4396 auto ld = get_naval_battle_lead_defender(state, b);
4397 dcon::leader_id d_lid;
4398 float d_score = 0.f;
4399 for(const auto a : state.world.naval_battle_get_navy_battle_participation(b)) {
4400 auto l = a.get_navy().get_admiral_from_navy_leadership();
4401 auto score = get_leader_select_score(state, l);
4402 if(a.get_navy().get_controller_from_navy_control() == la) {
4403 if(score > a_score) {
4404 a_lid = l;
4405 a_score = score;
4406 }
4407 } else if(a.get_navy().get_controller_from_navy_control() == ld) {
4408 if(score > d_score) {
4409 d_lid = l;
4410 d_score = score;
4411 }
4412 }
4413 }
4414 auto aa = state.world.naval_battle_get_attacking_admiral(b);
4415 state.world.attacking_admiral_set_admiral(aa, a_lid);
4416 auto ab = state.world.naval_battle_get_defending_admiral(b);
4417 state.world.defending_admiral_set_admiral(ab, d_lid);
4419
4420void cleanup_army(sys::state& state, dcon::army_id n) {
4421 assert(!state.world.army_get_battle_from_army_battle_participation(n));
4422
4423 auto regs = state.world.army_get_army_membership(n);
4424 while(regs.begin() != regs.end()) {
4425 state.world.delete_regiment((*regs.begin()).get_regiment().id);
4426 }
4427
4428 auto b = state.world.army_get_battle_from_army_battle_participation(n);
4429 if(b) {
4430 state.world.army_set_is_retreating(n, true); // prevents army from re-entering battles
4431
4432 bool should_end = true;
4433 auto controller = state.world.army_get_controller_from_army_control(n);
4434 if(bool(controller)) {
4435 // TODO: Do they have to be in common war or can they just be "hostile against"?
4436 bool has_other = false;
4437 bool has_rebels = false;
4438 for(auto bp : state.world.land_battle_get_army_battle_participation_as_battle(b)) {
4439 if(bp.get_army() != n) {
4440 has_other = true;
4441 if(are_allied_in_war(state, controller, bp.get_army().get_controller_from_army_control())) {
4442 should_end = false;
4443 } else if(bp.get_army().get_controller_from_army_rebel_control()) {
4444 has_rebels = true;
4445 }
4446 }
4447 }
4448 // continue fighting rebels
4449 if(has_other && has_rebels)
4450 should_end = false;
4451 } else if(state.world.army_get_controller_from_army_rebel_control(n)) {
4452 for(auto bp : state.world.land_battle_get_army_battle_participation_as_battle(b)) {
4453 if(bp.get_army() != n && bp.get_army().get_army_rebel_control()) {
4454 should_end = false;
4455 }
4456 }
4457 } else {
4458
4459 }
4460
4461 if(should_end) {
4462 bool as_attacker = state.world.land_battle_get_war_attacker_is_attacker(b);
4463 end_battle(state, b, as_attacker ? battle_result::defender_won : battle_result::attacker_won);
4464 }
4465 }
4466
4467 state.world.delete_army(n);
4469
4470void cleanup_navy(sys::state& state, dcon::navy_id n) {
4471 assert(!state.world.navy_get_battle_from_navy_battle_participation(n));
4472
4473 auto shps = state.world.navy_get_navy_membership(n);
4474 while(shps.begin() != shps.end()) {
4475 state.world.delete_ship((*shps.begin()).get_ship());
4476 }
4477 auto em = state.world.navy_get_army_transport(n);
4478 while(em.begin() != em.end()) {
4479 cleanup_army(state, (*em.begin()).get_army());
4480 }
4481
4482 auto controller = state.world.navy_get_controller_from_navy_control(n);
4483 auto b = state.world.navy_get_battle_from_navy_battle_participation(n);
4484
4485 state.world.navy_set_is_retreating(n, true); // prevents navy from re-entering battles
4486 if(b && controller) {
4487 bool should_end = true;
4488 // TODO: Do they have to be in common war or can they just be "hostile against"?
4489 for(auto bp : state.world.naval_battle_get_navy_battle_participation_as_battle(b)) {
4490 if(bp.get_navy() != n && are_allied_in_war(state, controller, state.world.navy_get_controller_from_navy_control(bp.get_navy()))) {
4491 should_end = false;
4492 }
4493 }
4494 if(should_end) {
4495 bool as_attacker = state.world.naval_battle_get_war_attacker_is_attacker(b);
4496 end_battle(state, b, as_attacker ? battle_result::defender_won : battle_result::attacker_won);
4497 }
4498 }
4499
4500 state.world.delete_navy(n);
4502
4503void adjust_leader_prestige(sys::state& state, dcon::leader_id l, float value) {
4504 auto v = state.world.leader_get_prestige(l);
4505 v = std::clamp(v + value, 0.f, 1.f); //from 0% to 100%
4506 state.world.leader_set_prestige(l, v);
4507}
4508void adjust_regiment_experience(sys::state& state, dcon::nation_id n, dcon::regiment_id l, float value) {
4509 auto v = state.world.regiment_get_experience(l);
4510
4511 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);
4512
4513 v = std::clamp(v + value, min_exp, 1.f); //from regular_experience_level to 100%
4514
4515 state.world.regiment_set_experience(l, v);
4516}
4517void adjust_ship_experience(sys::state& state, dcon::nation_id n, dcon::ship_id r, float value) {
4518 auto v = state.world.ship_get_experience(r);
4519
4520 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);
4521
4522 v = std::clamp(v + value * state.defines.exp_gain_div, min_exp, 1.f);
4523 state.world.ship_set_experience(r, v); //from regular_experience_level to 100%
4525
4526void end_battle(sys::state& state, dcon::land_battle_id b, battle_result result) {
4527 auto war = state.world.land_battle_get_war_from_land_battle_in_war(b);
4528 auto location = state.world.land_battle_get_location_from_land_battle_location(b);
4529
4530 assert(location);
4531
4532 auto make_leaderless = [&](dcon::army_id a) {
4533 state.world.army_set_controller_from_army_control(a, dcon::nation_id{});
4534 state.world.army_set_controller_from_army_rebel_control(a, dcon::rebel_faction_id{});
4535 state.world.army_set_is_retreating(a, true);
4536 };
4537
4538 auto a_nation = get_land_battle_lead_attacker(state, b);
4539 auto d_nation = get_land_battle_lead_defender(state, b);
4540
4541 for(auto n : state.world.land_battle_get_army_battle_participation(b)) {
4542 auto nation_owner = state.world.army_get_controller_from_army_control(n.get_army());
4543
4544 auto role_in_war = bool(war)
4545 ? get_role(state, war, n.get_army().get_controller_from_army_control())
4546 : (bool(nation_owner) ? war_role::defender : war_role::attacker);
4547
4548 bool battle_attacker = (role_in_war == war_role::attacker) == state.world.land_battle_get_war_attacker_is_attacker(b);
4549
4550
4551 if(battle_attacker && result == battle_result::defender_won) {
4552 if(!can_retreat_from_battle(state, b)) {
4553 make_leaderless(n.get_army());
4554 } else {
4555 if(!retreat(state, n.get_army()))
4556 make_leaderless(n.get_army());
4557 }
4558 } else if(!battle_attacker && result == battle_result::attacker_won) {
4559 if(!can_retreat_from_battle(state, b)) {
4560 make_leaderless(n.get_army());
4561 } else {
4562 if(!retreat(state, n.get_army()))
4563 make_leaderless(n.get_army());
4564 }
4565 } else {
4566 auto path = n.get_army().get_path();
4567 if(path.size() > 0) {
4568 state.world.army_set_arrival_time(n.get_army(), arrival_time_to(state, n.get_army(), path.at(path.size() - 1)));
4569 }
4570 }
4571 }
4572
4573 /*
4574 On finishing a battle:
4575 Each winning combatant get a random `on_battle_won` event, and each losing combatant gets a random `on_battle_lost` event
4576
4577 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
4578 */
4579
4580 if(result != battle_result::indecisive) {
4581 if(war)
4582 state.world.war_get_number_of_battles(war)++;
4583
4584 auto a_leader = state.world.land_battle_get_general_from_attacking_general(b);
4585 auto b_leader = state.world.land_battle_get_general_from_defending_general(b);
4586
4587 if(result == battle_result::attacker_won) {
4588 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);
4589 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);
4590 auto score = std::max(0.0f, 3.0f * (total_def_loss - total_att_loss) / 10.0f);
4591
4592 if(war) {
4593 if(state.world.land_battle_get_war_attacker_is_attacker(b)) {
4594 state.world.war_get_attacker_battle_score(war) += score;
4595 } else {
4596 state.world.war_get_defender_battle_score(war) += score;
4597 }
4598 }
4599
4600
4601 if(a_nation && d_nation) { // no prestige for beating up rebels
4602 nations::adjust_prestige(state, a_nation, score / 50.0f);
4603 nations::adjust_prestige(state, d_nation, score / -50.0f);
4604
4605 adjust_leader_prestige(state, a_leader, score / 50.f / 100.f);
4606 adjust_leader_prestige(state, b_leader, -score / 50.f / 100.f);
4607 }
4608
4609 // Report
4610 if(state.local_player_nation == a_nation || state.local_player_nation == d_nation) {
4611 land_battle_report rep;
4612 rep.attacker_infantry_losses = state.world.land_battle_get_attacker_infantry_lost(b);
4613 rep.attacker_infantry = state.world.land_battle_get_attacker_infantry(b);
4614 rep.attacker_cavalry_losses = state.world.land_battle_get_attacker_cav_lost(b);
4615 rep.attacker_cavalry = state.world.land_battle_get_attacker_cav(b);
4616 rep.attacker_support_losses = state.world.land_battle_get_attacker_support_lost(b);
4617 rep.attacker_support = state.world.land_battle_get_attacker_support(b);
4618
4619 rep.defender_infantry_losses = state.world.land_battle_get_defender_infantry_lost(b);
4620 rep.defender_infantry = state.world.land_battle_get_defender_infantry(b);
4621 rep.defender_cavalry_losses = state.world.land_battle_get_defender_cav_lost(b);
4622 rep.defender_cavalry = state.world.land_battle_get_defender_cav(b);
4623 rep.defender_support_losses = state.world.land_battle_get_defender_support_lost(b);
4624 rep.defender_support = state.world.land_battle_get_defender_support(b);
4625
4626 rep.attacker_won = (result == battle_result::attacker_won);
4627
4628 rep.attacking_nation = get_land_battle_lead_attacker(state, b);
4629 rep.defending_nation = get_land_battle_lead_defender(state, b);
4630 rep.attacking_general = state.world.land_battle_get_general_from_attacking_general(b);
4631 rep.defending_general = state.world.land_battle_get_general_from_defending_general(b);
4632
4633 rep.location = state.world.land_battle_get_location_from_land_battle_location(b);
4634 rep.player_on_winning_side = bool(war)
4635 ? is_attacker(state, war, state.local_player_nation) == state.world.land_battle_get_war_attacker_is_attacker(b)
4636 : !state.world.land_battle_get_war_attacker_is_attacker(b);
4637
4638 if(war) {
4639 if(rep.player_on_winning_side) {
4640 rep.warscore_effect = score;
4641 rep.prestige_effect = score / 50.0f;
4642 } else {
4643 rep.warscore_effect = -score;
4644 rep.prestige_effect = -score / 50.0f;
4645 }
4646 }
4647
4648 auto discard = state.land_battle_reports.try_push(rep);
4649 }
4650
4651 } else if(result == battle_result::defender_won) {
4652 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);
4653 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);
4654 auto score = std::max(0.0f, 3.0f * (total_att_loss - total_def_loss) / 10.0f);
4655
4656 if(war) {
4657 if(state.world.land_battle_get_war_attacker_is_attacker(b)) {
4658 state.world.war_get_defender_battle_score(war) += score;
4659 } else {
4660 state.world.war_get_attacker_battle_score(war) += score;
4661 }
4662 }
4663
4664 if(a_nation && d_nation) {
4665 nations::adjust_prestige(state, a_nation, score / -50.0f);
4666 nations::adjust_prestige(state, d_nation, score / 50.0f);
4667
4668 adjust_leader_prestige(state, a_leader, -score / 50.f / 100.f);
4669 adjust_leader_prestige(state, b_leader, score / 50.f / 100.f);
4670 }
4671
4672 // Report
4673 if(state.local_player_nation == a_nation || state.local_player_nation == d_nation) {
4674 land_battle_report rep;
4675 rep.attacker_infantry_losses = state.world.land_battle_get_attacker_infantry_lost(b);
4676 rep.attacker_infantry = state.world.land_battle_get_attacker_infantry(b);
4677 rep.attacker_cavalry_losses = state.world.land_battle_get_attacker_cav_lost(b);
4678 rep.attacker_cavalry = state.world.land_battle_get_attacker_cav(b);
4679 rep.attacker_support_losses = state.world.land_battle_get_attacker_support_lost(b);
4680 rep.attacker_support = state.world.land_battle_get_attacker_support(b);
4681
4682 rep.defender_infantry_losses = state.world.land_battle_get_defender_infantry_lost(b);
4683 rep.defender_infantry = state.world.land_battle_get_defender_infantry(b);
4684 rep.defender_cavalry_losses = state.world.land_battle_get_defender_cav_lost(b);
4685 rep.defender_cavalry = state.world.land_battle_get_defender_cav(b);
4686 rep.defender_support_losses = state.world.land_battle_get_defender_support_lost(b);
4687 rep.defender_support = state.world.land_battle_get_defender_support(b);
4688
4689 rep.attacker_won = (result == battle_result::attacker_won);
4690
4691 rep.attacking_nation = get_land_battle_lead_attacker(state, b);
4692 rep.defending_nation = get_land_battle_lead_defender(state, b);
4693 rep.attacking_general = state.world.land_battle_get_general_from_attacking_general(b);
4694 rep.defending_general = state.world.land_battle_get_general_from_defending_general(b);
4695
4696 rep.location = state.world.land_battle_get_location_from_land_battle_location(b);
4697 rep.player_on_winning_side = bool(war)
4698 ? is_attacker(state, war, state.local_player_nation) != state.world.land_battle_get_war_attacker_is_attacker(b)
4699 : state.world.land_battle_get_war_attacker_is_attacker(b);
4700
4701 if(war) {
4702 if(rep.player_on_winning_side) {
4703 rep.warscore_effect = score;
4704 rep.prestige_effect = score / 50.0f;
4705 } else {
4706 rep.warscore_effect = -score;
4707 rep.prestige_effect = -score / 50.0f;
4708 }
4709 }
4710 auto discard = state.land_battle_reports.try_push(rep);
4711 }
4712 }
4713 }
4714
4715
4716 if(result != battle_result::indecisive) { // so we don't restart battles as the war is ending
4717 auto par_range = state.world.land_battle_get_army_battle_participation(b);
4718 while(par_range.begin() != par_range.end()) {
4719 auto n = (*par_range.begin()).get_army();
4720 n.set_battle_from_army_battle_participation(dcon::land_battle_id{});
4721 army_arrives_in_province(state, n, location, crossing_type::none, b);
4722 }
4723 }
4724
4725 state.world.delete_land_battle(b);
4727
4728void end_battle(sys::state& state, dcon::naval_battle_id b, battle_result result) {
4729 auto war = state.world.naval_battle_get_war_from_naval_battle_in_war(b);
4730 auto location = state.world.naval_battle_get_location_from_naval_battle_location(b);
4731
4732 assert(war);
4733 assert(location);
4734
4735 auto a_nation = get_naval_battle_lead_attacker(state, b);
4736 auto d_nation = get_naval_battle_lead_defender(state, b);
4737
4738 for(auto n : state.world.naval_battle_get_navy_battle_participation(b)) {
4739 auto role_in_war = get_role(state, war, n.get_navy().get_controller_from_navy_control());
4740 bool battle_attacker = (role_in_war == war_role::attacker) == state.world.naval_battle_get_war_attacker_is_attacker(b);
4741
4742
4743 auto transport_cap = military::free_transport_capacity(state, n.get_navy());
4744 if(transport_cap < 0) {
4745 for(auto em : n.get_navy().get_army_transport()) {
4746 auto em_regs = em.get_army().get_army_membership();
4747 while(em_regs.begin() != em_regs.end() && transport_cap < 0) {
4748 auto reg_id = (*em_regs.begin()).get_regiment();
4749 disband_regiment_w_pop_death(state, reg_id);
4750 ++transport_cap;
4751 }
4752 if(transport_cap >= 0)
4753 break;
4754 }
4755 }
4756
4757 if(battle_attacker && result == battle_result::defender_won) {
4758 if(!can_retreat_from_battle(state, b)) {
4759 n.get_navy().set_controller_from_navy_control(dcon::nation_id{});
4760 n.get_navy().set_is_retreating(true);
4761 } else {
4762 if(!retreat(state, n.get_navy())) {
4763 n.get_navy().set_controller_from_navy_control(dcon::nation_id{});
4764 n.get_navy().set_is_retreating(true);
4765 }
4766 }
4767 } else if(!battle_attacker && result == battle_result::attacker_won) {
4768 if(!can_retreat_from_battle(state, b)) {
4769 n.get_navy().set_controller_from_navy_control(dcon::nation_id{});
4770 n.get_navy().set_is_retreating(true);
4771 } else {
4772 if(!retreat(state, n.get_navy())) {
4773 n.get_navy().set_controller_from_navy_control(dcon::nation_id{});
4774 n.get_navy().set_is_retreating(true);
4775 }
4776 }
4777 } else {
4778 auto path = n.get_navy().get_path();
4779 if(path.size() > 0) {
4780 state.world.navy_set_arrival_time(n.get_navy(), arrival_time_to(state, n.get_navy(), path.at(path.size() - 1)));
4781 }
4782
4783 for(auto em : n.get_navy().get_army_transport()) {
4784 auto apath = em.get_army().get_path();
4785 if(apath.size() > 0) {
4786 state.world.army_set_arrival_time(em.get_army(), arrival_time_to(state, em.get_army(), apath.at(apath.size() - 1)));
4787 }
4788 }
4789 }
4790 }
4791
4792 /*
4793 On finishing a naval battle:
4794 Each winning combatant get a random `on_battle_won` event, and each losing combatant gets a random `on_battle_lost` event.
4795
4796 Both sides compute their scaled losses fraction, which is (1 + (sum over ship type: supply-consumption-score x strength-losses))
4797 as a percentage of (total possible naval supply + 1). The scaled losses fraction of the loser / the sum of the scaled losses
4798 forms the base of the prestige gain for the nation and the leader in charge on the winning side. The winning leader gets that
4799 value / 100 as added prestige. The winning nations gets (defineLEADER_PRESTIGE_NAVAL_GAIN + 1) x (prestige-from-tech-modifier +
4800 1) x that value. Similarly, the losing nation and leader have their prestige reduced, calculated in the same way.
4801
4802 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
4803 */
4804
4805 if(result != battle_result::indecisive) {
4806 state.world.war_get_number_of_battles(war)++;
4807
4808 auto a_leader = state.world.naval_battle_get_admiral_from_attacking_admiral(b);
4809 auto b_leader = state.world.naval_battle_get_admiral_from_defending_admiral(b);
4810
4811 if(result == battle_result::attacker_won) {
4812 auto score = std::max(0.0f,
4813 (state.world.naval_battle_get_defender_loss_value(b) - state.world.naval_battle_get_attacker_loss_value(b)) / 10.0f);
4814 if(state.world.naval_battle_get_war_attacker_is_attacker(b)) {
4815 state.world.war_get_attacker_battle_score(war) += score;
4816 } else {
4817 state.world.war_get_defender_battle_score(war) += score;
4818 }
4819
4820
4821 if(a_nation && d_nation) {
4822 nations::adjust_prestige(state, a_nation, score / 50.0f);
4823 nations::adjust_prestige(state, d_nation, score / -50.0f);
4824 adjust_leader_prestige(state, a_leader, score / 50.f / 100.f);
4825 adjust_leader_prestige(state, b_leader, score / -50.f / 100.f);
4826
4827 // Report
4828 if(state.local_player_nation == a_nation || state.local_player_nation == d_nation) {
4829 naval_battle_report rep;
4830 rep.attacker_big_losses = state.world.naval_battle_get_attacker_big_ships_lost(b);
4831 rep.attacker_big_ships = state.world.naval_battle_get_attacker_big_ships(b);
4832 rep.attacker_small_losses = state.world.naval_battle_get_attacker_small_ships_lost(b);
4833 rep.attacker_small_ships = state.world.naval_battle_get_attacker_small_ships(b);
4834 rep.attacker_transport_losses = state.world.naval_battle_get_attacker_transport_ships_lost(b);
4835 rep.attacker_transport_ships = state.world.naval_battle_get_attacker_transport_ships(b);
4836
4837 rep.defender_big_losses = state.world.naval_battle_get_defender_big_ships_lost(b);
4838 rep.defender_big_ships = state.world.naval_battle_get_defender_big_ships(b);
4839 rep.defender_small_losses = state.world.naval_battle_get_defender_small_ships_lost(b);
4840 rep.defender_small_ships = state.world.naval_battle_get_defender_small_ships(b);
4841 rep.defender_transport_losses = state.world.naval_battle_get_defender_transport_ships_lost(b);
4842 rep.defender_transport_ships = state.world.naval_battle_get_defender_transport_ships(b);
4843
4844 rep.attacker_won = (result == battle_result::attacker_won);
4845
4846 rep.attacking_nation = get_naval_battle_lead_attacker(state, b);
4847 rep.defending_nation = get_naval_battle_lead_defender(state, b);
4848 rep.attacking_admiral = state.world.naval_battle_get_admiral_from_attacking_admiral(b);
4849 rep.defending_admiral = state.world.naval_battle_get_admiral_from_defending_admiral(b);
4850
4851 rep.location = state.world.naval_battle_get_location_from_naval_battle_location(b);
4852 rep.player_on_winning_side =
4853 is_attacker(state, war, state.local_player_nation) == state.world.naval_battle_get_war_attacker_is_attacker(b);
4854
4855 if(rep.player_on_winning_side) {
4856 rep.warscore_effect = score;
4857 rep.prestige_effect = score / 50.0f;
4858 } else {
4859 rep.warscore_effect = -score;
4860 rep.prestige_effect = -score / 50.0f;
4861 }
4862 auto discard = state.naval_battle_reports.try_push(rep);
4863 }
4864 }
4865 } else if(result == battle_result::defender_won) {
4866 auto score = std::max(0.0f,
4867 (state.world.naval_battle_get_attacker_loss_value(b) - state.world.naval_battle_get_defender_loss_value(b)) / 10.0f);
4868 if(state.world.naval_battle_get_war_attacker_is_attacker(b)) {
4869 state.world.war_get_attacker_battle_score(war) += score;
4870 } else {
4871 state.world.war_get_defender_battle_score(war) += score;
4872 }
4873
4874 if(a_nation && d_nation) {
4875 nations::adjust_prestige(state, a_nation, score / -50.0f);
4876 nations::adjust_prestige(state, d_nation, score / 50.0f);
4877 adjust_leader_prestige(state, a_leader, score / -50.f / 100.f);
4878 adjust_leader_prestige(state, b_leader, score / 50.f / 100.f);
4879
4880 // Report
4881 if(state.local_player_nation == a_nation || state.local_player_nation == d_nation) {
4882 naval_battle_report rep;
4883 rep.attacker_big_losses = state.world.naval_battle_get_attacker_big_ships_lost(b);
4884 rep.attacker_big_ships = state.world.naval_battle_get_attacker_big_ships(b);
4885 rep.attacker_small_losses = state.world.naval_battle_get_attacker_small_ships_lost(b);
4886 rep.attacker_small_ships = state.world.naval_battle_get_attacker_small_ships(b);
4887 rep.attacker_transport_losses = state.world.naval_battle_get_attacker_transport_ships_lost(b);
4888 rep.attacker_transport_ships = state.world.naval_battle_get_attacker_transport_ships(b);
4889
4890 rep.defender_big_losses = state.world.naval_battle_get_defender_big_ships_lost(b);
4891 rep.defender_big_ships = state.world.naval_battle_get_defender_big_ships(b);
4892 rep.defender_small_losses = state.world.naval_battle_get_defender_small_ships_lost(b);
4893 rep.defender_small_ships = state.world.naval_battle_get_defender_small_ships(b);
4894 rep.defender_transport_losses = state.world.naval_battle_get_defender_transport_ships_lost(b);
4895 rep.defender_transport_ships = state.world.naval_battle_get_defender_transport_ships(b);
4896
4897 rep.attacker_won = (result == battle_result::attacker_won);
4898
4899 rep.attacking_nation = get_naval_battle_lead_attacker(state, b);
4900 rep.defending_nation = get_naval_battle_lead_defender(state, b);
4901 rep.attacking_admiral = state.world.naval_battle_get_admiral_from_attacking_admiral(b);
4902 rep.defending_admiral = state.world.naval_battle_get_admiral_from_defending_admiral(b);
4903
4904 rep.location = state.world.naval_battle_get_location_from_naval_battle_location(b);
4905 rep.player_on_winning_side =
4906 is_attacker(state, war, state.local_player_nation) != state.world.naval_battle_get_war_attacker_is_attacker(b);
4907
4908 if(rep.player_on_winning_side) {
4909 rep.warscore_effect = score;
4910 rep.prestige_effect = score / 50.0f;
4911 } else {
4912 rep.warscore_effect = -score;
4913 rep.prestige_effect = -score / 50.0f;
4914 }
4915 auto discard = state.naval_battle_reports.try_push(rep);
4916 }
4917 }
4918 }
4919 }
4920
4921 if(result != battle_result::indecisive) { // so we don't restart battles as the war is ending
4922 auto par_range = state.world.naval_battle_get_navy_battle_participation(b);
4923 while(par_range.begin() != par_range.end()) {
4924 auto n = (*par_range.begin()).get_navy();
4925 n.set_battle_from_navy_battle_participation(dcon::naval_battle_id{});
4926 navy_arrives_in_province(state, n, location, b);
4927 }
4928 }
4929
4930 state.world.delete_naval_battle(b);
4932
4933inline 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,
4934 0.35f, 0.40f, 0.45f, 0.50f, 0.60f, 0.70f, 0.80f, 0.90f};
4935
4936dcon::nation_id tech_nation_for_regiment(sys::state& state, dcon::regiment_id r) {
4937 auto army = state.world.regiment_get_army_from_army_membership(r);
4938 auto nation = state.world.army_get_controller_from_army_control(army);
4939 if(nation)
4940 return nation;
4941 auto rf = state.world.army_get_controller_from_army_rebel_control(army);
4942 auto ruler = state.world.rebel_faction_get_ruler_from_rebellion_within(rf);
4943 if(ruler)
4944 return ruler;
4945 return state.world.national_identity_get_nation_from_identity_holder(state.national_definitions.rebel_id);
4947
4948bool will_recieve_attrition(sys::state& state, dcon::navy_id a) {
4949 return false;
4951
4952float peacetime_attrition_limit(sys::state& state, dcon::nation_id n, dcon::province_id prov) {
4953 auto supply_limit = supply_limit_in_province(state, n, prov);
4954 auto prov_attrition_mod = state.world.province_get_modifier_values(prov, sys::provincial_mod_offsets::attrition);
4955 auto attrition_mod = 1.0f + state.world.nation_get_modifier_values(n, sys::national_mod_offsets::land_attrition);
4956
4957 return (supply_limit + prov_attrition_mod) / (attrition_mod * 3.0f);
4959
4960bool will_recieve_attrition(sys::state& state, dcon::army_id a) {
4961 auto prov = state.world.army_get_location_from_army_location(a);
4962
4963 if(state.world.province_get_siege_progress(prov) > 0.f)
4964 return true;
4965
4966 float total_army_weight = 0;
4967 for(auto ar : state.world.province_get_army_location(prov)) {
4968 if(ar.get_army().get_black_flag() == false && ar.get_army().get_is_retreating() == false &&
4969 !bool(ar.get_army().get_navy_from_army_transport())) {
4970 for(auto rg : ar.get_army().get_army_membership()) {
4971 total_army_weight += 3.0f * rg.get_regiment().get_strength();
4972 }
4973 }
4974 }
4975
4976 auto prov_attrition_mod = state.world.province_get_modifier_values(prov, sys::provincial_mod_offsets::attrition);
4977 auto ar = fatten(state.world, a);
4978
4979 auto army_controller = ar.get_controller_from_army_control();
4980 auto supply_limit = supply_limit_in_province(state, army_controller, prov);
4981 auto attrition_mod = 1.0f + army_controller.get_modifier_values(sys::national_mod_offsets::land_attrition);
4982
4983 float greatest_hostile_fort = 0.0f;
4984 for(auto adj : state.world.province_get_province_adjacency(prov)) {
4986 auto other = adj.get_connected_provinces(0) != prov ? adj.get_connected_provinces(0) : adj.get_connected_provinces(1);
4987 if(other.get_building_level(uint8_t(economy::province_building_type::fort)) > 0) {
4988 if(are_at_war(state, army_controller, other.get_nation_from_province_control())) {
4989 greatest_hostile_fort = std::max(greatest_hostile_fort, float(other.get_building_level(uint8_t(economy::province_building_type::fort))));
4990 }
4991 }
4992 }
4993 }
4994 return total_army_weight * attrition_mod - (supply_limit + prov_attrition_mod + greatest_hostile_fort) > 0;
4996
4997float relative_attrition_amount(sys::state& state, dcon::navy_id a, dcon::province_id prov) {
4998 return 0.0f;
5000
5001float local_army_weight(sys::state& state, dcon::province_id prov) {
5002 float total_army_weight = 0;
5003 for(auto ar : state.world.province_get_army_location(prov)) {
5004 if(ar.get_army().get_black_flag() == false && ar.get_army().get_is_retreating() == false &&
5005 !bool(ar.get_army().get_navy_from_army_transport())) {
5006 for(auto rg : ar.get_army().get_army_membership()) {
5007 total_army_weight += 3.0f * rg.get_regiment().get_strength();
5008 }
5009 }
5010 }
5011 return total_army_weight;
5012}
5013float local_army_weight_max(sys::state& state, dcon::province_id prov) {
5014 float total_army_weight = 0;
5015 for(auto ar : state.world.province_get_army_location(prov)) {
5016 if(ar.get_army().get_black_flag() == false && ar.get_army().get_is_retreating() == false &&
5017 !bool(ar.get_army().get_navy_from_army_transport())) {
5018 for(auto rg : ar.get_army().get_army_membership()) {
5019 total_army_weight += 3.0f;
5020 }
5021 }
5022 }
5023 return total_army_weight;
5024}
5025float local_enemy_army_weight_max(sys::state& state, dcon::province_id prov, dcon::nation_id nation) {
5026 float total_army_weight = 0;
5027 for(auto ar : state.world.province_get_army_location(prov)) {
5028 if(
5029 ar.get_army().get_black_flag() == false
5030 && ar.get_army().get_is_retreating() == false
5031 && !bool(ar.get_army().get_navy_from_army_transport())
5032 && are_at_war(state, nation, ar.get_army().get_controller_from_army_control())
5033 ) {
5034 for(auto rg : ar.get_army().get_army_membership()) {
5035 total_army_weight += 3.0f;
5036 }
5037 }
5038 }
5039 return total_army_weight;
5041
5042float relative_attrition_amount(sys::state& state, dcon::army_id a, dcon::province_id prov) {
5043 float total_army_weight = 0;
5044 for(auto ar : state.world.province_get_army_location(prov)) {
5045 if(ar.get_army().get_black_flag() == false && ar.get_army().get_is_retreating() == false &&
5046 !bool(ar.get_army().get_navy_from_army_transport())) {
5047
5048 for(auto rg : ar.get_army().get_army_membership()) {
5049 total_army_weight += 3.0f * rg.get_regiment().get_strength();
5050 }
5051 }
5052 }
5053
5054 auto prov_attrition_mod = state.world.province_get_modifier_values(prov, sys::provincial_mod_offsets::attrition);
5055
5056 auto ar = fatten(state.world, a);
5057
5058 auto army_controller = ar.get_controller_from_army_control();
5059 auto supply_limit = supply_limit_in_province(state, army_controller, prov);
5060 auto attrition_mod = 1.0f + army_controller.get_modifier_values(sys::national_mod_offsets::land_attrition);
5061
5062 float greatest_hostile_fort = 0.0f;
5063
5064 for(auto adj : state.world.province_get_province_adjacency(prov)) {
5066 auto other = adj.get_connected_provinces(0) != prov ? adj.get_connected_provinces(0) : adj.get_connected_provinces(1);
5067 if(other.get_building_level(uint8_t(economy::province_building_type::fort)) > 0) {
5068 if(are_at_war(state, army_controller, other.get_nation_from_province_control())) {
5069 greatest_hostile_fort = std::max(greatest_hostile_fort, float(other.get_building_level(uint8_t(economy::province_building_type::fort))));
5070 }
5071 }
5072 }
5073 }
5074
5075 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))
5076 + state.world.province_get_siege_progress(prov) > 0.f ? state.defines.siege_attrition : 0.0f;
5077 return value * 0.01f;
5078}
5079float attrition_amount(sys::state& state, dcon::navy_id a) {
5080 return relative_attrition_amount(state, a, state.world.navy_get_location_from_navy_location(a));
5081}
5082float attrition_amount(sys::state& state, dcon::army_id a) {
5083 return relative_attrition_amount(state, a, state.world.army_get_location_from_army_location(a));
5085
5086void apply_attrition(sys::state& state) {
5087 concurrency::parallel_for(0, state.province_definitions.first_sea_province.index(), [&](int32_t i) {
5088 dcon::province_id prov{dcon::province_id::value_base_t(i)};
5089 float total_army_weight = 0;
5090 for(auto ar : state.world.province_get_army_location(prov)) {
5091 if(ar.get_army().get_black_flag() == false && ar.get_army().get_is_retreating() == false &&
5092 !bool(ar.get_army().get_navy_from_army_transport()) && !bool(ar.get_army().get_battle_from_army_battle_participation())) {
5093
5094 for(auto rg : ar.get_army().get_army_membership()) {
5095 total_army_weight += 3.0f * rg.get_regiment().get_strength();
5096 }
5097 }
5098 }
5099
5100 auto prov_attrition_mod = state.world.province_get_modifier_values(prov, sys::provincial_mod_offsets::attrition);
5101
5102 for(auto ar : state.world.province_get_army_location(prov)) {
5103 if(ar.get_army().get_black_flag() == false && ar.get_army().get_is_retreating() == false &&
5104 !bool(ar.get_army().get_navy_from_army_transport()) && !bool(ar.get_army().get_battle_from_army_battle_participation())) {
5105
5106 auto army_controller = ar.get_army().get_controller_from_army_control();
5107 auto supply_limit = supply_limit_in_province(state, army_controller, prov);
5108 auto attrition_mod = 1.0f + army_controller.get_modifier_values(sys::national_mod_offsets::land_attrition);
5109
5110 float greatest_hostile_fort = 0.0f;
5111
5112 for(auto adj : state.world.province_get_province_adjacency(prov)) {
5113 if((adj.get_type() & (province::border::impassible_bit | province::border::coastal_bit)) == 0) {
5114 auto other = adj.get_connected_provinces(0) != prov ? adj.get_connected_provinces(0) : adj.get_connected_provinces(1);
5115 if(other.get_building_level(uint8_t(economy::province_building_type::fort)) > 0) {
5116 if(are_at_war(state, army_controller, other.get_nation_from_province_control())) {
5117 greatest_hostile_fort = std::max(greatest_hostile_fort, float(other.get_building_level(uint8_t(economy::province_building_type::fort))));
5118 }
5119 }
5120 }
5121 }
5122
5123 /*
5124 First we calculate (total-strength + leader-attrition-trait) x (attrition-modifier-from-technology + 1) -
5125 effective-province-supply-limit (rounded down to the nearest integer) + province-attrition-modifier +
5126 the-level-of-the-highest-hostile-fort-in-an-adjacent-province. We then reduce that value to at most the max-attrition
5127 modifier of the province, and finally we add define:SEIGE_ATTRITION if the army is conducting a siege. Units taking
5128 attrition lose max-strength x attrition-value x 0.01 points of strength. This strength loss is treated just like damage
5129 taken in combat, meaning that it will reduce the size of the backing pop.
5130 */
5131
5132 float attrition_value =
5133 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))
5134 + state.world.province_get_siege_progress(prov) > 0.f ? state.defines.siege_attrition : 0.0f;
5135
5136 for(auto rg : ar.get_army().get_army_membership()) {
5137 rg.get_regiment().get_pending_damage() += attrition_value * 0.01f;
5138 rg.get_regiment().get_strength() -= attrition_value * 0.01f;
5139 }
5140 }
5141 }
5142 });
5144
5145void apply_regiment_damage(sys::state& state) {
5146 for(uint32_t i = state.world.regiment_size(); i-- > 0;) {
5147 dcon::regiment_id s{ dcon::regiment_id::value_base_t(i) };
5148 if(state.world.regiment_is_valid(s)) {
5149 auto& pending_damage = state.world.regiment_get_pending_damage(s);
5150 auto& current_strength = state.world.regiment_get_strength(s);
5151
5152 if(pending_damage > 0) {
5153 auto backing_pop = state.world.regiment_get_pop_from_regiment_source(s);
5154 auto tech_nation = tech_nation_for_regiment(state, s);
5155
5156 if(backing_pop) {
5157 auto& psize = state.world.pop_get_size(backing_pop);
5158 psize -= state.defines.pop_size_per_regiment * pending_damage * state.defines.soldier_to_pop_damage /
5159 (3.0f * (1.0f + state.world.nation_get_modifier_values(tech_nation,
5160 sys::national_mod_offsets::soldier_to_pop_loss)));
5161 if(psize <= 1.0f) {
5162 state.world.delete_pop(backing_pop);
5163 }
5164 }
5165 pending_damage = 0.0f;
5166 }
5167 if(current_strength <= 0.0f) {
5168 // When a rebel regiment is destroyed, divide the militancy of the backing pop by define:REDUCTION_AFTER_DEFEAT.
5169 auto army = state.world.regiment_get_army_from_army_membership(s);
5170 auto controller = state.world.army_get_controller_from_army_control(army);
5171 auto pop_backer = state.world.regiment_get_pop_from_regiment_source(s);
5172
5173 if(!controller) {
5174 if(pop_backer) {
5175 state.world.pop_get_militancy(pop_backer) /= state.defines.reduction_after_defeat;
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 pop.get_pop().get_militancy() /= state.defines.reduction_after_defeat;
6676 //}
6677 }
6678 }
6679
6680 state.world.province_set_former_controller(prov, controller);
6681 state.world.province_set_former_rebel_controller(prov, old_rf);
6682
6683 auto new_controller = state.world.army_get_controller_from_army_control(first_army);
6684 if(new_controller && !are_at_war(state, new_controller, owner)) {
6685 new_controller = owner;
6686 }
6687
6688 auto rebel_controller = state.world.army_get_controller_from_army_rebel_control(first_army);
6689 assert(bool(new_controller) != bool(rebel_controller));
6690
6691 new_nation_controller.set(prov, new_controller);
6692 new_rebel_controller.set(prov, rebel_controller);
6693 }
6694 }
6695 });
6696
6697 province::for_each_land_province(state, [&](dcon::province_id prov) {
6698 if(auto nc = new_nation_controller.get(prov); nc) {
6699 province::set_province_controller(state, prov, nc);
6700 eject_ships(state, prov);
6701
6702 auto cc = state.world.province_get_nation_from_province_control(prov);
6703 auto oc = state.world.province_get_former_controller(prov);
6704
6705 if(cc || oc) {
6706 notification::post(state, notification::message{
6707 [prov, cc, oc,
6708 crc = state.world.province_get_rebel_faction_from_province_rebel_control(prov),
6709 orc = state.world.province_get_former_rebel_controller(prov)](sys::state& state, text::layout_base& contents) {
6710
6711 text::add_line(state, contents, "msg_siegeover_1", text::variable_type::x, prov);
6712 if(oc)
6713 text::add_line(state, contents, "msg_siegeover_2", text::variable_type::x, oc);
6714 else
6715 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)));
6716 if(cc)
6717 text::add_line(state, contents, "msg_siegeover_4", text::variable_type::x, cc);
6718 else
6719 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)));
6720 },
6721 "msg_siegeover_title",
6722 cc, oc, dcon::nation_id{},
6723 sys::message_base_type::siegeover
6724 });
6725 }
6726
6727 for(auto ar : state.world.province_get_army_location(prov)) {
6728 auto a = ar.get_army();
6729
6730 if(a.get_is_rebel_hunter()
6731 && a.get_controller_from_army_control().get_is_player_controlled()
6732 && !a.get_battle_from_army_battle_participation()
6733 && !a.get_navy_from_army_transport()
6734 && !a.get_arrival_time()) {
6735
6736 send_rebel_hunter_to_next_province(state, a, prov);
6737
6738 }
6739 }
6740
6741 /*
6742 TODO: When a province controller changes as the result of a siege, and it does not go back to the owner a random,
6743 `on_siege_win` event is fired, subject to the conditions of the events being met.
6744 */
6745 // is controler != owner ...
6746 // event::fire_fixed_event(state, );
6747 }
6748 if(auto nr = new_rebel_controller.get(prov); nr) {
6749 province::set_province_controller(state, prov, nr);
6750 eject_ships(state, prov);
6751
6752 auto cc = state.world.province_get_nation_from_province_control(prov);
6753 auto oc = state.world.province_get_former_controller(prov);
6754 auto within = state.world.rebel_faction_get_ruler_from_rebellion_within(nr);
6755 auto t = state.world.rebel_faction_get_type(nr);
6756 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))) {
6757 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)));
6758 }
6759 }
6760 });
6762
6763void update_blackflag_status(sys::state& state, dcon::province_id p) {
6764 for(auto ar : state.world.province_get_army_location(p)) {
6765 if(!ar.get_army().get_battle_from_army_battle_participation() && !ar.get_army().get_navy_from_army_transport()) {
6766 auto controller = ar.get_army().get_controller_from_army_control();
6767 ar.get_army().set_black_flag(!province::has_access_to_province(state, controller, p));
6768 }
6769 }
6771
6772void eject_ships(sys::state& state, dcon::province_id p) {
6773 auto sea_zone = state.world.province_get_port_to(p);
6774
6775 if(!sea_zone)
6776 return;
6777
6778 static std::vector<dcon::navy_id> to_eject;
6779 to_eject.clear();
6780
6781 for(auto n : state.world.province_get_navy_location(p)) {
6782 if(!province::has_naval_access_to_province(state, n.get_navy().get_controller_from_navy_control(), p)) {
6783 to_eject.push_back(n.get_navy().id);
6784 }
6785 }
6786 for(auto n : to_eject) {
6787 navy_arrives_in_province(state, n, sea_zone, dcon::naval_battle_id{});
6788
6789 for(auto a : state.world.navy_get_army_transport(n)) {
6790 a.get_army().set_location_from_army_location(sea_zone);
6791 a.get_army().get_path().clear();
6792 a.get_army().set_arrival_time(sys::date{});
6793 }
6794 }
6796
6797void increase_dig_in(sys::state& state) {
6798 if(state.current_date.value % int32_t(state.defines.dig_in_increase_each_days) == 0) {
6799 for(auto ar : state.world.in_army) {
6800 if(ar.get_is_retreating() || ar.get_black_flag() || bool(ar.get_battle_from_army_battle_participation()) ||
6801 bool(ar.get_navy_from_army_transport()) || bool(ar.get_arrival_time())) {
6802
6803 continue;
6804 }
6805 auto& current_dig_in = ar.get_dig_in();
6806 if(current_dig_in <
6807 int32_t(ar.get_controller_from_army_control().get_modifier_values(sys::national_mod_offsets::dig_in_cap))) {
6808 ++current_dig_in;
6809 }
6810 }
6811 }
6813
6814economy::commodity_set get_required_supply(sys::state& state, dcon::nation_id owner, dcon::army_id army) {
6815 uint32_t total_commodities = state.world.commodity_size();
6816
6817 economy::commodity_set commodities;
6818 for (uint32_t i = 0; i < economy::commodity_set::set_size; ++i) {
6819 commodities.commodity_amounts[i] = 0.0f;
6820 }
6821
6822 for(auto r : state.world.army_get_army_membership(army)) {
6823 auto reg = fatten(state.world, r);
6824 auto type = state.world.regiment_get_type(r.get_regiment());
6825
6826 auto o_sc_mod = std::max(0.01f, state.world.nation_get_modifier_values(owner, sys::national_mod_offsets::supply_consumption) + 1.0f);
6827 auto& supply_cost = state.military_definitions.unit_base_definitions[type].supply_cost;
6828 for(uint32_t i = 0; i < economy::commodity_set::set_size; ++i) {
6829 if(supply_cost.commodity_type[i]) {
6830 commodities.commodity_amounts[i] += supply_cost.commodity_amounts[i] * state.world.nation_get_unit_stats(owner, type).supply_consumption * o_sc_mod;
6831 commodities.commodity_type[i] = supply_cost.commodity_type[i];
6832 } else {
6833 break;
6834 }
6835 }
6836
6837 }
6838
6839 return commodities;
6841
6842economy::commodity_set get_required_supply(sys::state& state, dcon::nation_id owner, dcon::navy_id navy) {
6843 // supply amount = type_consumption * (2 - admin_eff)*[(type_consumption_mod^0.01)*land_spending]
6844 float supply_amount = .0f;
6845 int32_t amount_of_units = 0;
6846
6847 economy::commodity_set commodities;
6848 for(uint32_t i = 0; i < economy::commodity_set::set_size; ++i) {
6849 commodities.commodity_amounts[i] = 0.0f;
6850 }
6851
6852 for(auto sh : state.world.navy_get_navy_membership(navy)) {
6853 auto shp = fatten(state.world, sh.get_ship());
6854 auto type = state.world.ship_get_type(sh.get_ship());
6855
6856 if(owner) {
6857 auto o_sc_mod = std::max(0.01f, state.world.nation_get_modifier_values(owner, sys::national_mod_offsets::supply_consumption) + 1.0f);
6858 auto& supply_cost = state.military_definitions.unit_base_definitions[type].supply_cost;
6859 for(uint32_t i = 0; i < economy::commodity_set::set_size; ++i) {
6860 if(supply_cost.commodity_type[i]) {
6861 commodities.commodity_amounts[i] +=
6862 supply_cost.commodity_amounts[i] * state.world.nation_get_unit_stats(owner, type).supply_consumption *
6863 o_sc_mod;
6864 commodities.commodity_type[i] = supply_cost.commodity_type[i];
6865 } else {
6866 break;
6867 }
6868 }
6869 }
6870 };
6871
6872 return commodities;
6874
6875void recover_org(sys::state& state) {
6876 /*
6877 Units that are not in combat and not embarked recover organization daily at: (national-organization-regeneration-modifier +
6878 morale-from-tech + leader-morale-trait + 1) x the-unit's-supply-factor / 5 up to the maximum organization possible for the unit
6879 times (0.25 + 0.75 x effective land or naval spending).
6880 - Additionally, the prestige of the leader factors in morale as unit-morale
6881 + (leader-prestige x defines:LEADER_PRESTIGE_TO_MORALE_FACTOR).
6882 */
6883
6884 for(auto ar : state.world.in_army) {
6885 if(ar.get_army_battle_participation().get_battle() || ar.get_navy_from_army_transport())
6886 continue;
6887
6888 auto in_nation = ar.get_controller_from_army_control();
6889 auto tech_nation = in_nation ? in_nation : ar.get_controller_from_army_rebel_control().get_ruler_from_rebellion_within();
6890
6891 auto leader = ar.get_general_from_army_leadership();
6892 auto regen_mod = tech_nation.get_modifier_values(sys::national_mod_offsets::org_regain) +
6893 leader.get_personality().get_morale() + leader.get_background().get_morale() + 1.0f
6894 + leader.get_prestige() * state.defines.leader_prestige_to_morale_factor;
6895 auto spending_level = (in_nation ? in_nation.get_effective_land_spending() : 1.0f);
6896 auto modified_regen = regen_mod * spending_level / 150.0f;
6897
6898 for(auto reg : ar.get_army_membership()) {
6899 auto c_org = reg.get_regiment().get_org();
6900 reg.get_regiment().set_org(std::min(c_org + modified_regen, std::max(c_org, 0.25f + 0.75f * spending_level)));
6901 }
6902 }
6903
6904 for(auto ar : state.world.in_navy) {
6905 if(ar.get_navy_battle_participation().get_battle())
6906 continue;
6907
6908 auto in_nation = ar.get_controller_from_navy_control();
6909
6910 auto leader = ar.get_admiral_from_navy_leadership();
6911 auto regen_mod = in_nation.get_modifier_values(sys::national_mod_offsets::org_regain) +
6912 leader.get_personality().get_morale() + leader.get_background().get_morale() + 1.0f
6913 + leader.get_prestige() * state.defines.leader_prestige_to_morale_factor;
6914 float oversize_amount =
6915 in_nation.get_naval_supply_points() > 0
6916 ? std::min(float(in_nation.get_used_naval_supply_points()) / float(in_nation.get_naval_supply_points()), 1.75f)
6917 : 1.75f;
6918 float over_size_penalty = oversize_amount > 1.0f ? 2.0f - oversize_amount : 1.0f;
6919 auto spending_level = in_nation.get_effective_naval_spending() * over_size_penalty;
6920 auto modified_regen = regen_mod * spending_level / 150.0f;
6921
6922 for(auto reg : ar.get_navy_membership()) {
6923 auto c_org = reg.get_ship().get_org();
6924 reg.get_ship().set_org(std::min(c_org + modified_regen, std::max(c_org, 0.25f + 0.75f * spending_level)));
6925 }
6926 }
6928
6929float reinforce_amount(sys::state& state, dcon::army_id a) {
6930 auto ar = fatten(state.world, a);
6931 if(ar.get_battle_from_army_battle_participation() || ar.get_navy_from_army_transport() || ar.get_is_retreating())
6932 return 0.0f;
6933
6934 auto in_nation = ar.get_controller_from_army_control();
6935 auto tech_nation = in_nation ? in_nation : ar.get_controller_from_army_rebel_control().get_ruler_from_rebellion_within();
6936
6937 auto spending_level = (in_nation ? in_nation.get_effective_land_spending() : 1.0f);
6938
6939 float location_modifier = 1.0f;
6940 if(ar.get_location_from_army_location().get_nation_from_province_ownership() == in_nation) {
6941 location_modifier = 2.0f;
6942 } else if(ar.get_location_from_army_location().get_nation_from_province_control() == in_nation) {
6943 location_modifier = 1.0f;
6944 } else {
6945 location_modifier = 0.1f;
6946 }
6947
6948 auto combined = state.defines.reinforce_speed * spending_level * location_modifier *
6949 (1.0f + tech_nation.get_modifier_values(sys::national_mod_offsets::reinforce_speed)) *
6950 (1.0f + tech_nation.get_modifier_values(sys::national_mod_offsets::reinforce_rate));
6951
6952 return combined;
6954
6955void reinforce_regiments(sys::state& state) {
6956 /*
6957 A unit that is not retreating, not embarked, not in combat is reinforced (has its strength increased) by:
6958define:REINFORCE_SPEED x (technology-reinforcement-modifier + 1.0) x (2 if in owned province, 0.1 in an unowned port province, 1
6959in a controlled province, 0.5 if in a province adjacent to a province with military access, 0.25 in a hostile, unblockaded port,
6960and 0.1 in any other hostile province) x (national-reinforce-speed-modifier + 1) x army-supplies x (number of actual regiments /
6961max possible regiments (feels like a bug to me) or 0.5 if mobilized)
6962 */
6963
6964 for(auto ar : state.world.in_army) {
6965 if(ar.get_battle_from_army_battle_participation() || ar.get_navy_from_army_transport() || ar.get_is_retreating())
6966 continue;
6967
6968 auto in_nation = ar.get_controller_from_army_control();
6969 auto tech_nation = in_nation ? in_nation : ar.get_controller_from_army_rebel_control().get_ruler_from_rebellion_within();
6970
6971 auto spending_level = (in_nation ? in_nation.get_effective_land_spending() : 1.0f);
6972
6973 float location_modifier = 1.0f;
6974 if(ar.get_location_from_army_location().get_nation_from_province_ownership() == in_nation) {
6975 location_modifier = 2.0f;
6976 } else if(ar.get_location_from_army_location().get_nation_from_province_control() == in_nation) {
6977 location_modifier = 1.0f;
6978 } else {
6979 location_modifier = 0.1f;
6980 }
6981
6982 auto combined = state.defines.reinforce_speed * spending_level * location_modifier *
6983 (1.0f + tech_nation.get_modifier_values(sys::national_mod_offsets::reinforce_speed)) *
6984 (1.0f + tech_nation.get_modifier_values(sys::national_mod_offsets::reinforce_rate));
6985
6986 for(auto reg : ar.get_army_membership()) {
6987 auto pop = reg.get_regiment().get_pop_from_regiment_source();
6988 auto pop_size = pop.get_size();
6989 auto limit_fraction = std::max(state.defines.alice_full_reinforce, std::min(1.0f, pop_size / state.defines.pop_size_per_regiment));
6990 auto curstr = reg.get_regiment().get_strength();
6991 auto newstr = std::min(curstr + combined, limit_fraction);
6992 reg.get_regiment().set_strength(newstr);
6993 adjust_regiment_experience(state, in_nation.id, reg.get_regiment(), std::min(0.f, (newstr - curstr) * 5.f * state.defines.exp_gain_div));
6994 }
6995 }
6997
6998void repair_ships(sys::state& state) {
6999 /*
7000 A ship that is docked at a naval base is repaired (has its strength increase) by:
7001maximum-strength x (technology-repair-rate + provincial-modifier-to-repair-rate + 1) x ship-supplies x
7002(national-reinforce-speed-modifier + 1) x navy-supplies
7003 */
7004 for(auto n : state.world.in_navy) {
7005 auto nb_level = n.get_location_from_navy_location().get_building_level(uint8_t(economy::province_building_type::naval_base));
7006 if(!n.get_arrival_time() && nb_level > 0) {
7007
7008 auto in_nation = n.get_controller_from_navy_control();
7009
7010 float oversize_amount =
7011 in_nation.get_naval_supply_points() > 0
7012 ? std::min(float(in_nation.get_used_naval_supply_points()) / float(in_nation.get_naval_supply_points()), 1.75f)
7013 : 1.75f;
7014 float over_size_penalty = oversize_amount > 1.0f ? 2.0f - oversize_amount : 1.0f;
7015 auto spending_level = in_nation.get_effective_naval_spending() * over_size_penalty;
7016
7017 auto rr_mod = n.get_location_from_navy_location().get_modifier_values(sys::provincial_mod_offsets::local_repair) + 1.0f;
7018 auto reinf_mod = in_nation.get_modifier_values(sys::national_mod_offsets::reinforce_speed) + 1.0f;
7019 auto repair_val = rr_mod * reinf_mod * spending_level;
7020
7021 for(auto reg : n.get_navy_membership()) {
7022 auto curstr = reg.get_ship().get_strength();
7023 auto newstr = std::min(curstr + repair_val, 1.0f);
7024 reg.get_ship().set_strength(newstr);
7025 adjust_ship_experience(state, in_nation.id, reg.get_ship(), std::min(0.f, (newstr - curstr) * 5.f * state.defines.exp_gain_div));
7026 }
7027 }
7028 }
7030
7031void start_mobilization(sys::state& state, dcon::nation_id n) {
7032 if(state.world.nation_get_is_mobilized(n))
7033 return;
7034
7035 state.world.nation_set_is_mobilized(n, true);
7036 /*
7037 At most, national-mobilization-impact-modifier x (define:MIN_MOBILIZE_LIMIT v nation's-number-of-regiments regiments may be
7038 created by mobilization).
7039 */
7040 auto real_regs = std::max(int32_t(state.world.nation_get_active_regiments(n)), int32_t(state.defines.min_mobilize_limit));
7041 state.world.nation_set_mobilization_remaining(n,
7042 uint16_t(real_regs * state.world.nation_get_modifier_values(n, sys::national_mod_offsets::mobilization_impact)));
7043
7044 auto schedule_array = state.world.nation_get_mobilization_schedule(n);
7045 schedule_array.clear();
7046
7047 for(auto pr : state.world.nation_get_province_ownership(n)) {
7048 if(pr.get_province().get_is_colonial())
7049 continue;
7050 if(pr.get_province().get_nation_from_province_control() != n)
7051 continue;
7052 if(mobilized_regiments_possible_from_province(state, pr.get_province()) <= 0)
7053 continue;
7054
7055 schedule_array.push_back(mobilization_order{ sys::date{}, pr.get_province().id });
7056 }
7057
7058 std::sort(schedule_array.begin(), schedule_array.end(),
7059 [&, cap = state.world.nation_get_capital(n)](mobilization_order const& a, mobilization_order const& b) {
7060 auto a_dist = province::direct_distance(state, a.where, cap);
7061 auto b_dist = province::direct_distance(state, b.where, cap);
7062 if(a_dist != b_dist)
7063 return a_dist > b_dist;
7064 return a.where.value < b.where.value;
7065 });
7066
7067 int32_t delay = 0;
7068
7069 for(uint32_t count = schedule_array.size(); count-- > 0;) {
7070 /*
7071 Province by province, mobilization advances by define:MOBILIZATION_SPEED_BASE x (1 + define:MOBILIZATION_SPEED_RAILS_MULT x
7072 average-railroad-level-in-state / 5) until it reaches 1
7073 */
7074 auto province_speed = state.defines.mobilization_speed_base *
7075 float(1.0f + state.defines.mobilization_speed_rails_mult *
7076 (state.world.province_get_building_level(schedule_array[count].where, uint8_t(economy::province_building_type::railroad))) / 5.0f);
7077 auto days = std::max(1, int32_t(1.0f / province_speed));
7078 delay += days;
7079 schedule_array[count].when = state.current_date + delay;
7080 }
7081
7082 /*
7083 Mobilizing increases crisis tension by define:CRISIS_TEMPERATURE_ON_MOBILIZE
7084 */
7085 if(state.current_crisis_mode == sys::crisis_mode::heating_up) {
7086 for(auto& par : state.crisis_participants) {
7087 if(!par.id)
7088 break;
7089 if(par.id == n)
7090 state.crisis_temperature += state.defines.crisis_temperature_on_mobilize;
7091 }
7092 }
7093
7095 text::add_line(state, contents, "msg_mobilize_start_1", text::variable_type::x, n);
7096 },
7097 "msg_mobilize_start_title",
7098 n, dcon::nation_id{}, dcon::nation_id{},
7100 });
7101}
7102void end_mobilization(sys::state& state, dcon::nation_id n) {
7103 if(!state.world.nation_get_is_mobilized(n))
7104 return;
7105
7106 state.world.nation_set_is_mobilized(n, false);
7107 state.world.nation_set_mobilization_remaining(n, 0);
7108 auto schedule_array = state.world.nation_get_mobilization_schedule(n);
7109 schedule_array.clear();
7110
7111 for(auto ar : state.world.nation_get_army_control(n)) {
7112 for(auto rg : ar.get_army().get_army_membership()) {
7113 auto pop = rg.get_regiment().get_pop_from_regiment_source();
7114 if(!pop || pop.get_poptype() != state.culture_definitions.soldiers) {
7115 rg.get_regiment().set_strength(0.0f);
7116 rg.get_regiment().set_pop_from_regiment_source(dcon::pop_id{});
7117 }
7118 }
7119 }
7120
7122 text::add_line(state, contents, "msg_mobilize_end_1", text::variable_type::x, n);
7123 },
7124 "msg_mobilize_end_title",
7125 n, dcon::nation_id{}, dcon::nation_id{},
7127 });
7128}
7129void advance_mobilizations(sys::state& state) {
7130 for(auto n : state.world.in_nation) {
7131 auto& to_mobilize = n.get_mobilization_remaining();
7132 if(to_mobilize > 0) {
7133 auto schedule = n.get_mobilization_schedule();
7134 auto s_size = schedule.size();
7135 if(s_size > 0) {
7136 auto back = schedule[s_size - 1];
7137 if(state.current_date == back.when) {
7138 schedule.pop_back();
7139 bool mob_infantry = state.world.nation_get_active_unit(n, state.military_definitions.infantry);
7140
7141 // mobilize the province
7142
7143 if(state.world.province_get_nation_from_province_control(back.where) ==
7144 state.world.province_get_nation_from_province_ownership(back.where)) { // only if un occupied
7145 /*
7146 In those provinces, mobilized regiments come from non-soldier, non-slave, poor-strata pops with a culture that is
7147 either the primary culture of the nation or an accepted culture.
7148 */
7149 for(auto pop : state.world.province_get_pop_location(back.where)) {
7150 if(pop_eligible_for_mobilization(state, pop.get_pop())) {
7151 /*
7152 The number of regiments these pops can provide is determined by pop-size x mobilization-size /
7153 define:POP_SIZE_PER_REGIMENT.
7154 */
7155 auto available = int32_t(pop.get_pop().get_size() * mobilization_size(state, n) / state.defines.pop_size_per_regiment);
7156 if(available > 0) {
7157
7158 bool army_is_new = false;
7159
7160 auto a = [&]() {
7161 for(auto ar : state.world.province_get_army_location(back.where)) {
7162 if(ar.get_army().get_controller_from_army_control() == n)
7163 return ar.get_army().id;
7164 }
7165 auto new_army = fatten(state.world, state.world.create_army());
7166 new_army.set_controller_from_army_control(n);
7167 new_army.set_is_ai_controlled(n.get_mobilized_is_ai_controlled()); //toggle
7168
7169 army_is_new = true;
7170 return new_army.id;
7171 }();
7172
7173 while(available > 0 && to_mobilize > 0) {
7174 auto new_reg = military::create_new_regiment(state, dcon::nation_id{}, mob_infantry ?state.military_definitions.infantry : state.military_definitions.irregular);
7175 state.world.regiment_set_org(new_reg, 0.1f);
7176 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));
7177 state.world.try_create_army_membership(new_reg, a);
7178 auto p = pop.get_pop();
7179 assert(p);
7180 state.world.try_create_regiment_source(new_reg, p);
7181
7182 --available;
7183 --to_mobilize;
7184 }
7185 if(army_is_new) {
7186 military::army_arrives_in_province(state, a, back.where, military::crossing_type::none, dcon::land_battle_id{});
7187 military::move_land_to_merge(state, n, a, back.where, dcon::province_id{});
7188 }
7189 }
7190 }
7191
7192 if(to_mobilize == 0)
7193 break;
7194 }
7195 }
7196 }
7197 } else {
7198 to_mobilize = 0;
7199 }
7200 }
7201 }
7203
7204bool can_retreat_from_battle(sys::state& state, dcon::naval_battle_id battle) {
7205 return (state.world.naval_battle_get_start_date(battle) + days_before_retreat < state.current_date);
7206}
7207bool can_retreat_from_battle(sys::state& state, dcon::land_battle_id battle) {
7208 return (state.world.land_battle_get_start_date(battle) + days_before_retreat < state.current_date);
7210
7211bool 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) {
7212 for(auto wg : state.world.war_get_wargoals_attached(w)) {
7213 if(wg.get_wargoal().get_target_nation() == target) {
7214 if(auto bits = wg.get_wargoal().get_type().get_type_bits(); (bits & (cb_flag::po_transfer_provinces | cb_flag::po_demand_state)) != 0) {
7215 if((bits & cb_flag::all_allowed_states) != 0) {
7216 auto state_filter = wg.get_wargoal().get_type().get_allowed_states();
7217
7218 if((bits & cb_flag::po_transfer_provinces) != 0) {
7219 auto holder = state.world.national_identity_get_nation_from_identity_holder(wg.get_wargoal().get_associated_tag());
7220
7221 if(state_filter) {
7222 for(auto si : state.world.nation_get_state_ownership(target)) {
7223 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))) {
7224 return true;
7225 }
7226 }
7227 }
7228 } else if((bits & cb_flag::po_demand_state) != 0) {
7229
7230 if(state_filter) {
7231 for(auto si : state.world.nation_get_state_ownership(target)) {
7232 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))) {
7233 return true;
7234 }
7235 }
7236 }
7237 }
7238 } else {
7239 if(wg.get_wargoal().get_associated_state() == cb_state)
7240 return true;
7241 }
7242 }
7243 if((wg.get_wargoal().get_type().get_type_bits() & cb_flag::po_annex) != 0) {
7244 return true;
7245 }
7246 }
7247 }
7248
7249 return false;
7251
7252bool 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) {
7253
7254 if(cb_state) { // ensure that the state will not be annexed, transferred, or liberated
7255 if(state_claimed_in_war(state, w, source, target, cb_state))
7256 return true;
7257 }
7258
7259 // ensure no exact duplicate
7260 for(auto wg : state.world.war_get_wargoals_attached(w)) {
7261 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) {
7262 return true;
7263 }
7264 }
7265
7266 // annexing a state... but we already added for annexing the whole nation
7267 if((state.world.cb_type_get_type_bits(cb_type) & cb_flag::po_demand_state) != 0) {
7268 for(auto wg : state.world.war_get_wargoals_attached(w)) {
7269 if((wg.get_wargoal().get_type().get_type_bits() & cb_flag::po_annex) != 0 && wg.get_wargoal().get_target_nation() == target) {
7270 return true;
7271 }
7272 }
7273 }
7274
7275 // if all applicable states: ensure that at least one state affected will not be transferred, annexed, or liberated
7276 auto bits = state.world.cb_type_get_type_bits(cb_type);
7277 if((bits & cb_flag::po_annex) != 0) {
7278 for(auto si : state.world.nation_get_state_ownership(target)) {
7279 if(!state_claimed_in_war(state, w, source, target, si.get_state().get_definition()))
7280 return false;
7281 }
7282 return true;
7283 }
7284 if((bits & cb_flag::all_allowed_states) != 0) {
7285 auto state_filter = state.world.cb_type_get_allowed_states(cb_type);
7286 if((bits & cb_flag::po_transfer_provinces) != 0) {
7287 auto target_tag = state.world.nation_get_identity_from_identity_holder(target);
7288 auto holder = state.world.national_identity_get_nation_from_identity_holder(cb_tag);
7289
7290 if(state_filter) {
7291 for(auto si : state.world.nation_get_state_ownership(target)) {
7292 if(trigger::evaluate(state, state_filter, trigger::to_generic(si.get_state().id), trigger::to_generic(source), trigger::to_generic(holder))
7293 && !state_claimed_in_war(state, w, source, target, si.get_state().get_definition())) {
7294
7295 return false;
7296 }
7297 }
7298 return true;
7299 }
7300 } else if((bits & cb_flag::po_demand_state) != 0) {
7301 auto target_tag = state.world.nation_get_identity_from_identity_holder(target);
7302
7303 if(state_filter) {
7304 for(auto si : state.world.nation_get_state_ownership(target)) {
7305 if(trigger::evaluate(state, state_filter, trigger::to_generic(si.get_state().id), trigger::to_generic(source), trigger::to_generic(source))
7306 && !state_claimed_in_war(state, w, source, target, si.get_state().get_definition())) {
7307
7308 return false;
7309 }
7310 }
7311 return true;
7312 }
7313 }
7314 }
7315
7316 return false;
7317}
7319
7321 if(state.military_definitions.pending_blackflag_update) {
7322 state.military_definitions.pending_blackflag_update = false;
7323
7324 for(auto a : state.world.in_army) {
7325 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())) {
7326 a.set_black_flag(true);
7327 } else {
7328 a.set_black_flag(false);
7329 }
7330 }
7331 }
7333
7334bool rebel_army_in_province(sys::state& state, dcon::province_id p) {
7335 for(auto ar : state.world.province_get_army_location(p)) {
7336 if(ar.get_army().get_controller_from_army_rebel_control())
7337 return true;
7338 }
7339 return false;
7340}
7341dcon::province_id find_land_rally_pt(sys::state& state, dcon::nation_id by, dcon::province_id start) {
7342 float distance = 2.0f;
7343 dcon::province_id closest;
7344 auto region = state.world.province_get_connected_region_id(start);
7345
7346 for(auto p : state.world.nation_get_province_ownership(by)) {
7347 if(!p.get_province().get_land_rally_point())
7348 continue;
7349 if(p.get_province().get_connected_region_id() != region)
7350 continue;
7351 if(p.get_province().get_nation_from_province_control() != by)
7352 continue;
7353 if(auto dist = province::sorting_distance(state, start, p.get_province()); !closest || dist < distance) {
7354 distance = dist;
7355 closest = p.get_province();
7356 }
7357 }
7358
7359 return closest;
7360}
7361dcon::province_id find_naval_rally_pt(sys::state& state, dcon::nation_id by, dcon::province_id start) {
7362 float distance = 2.0f;
7363 dcon::province_id closest;
7364
7365 for(auto p : state.world.nation_get_province_ownership(by)) {
7366 if(!p.get_province().get_naval_rally_point())
7367 continue;
7368 if(p.get_province().get_nation_from_province_control() != by)
7369 continue;
7370 if(auto dist = province::sorting_distance(state, start, p.get_province()); !closest || dist < distance) {
7371 distance = dist;
7372 closest = p.get_province();
7373 }
7374 }
7375
7376 return closest;
7377}
7378void move_land_to_merge(sys::state& state, dcon::nation_id by, dcon::army_id a, dcon::province_id start, dcon::province_id dest) {
7379 if(state.world.nation_get_is_player_controlled(by) == false)
7380 return; // AI doesn't use rally points or templates
7381 if(!dest)
7382 dest = find_land_rally_pt(state, by, start);
7383 if(!dest || state.world.province_get_nation_from_province_control(dest) != by)
7384 return;
7385 if(state.world.army_get_battle_from_army_battle_participation(a))
7386 return;
7387
7388 if(dest == start) { // merge in place
7389 for(auto ar : state.world.province_get_army_location(start)) {
7390 if(ar.get_army().get_controller_from_army_control() == by && ar.get_army() != a) {
7391 auto regs = state.world.army_get_army_membership(a);
7392 while(regs.begin() != regs.end()) {
7393 (*regs.begin()).set_army(ar.get_army());
7394 }
7395 return;
7396 }
7397 }
7398 } else {
7399 auto path = province::make_land_path(state, start, dest, by, a);
7400 if(path.empty())
7401 return;
7402
7403 auto existing_path = state.world.army_get_path(a);
7404 auto new_size = uint32_t(path.size());
7405 existing_path.resize(new_size);
7406
7407 for(uint32_t k = 0; k < new_size; ++k) {
7408 assert(path[k]);
7409 existing_path[k] = path[k];
7410 }
7411 state.world.army_set_arrival_time(a, military::arrival_time_to(state, a, path.back()));
7412 state.world.army_set_moving_to_merge(a, true);
7414}
7415void move_navy_to_merge(sys::state& state, dcon::nation_id by, dcon::navy_id a, dcon::province_id start, dcon::province_id dest) {
7416 if(state.world.nation_get_is_player_controlled(by) == false)
7417 return; // AI doesn't use rally points or templates
7418 if(!dest)
7419 dest = find_naval_rally_pt(state, by, start);
7420 if(!dest || state.world.province_get_nation_from_province_control(dest) != by)
7421 return;
7422
7423 if(dest == start) { // merge in place
7424 for(auto ar : state.world.province_get_navy_location(start)) {
7425 if(ar.get_navy().get_controller_from_navy_control() == by && ar.get_navy() != a) {
7426 auto regs = state.world.navy_get_navy_membership(a);
7427 while(regs.begin() != regs.end()) {
7428 (*regs.begin()).set_navy(ar.get_navy());
7429 }
7430 return;
7431 }
7432 }
7433 } else {
7434 auto path = province::make_naval_path(state, start, dest);
7435 if(path.empty())
7436 return;
7437
7438 auto existing_path = state.world.navy_get_path(a);
7439 auto new_size = uint32_t(path.size());
7440 existing_path.resize(new_size);
7441
7442 for(uint32_t k = 0; k < new_size; ++k) {
7443 assert(path[k]);
7444 existing_path[k] = path[k];
7445 }
7446 state.world.navy_set_arrival_time(a, military::arrival_time_to(state, a, path.back()));
7447 state.world.navy_set_moving_to_merge(a, true);
7448 }
7450
7451bool pop_eligible_for_mobilization(sys::state& state, dcon::pop_id p) {
7452 auto const pop = dcon::fatten(state.world, p);
7453 return pop.get_poptype() != state.culture_definitions.soldiers
7454 && pop.get_poptype() != state.culture_definitions.slaves
7455 && pop.get_is_primary_or_accepted_culture()
7456 && pop.get_poptype().get_strata() == uint8_t(culture::pop_strata::poor);
7458
7459void disband_regiment_w_pop_death(sys::state& state, dcon::regiment_id reg_id) {
7460 auto base_pop = state.world.regiment_get_pop_from_regiment_source(reg_id);
7461 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));
7462 state.world.delete_regiment(reg_id);
7463}
7464
7465} // 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:2507
float estimate_army_offensive_strength(sys::state &state, dcon::army_id a)
Definition: ai.cpp:4461
army_activity
Definition: constants.hpp:622
void gather_to_battle(sys::state &state, dcon::nation_id n, dcon::province_id p)
Definition: ai.cpp:4346
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:5361
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:3629
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:3993
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:6770
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:4524
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:3447
void update_ticking_war_score(sys::state &state)
Definition: military.cpp:3487
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:4260
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:7202
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:4468
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:4280
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:3890
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:7127
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:7376
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:4240
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:4501
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:7413
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:7100
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:4418
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:4995
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:7449
bool retreat(sys::state &state, dcon::navy_id n)
Definition: military.cpp:4195
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:7209
dcon::nation_id get_land_battle_lead_defender(sys::state &state, dcon::land_battle_id b)
Definition: military.cpp:4311
dcon::province_id find_naval_rally_pt(sys::state &state, dcon::nation_id by, dcon::province_id start)
Definition: military.cpp:7359
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:6761
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:320
void change_government_type(sys::state &state, dcon::nation_id n, dcon::government_type_id new_type)
Definition: politics.cpp:413
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:5810
uint uint32_t
uchar uint8_t
float commodity_amounts[set_size]
dcon::commodity_id commodity_type[set_size]
static constexpr uint32_t set_size