Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions C3X.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
12 changes: 10 additions & 2 deletions civ_prog_objects.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand All @@ -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", ""
Expand Down
6 changes: 6 additions & 0 deletions default.c3x_config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 68 additions & 4 deletions injected_code.c
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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))
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -31436,19 +31470,49 @@ 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)
{
// This function is called when the defender wins in combat. If the attacker was actually killed by defensive bombardment, then award credit
// 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
Expand Down