From 7280a3b680739f0e74d212c6795d0acc6a0f6c54 Mon Sep 17 00:00:00 2001 From: Instafluff Date: Tue, 14 Jul 2026 22:30:47 -0700 Subject: [PATCH 1/8] Implement war weariness exclusion, parsing and logic --- C3X.h | 4 ++++ civ_prog_objects.csv | 4 +++- default.c3x_config.ini | 6 ++++++ injected_code.c | 29 ++++++++++++++++++++++++++--- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/C3X.h b/C3X.h index 3ebc7634..7af501af 100644 --- a/C3X.h +++ b/C3X.h @@ -289,6 +289,7 @@ struct c3x_config { int limit_units_per_tile[3]; // Limits for land, sea, and air units respectively bool exclude_cities_from_units_per_tile_limit; struct table exclude_types_from_units_per_tile_limit; + struct table exclude_types_from_units_war_weariness; // Table mapping unit type IDs to 1's; used as a hash set bool enable_free_buildings_from_small_wonders; bool enable_stack_unit_commands; bool skip_repeated_tile_improv_replacement_asks; @@ -2128,6 +2129,9 @@ struct injected_state { // Normally false. When true, calls to Unit::score_kill will not enslave. bool do_not_enslave_units; + // Set by patch_Unit_score_kill while Unit::score_kill is running so the war weariness call inside can inspect the victim type. + int war_weariness_kill_victim_type_id; + // If limit_unit_loading_to_one_transport_per_turn is on, maps unit IDs to the ID of the transport unit they're tied to for the current turn. struct table unit_transport_ties; diff --git a/civ_prog_objects.csv b/civ_prog_objects.csv index 85581bb3..f02c3180 100644 --- a/civ_prog_objects.csv +++ b/civ_prog_objects.csv @@ -437,7 +437,9 @@ repl call, 0x5B945B, 0x5C7EDE, 0x5B916B, "Trade_Net_get_move_cost_after_zoc", " repl call, 0x5B9471, 0x5C7EF4, 0x5B9181, "Unit_can_move_after_zoc", "" repl call, 0x5B94E4, 0x5C7F67, 0x5B91F4, "Unit_get_max_move_points_for_bridge_exit", "" repl vptr, 0x66DD40, 0x68AE20, 0x66DD40, "Unit_teleport", "int (__fastcall *) (Unit * this, int edx, int tile_x, int tile_y, Unit * unit_telepad)" -define, 0x5BEF00, 0x5CDB10, 0x5BEC10, "Unit_score_kill", "void (__fastcall *) (Unit * this, int edx, Unit * victim, bool was_attacking)" +inlead, 0x5BEF00, 0x5CDB10, 0x5BEC10, "Unit_score_kill", "void (__fastcall *) (Unit * this, int edx, Unit * victim, bool was_attacking)" +define, 0x5631B0, 0x0, 0x0, "Leader_add_recent_war_weariness", "void (__fastcall *) (Leader * this, int edx, int victim_civ_id, int amount, int victim_civ_offset, int our_civ_id)" +repl call, 0x5BEF00, 0x0, 0x0, "Leader_add_recent_war_weariness_for_kill", "" define, 0x5BF558, 0x5CE0EA, 0x5BF268, "ADDR_EXISTING_BATTLE_CREATED_UNIT_CHECK", "void *" inlead, 0x4A1AE0, 0x4A86E0, 0x4A1B70, "Fighter_find_defensive_bombarder", "Unit * (__fastcall *) (Fighter * this, int edx, Unit * attacker, Unit * defender)" define, 0x5BCA90, 0x5CB620, 0x5BC7A0, "Unit_get_containing_army", "Unit * (__fastcall *) (Unit * this)" diff --git a/default.c3x_config.ini b/default.c3x_config.ini index f926c44a..95a1cbb1 100644 --- a/default.c3x_config.ini +++ b/default.c3x_config.ini @@ -429,6 +429,12 @@ exclude_cities_from_units_per_tile_limit = false ; This option does nothing if limit_units_per_tile is set to false. exclude_types_from_units_per_tile_limit = [] +; Prevents the loss of certain unit types from adding war weariness. This does not change other war weariness sources such as city bombardment, +; captured cities, or tile improvement destruction. This option is a space separated list of unit type names. Multi-word names must be wrapped in quotes. +; For example: +; [Drone "Cruise Missile"] +exclude_types_from_units_war_weariness = [] + enable_free_buildings_from_small_wonders = true allow_stealth_attack_against_single_unit = false disallow_trespassing = false diff --git a/injected_code.c b/injected_code.c index 4ec86346..bfcd0d35 100644 --- a/injected_code.c +++ b/injected_code.c @@ -692,7 +692,9 @@ reset_to_base_config () for (int n = 0; n < ARRAY_LEN (cc->limit_units_per_tile); n++) cc->limit_units_per_tile[n] = 0; + is->war_weariness_kill_victim_type_id = -1; table_deinit (&cc->exclude_types_from_units_per_tile_limit); + table_deinit (&cc->exclude_types_from_units_war_weariness); for (int n = 0; n < COUNT_PERFUME_KINDS; n++) stable_deinit (&cc->perfume_specs[n]); @@ -2862,6 +2864,9 @@ load_config (char const * file_path, int path_is_relative_to_mod_dir) } else if (slice_matches_str (&p.key, "exclude_types_from_units_per_tile_limit")) { if (! read_unit_type_list (&value, &unrecognized_lines, &cfg->exclude_types_from_units_per_tile_limit)) handle_config_error (&p, CPE_BAD_VALUE); + } else if (slice_matches_str (&p.key, "exclude_types_from_units_war_weariness")) { + if (! read_unit_type_list (&value, &unrecognized_lines, &cfg->exclude_types_from_units_war_weariness)) + handle_config_error (&p, CPE_BAD_VALUE); } else if (slice_matches_str (&p.key, "limit_defensive_retreat_on_water_to_types")) { if (! read_unit_type_list (&value, &unrecognized_lines, &cfg->limit_defensive_retreat_on_water_to_types)) handle_config_error (&p, CPE_BAD_VALUE); @@ -30568,6 +30573,14 @@ patch_Unit_can_move_after_zoc (Unit * this, int edx, int neighbor_index, int par AMV_1; } +void __fastcall +patch_Unit_score_kill (Unit * this, int edx, Unit * victim, bool was_attacking) +{ + is->war_weariness_kill_victim_type_id = unit_has_valid_type_id (victim) ? victim->Body.UnitTypeID : -1; + Unit_score_kill (this, __, victim, was_attacking); + is->war_weariness_kill_victim_type_id = -1; +} + // Checks unit's HP after it was possibly hit by ZoC and deals with the consequences if it's dead. Does nothing if config option to make ZoC lethal // isn't set or if interceptor is NULL. Returns true if the unit was killed, false otherwise. bool @@ -30583,7 +30596,7 @@ check_life_after_zoc (Unit * unit, Unit * interceptor) ((p_bic_data->UnitTypes[unit->Body.UnitTypeID].Unit_Class != interceptor_type->Unit_Class) || (interceptor_type->Bombard_Strength >= Unit_get_attack_strength (interceptor)))) is->do_not_enslave_units = true; - Unit_score_kill (interceptor, __, unit, false); + patch_Unit_score_kill (interceptor, __, unit, false); is->do_not_enslave_units = false; if ((! is_online_game ()) && Fighter_check_combat_anim_visibility (&p_bic_data->fighter, __, interceptor, unit, true)) @@ -31436,6 +31449,16 @@ patch_Unit_get_defense_strength (Unit * this) return base; } +void __fastcall +patch_Leader_add_recent_war_weariness_for_kill (Leader * this, int victim_civ_id, int amount, int victim_civ_offset, int our_civ_id) +{ + if ((is->war_weariness_kill_victim_type_id >= 0) && + itable_look_up_or (&is->current_config.exclude_types_from_units_war_weariness, is->war_weariness_kill_victim_type_id, 0)) + return; + + Leader_add_recent_war_weariness (this, __, victim_civ_id, amount, victim_civ_offset, our_civ_id); +} + void __fastcall patch_Unit_score_kill_by_defender (Unit * this, int edx, Unit * victim, bool was_attacking) { @@ -31443,12 +31466,12 @@ patch_Unit_score_kill_by_defender (Unit * this, int edx, Unit * victim, bool was // for that kill to the defensive bombarder not the defender in combat. if (is->dbe.defender_was_destroyed) { is->do_not_enslave_units = is->current_config.prevent_enslaving_by_bombardment; - Unit_score_kill (is->dbe.bombarder, __, victim, was_attacking); + patch_Unit_score_kill (is->dbe.bombarder, __, victim, was_attacking); is->do_not_enslave_units = false; p_bic_data->fighter.play_animations = is->dbe.saved_animation_setting; } else - Unit_score_kill (this, __, victim, was_attacking); + patch_Unit_score_kill (this, __, victim, was_attacking); } void __fastcall From cd2a7fde738a243e699fe27f785f7eaaaff572f5 Mon Sep 17 00:00:00 2001 From: Instafluff Date: Tue, 14 Jul 2026 22:37:02 -0700 Subject: [PATCH 2/8] Update repl call func name for clarity --- civ_prog_objects.csv | 2 +- injected_code.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/civ_prog_objects.csv b/civ_prog_objects.csv index f02c3180..3607b2f9 100644 --- a/civ_prog_objects.csv +++ b/civ_prog_objects.csv @@ -439,7 +439,7 @@ repl call, 0x5B94E4, 0x5C7F67, 0x5B91F4, "Unit_get_max_move_points_for_bridge_ex repl vptr, 0x66DD40, 0x68AE20, 0x66DD40, "Unit_teleport", "int (__fastcall *) (Unit * this, int edx, int tile_x, int tile_y, Unit * unit_telepad)" inlead, 0x5BEF00, 0x5CDB10, 0x5BEC10, "Unit_score_kill", "void (__fastcall *) (Unit * this, int edx, Unit * victim, bool was_attacking)" define, 0x5631B0, 0x0, 0x0, "Leader_add_recent_war_weariness", "void (__fastcall *) (Leader * this, int edx, int victim_civ_id, int amount, int victim_civ_offset, int our_civ_id)" -repl call, 0x5BEF00, 0x0, 0x0, "Leader_add_recent_war_weariness_for_kill", "" +repl call, 0x5BEF00, 0x0, 0x0, "Leader_add_recent_war_weariness_in_Unit_score_kill", "" define, 0x5BF558, 0x5CE0EA, 0x5BF268, "ADDR_EXISTING_BATTLE_CREATED_UNIT_CHECK", "void *" inlead, 0x4A1AE0, 0x4A86E0, 0x4A1B70, "Fighter_find_defensive_bombarder", "Unit * (__fastcall *) (Fighter * this, int edx, Unit * attacker, Unit * defender)" define, 0x5BCA90, 0x5CB620, 0x5BC7A0, "Unit_get_containing_army", "Unit * (__fastcall *) (Unit * this)" diff --git a/injected_code.c b/injected_code.c index bfcd0d35..6df61be1 100644 --- a/injected_code.c +++ b/injected_code.c @@ -31450,7 +31450,7 @@ patch_Unit_get_defense_strength (Unit * this) } void __fastcall -patch_Leader_add_recent_war_weariness_for_kill (Leader * this, int victim_civ_id, int amount, int victim_civ_offset, int our_civ_id) +patch_Leader_add_recent_war_weariness_in_Unit_score_kill (Leader * this, int victim_civ_id, int amount, int victim_civ_offset, int our_civ_id) { if ((is->war_weariness_kill_victim_type_id >= 0) && itable_look_up_or (&is->current_config.exclude_types_from_units_war_weariness, is->war_weariness_kill_victim_type_id, 0)) From c1830903063bb65d51ed9b0922b54faf6ab8962e Mon Sep 17 00:00:00 2001 From: Instafluff Date: Tue, 14 Jul 2026 22:38:28 -0700 Subject: [PATCH 3/8] Shorten func name for consistency --- civ_prog_objects.csv | 2 +- injected_code.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/civ_prog_objects.csv b/civ_prog_objects.csv index 3607b2f9..11f05160 100644 --- a/civ_prog_objects.csv +++ b/civ_prog_objects.csv @@ -439,7 +439,7 @@ repl call, 0x5B94E4, 0x5C7F67, 0x5B91F4, "Unit_get_max_move_points_for_bridge_ex repl vptr, 0x66DD40, 0x68AE20, 0x66DD40, "Unit_teleport", "int (__fastcall *) (Unit * this, int edx, int tile_x, int tile_y, Unit * unit_telepad)" inlead, 0x5BEF00, 0x5CDB10, 0x5BEC10, "Unit_score_kill", "void (__fastcall *) (Unit * this, int edx, Unit * victim, bool was_attacking)" define, 0x5631B0, 0x0, 0x0, "Leader_add_recent_war_weariness", "void (__fastcall *) (Leader * this, int edx, int victim_civ_id, int amount, int victim_civ_offset, int our_civ_id)" -repl call, 0x5BEF00, 0x0, 0x0, "Leader_add_recent_war_weariness_in_Unit_score_kill", "" +repl call, 0x5BEF00, 0x0, 0x0, "Leader_add_recent_war_weariness_in_score_kill", "" define, 0x5BF558, 0x5CE0EA, 0x5BF268, "ADDR_EXISTING_BATTLE_CREATED_UNIT_CHECK", "void *" inlead, 0x4A1AE0, 0x4A86E0, 0x4A1B70, "Fighter_find_defensive_bombarder", "Unit * (__fastcall *) (Fighter * this, int edx, Unit * attacker, Unit * defender)" define, 0x5BCA90, 0x5CB620, 0x5BC7A0, "Unit_get_containing_army", "Unit * (__fastcall *) (Unit * this)" diff --git a/injected_code.c b/injected_code.c index 6df61be1..92fc04c5 100644 --- a/injected_code.c +++ b/injected_code.c @@ -31450,7 +31450,7 @@ patch_Unit_get_defense_strength (Unit * this) } void __fastcall -patch_Leader_add_recent_war_weariness_in_Unit_score_kill (Leader * this, int victim_civ_id, int amount, int victim_civ_offset, int our_civ_id) +patch_Leader_add_recent_war_weariness_in_score_kill (Leader * this, int victim_civ_id, int amount, int victim_civ_offset, int our_civ_id) { if ((is->war_weariness_kill_victim_type_id >= 0) && itable_look_up_or (&is->current_config.exclude_types_from_units_war_weariness, is->war_weariness_kill_victim_type_id, 0)) From a366b2e55c31895bbe46dd0abb5dd4618fe4ca2a Mon Sep 17 00:00:00 2001 From: Instafluff Date: Thu, 16 Jul 2026 07:11:29 -0700 Subject: [PATCH 4/8] Fix function arguments and mem address --- civ_prog_objects.csv | 4 ++-- injected_code.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/civ_prog_objects.csv b/civ_prog_objects.csv index 11f05160..8c24f581 100644 --- a/civ_prog_objects.csv +++ b/civ_prog_objects.csv @@ -438,8 +438,8 @@ repl call, 0x5B9471, 0x5C7EF4, 0x5B9181, "Unit_can_move_after_zoc", "" repl call, 0x5B94E4, 0x5C7F67, 0x5B91F4, "Unit_get_max_move_points_for_bridge_exit", "" repl vptr, 0x66DD40, 0x68AE20, 0x66DD40, "Unit_teleport", "int (__fastcall *) (Unit * this, int edx, int tile_x, int tile_y, Unit * unit_telepad)" inlead, 0x5BEF00, 0x5CDB10, 0x5BEC10, "Unit_score_kill", "void (__fastcall *) (Unit * this, int edx, Unit * victim, bool was_attacking)" -define, 0x5631B0, 0x0, 0x0, "Leader_add_recent_war_weariness", "void (__fastcall *) (Leader * this, int edx, int victim_civ_id, int amount, int victim_civ_offset, int our_civ_id)" -repl call, 0x5BEF00, 0x0, 0x0, "Leader_add_recent_war_weariness_in_score_kill", "" +define, 0x5631B0, 0x0, 0x0, "Leader_add_recent_war_weariness", "void (__fastcall *) (Leader * this, int edx, int victim_civ_id, int amount)" +repl call, 0x5BEF49, 0x0, 0x0, "Leader_add_recent_war_weariness_in_score_kill", "" define, 0x5BF558, 0x5CE0EA, 0x5BF268, "ADDR_EXISTING_BATTLE_CREATED_UNIT_CHECK", "void *" inlead, 0x4A1AE0, 0x4A86E0, 0x4A1B70, "Fighter_find_defensive_bombarder", "Unit * (__fastcall *) (Fighter * this, int edx, Unit * attacker, Unit * defender)" define, 0x5BCA90, 0x5CB620, 0x5BC7A0, "Unit_get_containing_army", "Unit * (__fastcall *) (Unit * this)" diff --git a/injected_code.c b/injected_code.c index 92fc04c5..292e3d07 100644 --- a/injected_code.c +++ b/injected_code.c @@ -31450,13 +31450,13 @@ patch_Unit_get_defense_strength (Unit * this) } void __fastcall -patch_Leader_add_recent_war_weariness_in_score_kill (Leader * this, int victim_civ_id, int amount, int victim_civ_offset, int our_civ_id) +patch_Leader_add_recent_war_weariness_in_score_kill (Leader * this, int edx, int victim_civ_id, int amount) { if ((is->war_weariness_kill_victim_type_id >= 0) && itable_look_up_or (&is->current_config.exclude_types_from_units_war_weariness, is->war_weariness_kill_victim_type_id, 0)) return; - Leader_add_recent_war_weariness (this, __, victim_civ_id, amount, victim_civ_offset, our_civ_id); + Leader_add_recent_war_weariness (this, __, victim_civ_id, amount); } void __fastcall From 809f3ee0cc842d62aa0bad97cefeb868642ceafb Mon Sep 17 00:00:00 2001 From: Insta Fluff Date: Thu, 16 Jul 2026 09:44:10 -0700 Subject: [PATCH 5/8] Remove unnecessary comment --- C3X.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/C3X.h b/C3X.h index 7af501af..fbc2b1ee 100644 --- a/C3X.h +++ b/C3X.h @@ -289,7 +289,7 @@ struct c3x_config { int limit_units_per_tile[3]; // Limits for land, sea, and air units respectively bool exclude_cities_from_units_per_tile_limit; struct table exclude_types_from_units_per_tile_limit; - struct table exclude_types_from_units_war_weariness; // Table mapping unit type IDs to 1's; used as a hash set + struct table exclude_types_from_units_war_weariness; bool enable_free_buildings_from_small_wonders; bool enable_stack_unit_commands; bool skip_repeated_tile_improv_replacement_asks; From 231c5bcde11f17c7237577cdcb909f83910629c3 Mon Sep 17 00:00:00 2001 From: Insta Fluff Date: Fri, 17 Jul 2026 10:33:10 -0700 Subject: [PATCH 6/8] Add patches for other cases where an HP of 1 also causes WW --- C3X.h | 4 +++- civ_prog_objects.csv | 10 ++++++-- injected_code.c | 54 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/C3X.h b/C3X.h index fbc2b1ee..5e32c2c7 100644 --- a/C3X.h +++ b/C3X.h @@ -2130,7 +2130,9 @@ struct injected_state { bool do_not_enslave_units; // Set by patch_Unit_score_kill while Unit::score_kill is running so the war weariness call inside can inspect the victim type. - int war_weariness_kill_victim_type_id; + // war_weariness_fight_context is used for similar purposes but specific to Fighter_fight + int war_weariness_subject_unit_type_id; + Fighter * war_weariness_fight_context; // If limit_unit_loading_to_one_transport_per_turn is on, maps unit IDs to the ID of the transport unit they're tied to for the current turn. struct table unit_transport_ties; diff --git a/civ_prog_objects.csv b/civ_prog_objects.csv index 8c24f581..ea0ee67e 100644 --- a/civ_prog_objects.csv +++ b/civ_prog_objects.csv @@ -439,7 +439,13 @@ repl call, 0x5B94E4, 0x5C7F67, 0x5B91F4, "Unit_get_max_move_points_for_bridge_ex repl vptr, 0x66DD40, 0x68AE20, 0x66DD40, "Unit_teleport", "int (__fastcall *) (Unit * this, int edx, int tile_x, int tile_y, Unit * unit_telepad)" inlead, 0x5BEF00, 0x5CDB10, 0x5BEC10, "Unit_score_kill", "void (__fastcall *) (Unit * this, int edx, Unit * victim, bool was_attacking)" define, 0x5631B0, 0x0, 0x0, "Leader_add_recent_war_weariness", "void (__fastcall *) (Leader * this, int edx, int victim_civ_id, int amount)" -repl call, 0x5BEF49, 0x0, 0x0, "Leader_add_recent_war_weariness_in_score_kill", "" +repl call, 0x5BEF49, 0x0, 0x0, "Leader_add_recent_war_weariness_for_unit_type_context", "" +repl call, 0x4A2F94, 0x0, 0x0, "Leader_add_recent_war_weariness_for_unit_type_context", "" +repl call, 0x4A3304, 0x0, 0x0, "Leader_add_recent_war_weariness_for_unit_type_context", "" +repl call, 0x4A3C99, 0x0, 0x0, "Leader_add_recent_war_weariness_for_unit_type_context", "" +repl call, 0x4A70F8, 0x0, 0x0, "Leader_add_recent_war_weariness_for_fight_attacker", "" +repl call, 0x4A66AA, 0x0, 0x0, "Leader_add_recent_war_weariness_for_fight_defender", "" +inlead, 0x4A2B40, 0x0, 0x0, "Fighter_do_cruise_missile_strike", "void (__fastcall *) (Fighter * this, int edx, Unit * unit, int neighbor_index)" define, 0x5BF558, 0x5CE0EA, 0x5BF268, "ADDR_EXISTING_BATTLE_CREATED_UNIT_CHECK", "void *" inlead, 0x4A1AE0, 0x4A86E0, 0x4A1B70, "Fighter_find_defensive_bombarder", "Unit * (__fastcall *) (Fighter * this, int edx, Unit * attacker, Unit * defender)" define, 0x5BCA90, 0x5CB620, 0x5BC7A0, "Unit_get_containing_army", "Unit * (__fastcall *) (Unit * this)" @@ -449,7 +455,7 @@ repl call, 0x4A3621, 0x4AA285, 0x4A36B1, "Fighter_get_odds_for_bombardment", "" repl call, 0x4A3F5B, 0x4AAC0D, 0x4A3FEB, "Fighter_get_odds_for_bombardment", "" repl call, 0x4A7343, 0x4AE003, 0x4A73D3, "Fighter_get_odds_for_bombardment", "" repl call, 0x4A5AF9, 0x4AC799, 0x4A5B89, "Fighter_get_odds_for_main_combat_loop", "" -define, 0x4A3280, 0x4A9ED0, 0x4A3310, "Fighter_damage_by_defensive_bombard", "void (__fastcall *) (Fighter * this, int edx, Unit * bombarder, Unit * defender)" +inlead, 0x4A3280, 0x4A9ED0, 0x4A3310, "Fighter_damage_by_defensive_bombard", "void (__fastcall *) (Fighter * this, int edx, Unit * bombarder, Unit * defender)" repl call, 0x4A57C5, 0x4AC477, 0x4A5855, "Fighter_damage_by_db_in_main_loop", "" inlead, 0x4A53A0, 0x4AC060, 0x4A5430, "Fighter_fight", "byte (__fastcall *) (Fighter * this, int edx, Unit * attacker, int attack_direction, Unit * defender_or_null)" repl call, 0x4A6EB7, 0x4ADB60, 0x4A6F47, "Unit_score_kill_by_defender", "" diff --git a/injected_code.c b/injected_code.c index 292e3d07..0840ab89 100644 --- a/injected_code.c +++ b/injected_code.c @@ -692,7 +692,7 @@ reset_to_base_config () for (int n = 0; n < ARRAY_LEN (cc->limit_units_per_tile); n++) cc->limit_units_per_tile[n] = 0; - is->war_weariness_kill_victim_type_id = -1; + is->war_weariness_subject_unit_type_id = -1; table_deinit (&cc->exclude_types_from_units_per_tile_limit); table_deinit (&cc->exclude_types_from_units_war_weariness); @@ -29412,6 +29412,7 @@ patch_Fighter_do_bombard_tile (Fighter * this, int edx, Unit * unit, int neighbo // Unit::score_kill will be called if the bombarder destroys its target, and that is the only way score_kill can be called while this method // is running. So if we're configured to stop enslaving from bombard, turn off enslaving while it's running. is->do_not_enslave_units = is->current_config.prevent_enslaving_by_bombardment; + is->war_weariness_subject_unit_type_id = unit_has_valid_type_id (unit) ? unit->Body.UnitTypeID : -1; // Check if we're going to do PTW-like targeting, if not fall back on the base game's do_bombard_tile method. We'll also fall back on that // method in the case where we're in an online game and the bombard can't happen b/c the tile is occupied by another battle. In that case, no @@ -29438,6 +29439,7 @@ patch_Fighter_do_bombard_tile (Fighter * this, int edx, Unit * unit, int neighbo Fighter_do_bombard_tile (this, __, unit, neighbor_index, mp_tile_x, mp_tile_y); is->do_not_enslave_units = false; + is->war_weariness_subject_unit_type_id = -1; } bool __fastcall @@ -30576,9 +30578,25 @@ patch_Unit_can_move_after_zoc (Unit * this, int edx, int neighbor_index, int par void __fastcall patch_Unit_score_kill (Unit * this, int edx, Unit * victim, bool was_attacking) { - is->war_weariness_kill_victim_type_id = unit_has_valid_type_id (victim) ? victim->Body.UnitTypeID : -1; + is->war_weariness_subject_unit_type_id = unit_has_valid_type_id (victim) ? victim->Body.UnitTypeID : -1; Unit_score_kill (this, __, victim, was_attacking); - is->war_weariness_kill_victim_type_id = -1; + is->war_weariness_subject_unit_type_id = -1; +} + +void __fastcall +patch_Fighter_do_cruise_missile_strike (Fighter * this, int edx, Unit * unit, int neighbor_index) +{ + is->war_weariness_subject_unit_type_id = unit_has_valid_type_id (unit) ? unit->Body.UnitTypeID : -1; + Fighter_do_cruise_missile_strike (this, __, unit, neighbor_index); + is->war_weariness_subject_unit_type_id = -1; +} + +void __fastcall +patch_Fighter_damage_by_defensive_bombard (Fighter * this, int edx, Unit * bombarder, Unit * defender) +{ + is->war_weariness_subject_unit_type_id = unit_has_valid_type_id (defender) ? defender->Body.UnitTypeID : -1; + Fighter_damage_by_defensive_bombard (this, __, bombarder, defender); + is->war_weariness_subject_unit_type_id = -1; } // Checks unit's HP after it was possibly hit by ZoC and deals with the consequences if it's dead. Does nothing if config option to make ZoC lethal @@ -30853,7 +30871,7 @@ patch_Fighter_damage_by_db_in_main_loop (Fighter * this, int edx, Unit * bombard } int damage_before = defender->Body.Damage; - Fighter_damage_by_defensive_bombard (this, __, bombarder, defender); + patch_Fighter_damage_by_defensive_bombard (this, __, bombarder, defender); int damage_after = defender->Body.Damage; is->dbe.bombarder = bombarder; @@ -31387,6 +31405,7 @@ patch_Fighter_fight (Fighter * this, int edx, Unit * attacker, is->saved_combat_unit_display_override = is->unit_display_override; is->combat_unit_display_override_active = true; is->unit_display_override_2 = (struct unit_display_override) {-1, -1, -1}; + is->war_weariness_fight_context = this; Unit * defender_for_fight = defender_or_null; int defender_tile_x = 0, @@ -31415,6 +31434,7 @@ patch_Fighter_fight (Fighter * this, int edx, Unit * attacker, is->combat_unit_display_override_active = saved_combat_udo_active; is->dbe = (struct defensive_bombard_event) {0}; is->counter_combat_ctx.active = false; + is->war_weariness_fight_context = NULL; return tr; } @@ -31450,15 +31470,35 @@ patch_Unit_get_defense_strength (Unit * this) } void __fastcall -patch_Leader_add_recent_war_weariness_in_score_kill (Leader * this, int edx, int victim_civ_id, int amount) +patch_Leader_add_recent_war_weariness_for_unit_type_context (Leader * this, int edx, int victim_civ_id, int amount) { - if ((is->war_weariness_kill_victim_type_id >= 0) && - itable_look_up_or (&is->current_config.exclude_types_from_units_war_weariness, is->war_weariness_kill_victim_type_id, 0)) + if ((is->war_weariness_subject_unit_type_id >= 0) && + itable_look_up_or (&is->current_config.exclude_types_from_units_war_weariness, is->war_weariness_subject_unit_type_id, 0)) return; Leader_add_recent_war_weariness (this, __, victim_civ_id, amount); } +void __fastcall +patch_Leader_add_recent_war_weariness_for_fight_attacker (Leader * this, int edx, int victim_civ_id, int amount) +{ + if (unit_has_valid_type_id (is->war_weariness_fight_context->attacker)) { + is->war_weariness_subject_unit_type_id = is->war_weariness_fight_context->attacker->Body.UnitTypeID; + } + patch_Leader_add_recent_war_weariness_for_unit_type_context (this, __, victim_civ_id, amount); + is->war_weariness_subject_unit_type_id = -1; +} + +void __fastcall +patch_Leader_add_recent_war_weariness_for_fight_defender (Leader * this, int edx, int victim_civ_id, int amount) +{ + if (unit_has_valid_type_id (is->war_weariness_fight_context->defender)) { + is->war_weariness_subject_unit_type_id = is->war_weariness_fight_context->defender->Body.UnitTypeID; + } + patch_Leader_add_recent_war_weariness_for_unit_type_context (this, __, victim_civ_id, amount); + is->war_weariness_subject_unit_type_id = -1; +} + void __fastcall patch_Unit_score_kill_by_defender (Unit * this, int edx, Unit * victim, bool was_attacking) { From daa5d7a4ee65d63f80a194eafef611de461e9100 Mon Sep 17 00:00:00 2001 From: Instafluff Date: Sun, 19 Jul 2026 10:43:42 -0700 Subject: [PATCH 7/8] Fix war weariness context null in Fighter_fight --- injected_code.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/injected_code.c b/injected_code.c index 0840ab89..6ad22c80 100644 --- a/injected_code.c +++ b/injected_code.c @@ -693,6 +693,7 @@ reset_to_base_config () cc->limit_units_per_tile[n] = 0; is->war_weariness_subject_unit_type_id = -1; + is->war_weariness_fight_context = NULL; table_deinit (&cc->exclude_types_from_units_per_tile_limit); table_deinit (&cc->exclude_types_from_units_war_weariness); @@ -31482,9 +31483,9 @@ patch_Leader_add_recent_war_weariness_for_unit_type_context (Leader * this, int void __fastcall patch_Leader_add_recent_war_weariness_for_fight_attacker (Leader * this, int edx, int victim_civ_id, int amount) { - if (unit_has_valid_type_id (is->war_weariness_fight_context->attacker)) { - is->war_weariness_subject_unit_type_id = is->war_weariness_fight_context->attacker->Body.UnitTypeID; - } + Fighter * context = is->war_weariness_fight_context; + is->war_weariness_subject_unit_type_id = + ((context != NULL) && unit_has_valid_type_id (context->attacker)) ? context->attacker->Body.UnitTypeID : -1; patch_Leader_add_recent_war_weariness_for_unit_type_context (this, __, victim_civ_id, amount); is->war_weariness_subject_unit_type_id = -1; } @@ -31492,9 +31493,9 @@ patch_Leader_add_recent_war_weariness_for_fight_attacker (Leader * this, int edx void __fastcall patch_Leader_add_recent_war_weariness_for_fight_defender (Leader * this, int edx, int victim_civ_id, int amount) { - if (unit_has_valid_type_id (is->war_weariness_fight_context->defender)) { - is->war_weariness_subject_unit_type_id = is->war_weariness_fight_context->defender->Body.UnitTypeID; - } + Fighter * context = is->war_weariness_fight_context; + is->war_weariness_subject_unit_type_id = + ((context != NULL) && unit_has_valid_type_id (context->defender)) ? context->defender->Body.UnitTypeID : -1; patch_Leader_add_recent_war_weariness_for_unit_type_context (this, __, victim_civ_id, amount); is->war_weariness_subject_unit_type_id = -1; } From 62a3afd2c748df41e073a6008b945bc92a626a46 Mon Sep 17 00:00:00 2001 From: Instafluff Date: Sun, 19 Jul 2026 10:47:10 -0700 Subject: [PATCH 8/8] Update docs for war weariness exclusions --- default.c3x_config.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/default.c3x_config.ini b/default.c3x_config.ini index 95a1cbb1..57b9a6d8 100644 --- a/default.c3x_config.ini +++ b/default.c3x_config.ini @@ -429,9 +429,9 @@ exclude_cities_from_units_per_tile_limit = false ; This option does nothing if limit_units_per_tile is set to false. exclude_types_from_units_per_tile_limit = [] -; Prevents the loss of certain unit types from adding war weariness. This does not change other war weariness sources such as city bombardment, -; captured cities, or tile improvement destruction. This option is a space separated list of unit type names. Multi-word names must be wrapped in quotes. -; For example: +; Prevents the loss of certain unit types from adding war weariness, including both from loss of a unit or HP dropping to 1. This does not change other +; war weariness sources such as city bombardment, captured cities, or tile improvement destruction. This option is a space separated list of unit type names. +; Multi-word names must be wrapped in quotes. For example: ; [Drone "Cruise Missile"] exclude_types_from_units_war_weariness = []