diff --git a/C3X.h b/C3X.h index 3ebc7634..5e32c2c7 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; bool enable_free_buildings_from_small_wonders; bool enable_stack_unit_commands; bool skip_repeated_tile_improv_replacement_asks; @@ -2128,6 +2129,11 @@ 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. + // 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 85581bb3..ea0ee67e 100644 --- a/civ_prog_objects.csv +++ b/civ_prog_objects.csv @@ -437,7 +437,15 @@ 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)" +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)" @@ -447,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/default.c3x_config.ini b/default.c3x_config.ini index f926c44a..57b9a6d8 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, 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 = [] + 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..6ad22c80 100644 --- a/injected_code.c +++ b/injected_code.c @@ -692,7 +692,10 @@ 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_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); for (int n = 0; n < COUNT_PERFUME_KINDS; n++) stable_deinit (&cc->perfume_specs[n]); @@ -2862,6 +2865,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); @@ -29407,6 +29413,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 @@ -29433,6 +29440,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 @@ -30568,6 +30576,30 @@ 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_subject_unit_type_id = unit_has_valid_type_id (victim) ? victim->Body.UnitTypeID : -1; + Unit_score_kill (this, __, victim, was_attacking); + 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 // isn't set or if interceptor is NULL. Returns true if the unit was killed, false otherwise. bool @@ -30583,7 +30615,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)) @@ -30840,7 +30872,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; @@ -31374,6 +31406,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, @@ -31402,6 +31435,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; } @@ -31436,6 +31470,36 @@ patch_Unit_get_defense_strength (Unit * this) return base; } +void __fastcall +patch_Leader_add_recent_war_weariness_for_unit_type_context (Leader * this, int edx, int victim_civ_id, int amount) +{ + 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) +{ + 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; +} + +void __fastcall +patch_Leader_add_recent_war_weariness_for_fight_defender (Leader * this, int edx, int victim_civ_id, int amount) +{ + 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; +} + void __fastcall patch_Unit_score_kill_by_defender (Unit * this, int edx, Unit * victim, bool was_attacking) { @@ -31443,12 +31507,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