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
53 changes: 52 additions & 1 deletion C3X.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

#include <stdbool.h>

#define NOVIRTUALKEYCODES // Keycodes defined in Civ3Conquests.h instead
Expand Down Expand Up @@ -197,6 +196,41 @@ enum perfume_kind {
COUNT_PERFUME_KINDS
};

struct unit_counter_group {
char * name;
int * type_ids;
int count_type_ids;
};

// Attacker/defender match modes
#define UCM_ANY -1 // * Any unit type
#define UCM_GROUP -2 // Match using the group_name field

struct counter_rule {
// Attacker side
int attacker_match; // UnitTypeID, or UCM_ANY / UCM_GROUP
char * attacker_group; // Used when attacker_match == UCM_GROUP

// Defender side
int defender_match;
char * defender_group;

// Environment conditions (0 / false means no restriction)
unsigned int terrain_mask; // SquareTypes mask, 0 = no restriction
bool only_in_city;
int district_id; // -1 = no restriction
char * district_name; // Resolved after district configs are loaded
unsigned int self_experience_mask; // 0 = no restriction
unsigned int enemy_experience_mask; // 0 = no restriction
bool ignore_terrain; // true = set defender terrain defense to 0

// Effects (percent values, 100 = no change)
int self_atk_pct;
int self_def_pct;
int enemy_atk_pct;
int enemy_def_pct;
};

struct c3x_config {
bool enable_stack_bombard;
bool enable_disorder_warning;
Expand Down Expand Up @@ -355,6 +389,12 @@ struct c3x_config {
enum no_ai_patrol_override override_no_ai_patrol;
enum barbarian_activity_override override_barbarian_activity_level_for_scenario_maps;
bool initialize_preplaced_scenario_leaders_as_mgls;
bool enable_unit_counters;
struct unit_counter_group * unit_counter_groups;
int count_unit_counter_groups;
struct counter_rule * counter_rules;
int count_counter_rules;
bool use_civ4_style_best_defender;

bool enable_trade_net_x;
bool optimize_improvement_loops;
Expand Down Expand Up @@ -1834,6 +1874,17 @@ struct injected_state {
int unit_id, tile_x, tile_y;
} unit_display_override;

// Set in patch_Fighter_get_odds_for_main_combat_loop, read by patch_Unit_get_attack/defense_strength.
// Stores counter multipliers for the current combat. Active only during Fighter_get_combat_odds call.
struct {
bool active;
Unit * attacker;
Unit * defender;
int attacker_atk_pct; // Attacker attack multiplier (combines forward self-atk and reverse enemy-atk)
int defender_def_pct; // Defender defense multiplier (combines forward enemy-def and reverse self-def)
bool ignore_terrain;
} counter_combat_ctx;

// Used to extract which unit (if any) exerted zone of control from within Fighter::apply_zone_of_control.
Unit * zoc_interceptor;

Expand Down
5 changes: 2 additions & 3 deletions civ_prog_objects.csv
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ define, 0x499FE0, 0x49F9F0, 0x49A070, "is_online_game", "char (__stdcall *
define, 0x437A70, 0x439620, 0x437AF0, "tile_at", "Tile * (__cdecl *) (int x, int y)"
define, 0x426C80, 0x4283C0, 0x426D00, "TileUnits_TileUnitID_to_UnitID", "int (__fastcall *) (TileUnits * this, int edx, int tile_unit_id, int * out_UnitItem_field_0)"
inlead, 0x5C1410, 0x5CFFA0, 0x5C1120, "Unit_bombard_tile", "void (__fastcall *) (Unit * this, int edx, int x, int y)"
define, 0x5BE820, 0x5CD420, 0x5BE530, "Unit_get_defense_strength", "int (__fastcall *) (Unit * this)"
inlead, 0x5BE820, 0x5CD420, 0x5BE530, "Unit_get_defense_strength", "int (__fastcall *) (Unit * this)"
inlead, 0x5BB650, 0x5CA190, 0x5BB360, "Unit_is_visible_to_civ", "char (__fastcall *) (Unit * this, int edx, int civ_id, int param_2)"
define, 0x5EA6C0, 0x5F9F10, 0x5EA5F0, "Tile_has_city", "char (__fastcall *) (Tile * this)"
define, 0x5EA6E0, 0x5F9F30, 0x5EA610, "Tile_has_colony", "bool (__fastcall *) (Tile * this)"
Expand Down Expand Up @@ -412,7 +412,7 @@ repl call, 0x4A784E, 0x4AE509, 0x4a78DE, "Tile_check_water_for_sea_zoc", ""
define, 0x4A79C2, 0x4AE66D, 0x4A7A52, "ADDR_SKIP_LAND_UNITS_FOR_SEA_ZOC", "byte *"
define, 0x4A7CAA, 0x4AE962, 0x4A7D3A, "ADDR_SKIP_SEA_UNITS_FOR_LAND_ZOC", "byte *"
repl call, 0x4A7BF2, 0x4AE8AA, 0x4A7C82, "Tile_check_water_for_land_zoc", ""
define, 0x5BE6E0, 0x5CD2C0, 0x5BE3F0, "Unit_get_attack_strength", "int (__fastcall *) (Unit * this)"
inlead, 0x5BE6E0, 0x5CD2C0, 0x5BE3F0, "Unit_get_attack_strength", "int (__fastcall *) (Unit * this)"
repl call, 0x4A79CA, 0x4AE675, 0x4A7A5A, "Unit_get_attack_strength_for_sea_zoc", ""
repl call, 0x4A7B15, 0x4AE7BE, 0x4A7BA5, "Unit_get_attack_strength_for_sea_zoc", ""
repl call, 0x4A7B20, 0x4AE7C9, 0x4A7BB0, "Unit_get_attack_strength_for_sea_zoc", ""
Expand Down Expand Up @@ -942,7 +942,6 @@ repl call, 0x4D5464, 0x4DDC29, 0x4D5524, "PCX_Image_draw_pedia_unit_stats_2nd_co
define, 0x1A5, 0x1A6, 0x1A5, "LBL_OPERATIONAL_RANGE", "int"
define, 0x4D519C, 0x4DD953, 0x4D525C, "ADDR_AIR_UNIT_CHECK_TO_DRAW_PEDIA_STATS", "byte *"
define, 0x1A8, 0x1A9, 0x1A8, "LBL_BOMBARD_RANGE", "int"

ignore, 0x5FC710, 0x0, 0x0, "PCX_Image_create_and_init_jgl_image", "int (__fastcall *) (PCX_Image * this, int edx, int width, int height, int bit_depth, int param_4, int param_5, int param_6)"
ignore, 0x5FCC50, 0x0, 0x0, "PCX_Image_draw_region_to_location", "void (__fastcall *) (PCX_Image * this, int edx, PCX_Image * canvas, int src_x, int src_y, int dest_x, int dest_y, int width, int height)"
ignore, 0x600050, 0x0, 0x0, "PCX_Image_fill", "void (__fastcall *) (PCX_Image * this, int edx, int color)"
Expand Down
83 changes: 77 additions & 6 deletions default.c3x_config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -557,10 +557,6 @@ ai_multi_start_extra_palaces = []
promote_wonder_decorruption_effect = false

allow_military_leaders_to_hurry_wonders = false

; Under the standard rules, a player can't spawn a battle-created unit from combat while they already have one. (The battle-created unit is specified
; in the scenario editor, typically it's a military great leader.) This option removes that rule, allowing players to spawn more MGLs from combat
; victories while they already have one or more on the board.
allow_multiple_battle_created_units_per_player = false

; The AI's beaker production will be multiplied by this amount, as a percent. A value of 100 gives you the standard game behavior, a value of 75
Expand Down Expand Up @@ -891,7 +887,7 @@ special_capital_decorruption_effect = 10
; zero: Set NoAIPatrol to 0, allowing AI units to patrol
; The purpose of this option is to enable you to configure NoAIPatrol like a C3X setting instead of globally. In particlar, it allows you to configure
; NoAIPatrol on or off on a per-scenario basis.
override_no_ai_patrol = none
override_no_ai_patrol = one

; Overrides the barbarian activity level when starting a new game for a scenario with a custom map. Normally, in that case, the game will use the
; barbarian settings kept in conquests.ini, ignoring what was set for the map in the editor. This option allows you to set an activity level on a
Expand All @@ -906,6 +902,81 @@ override_barbarian_activity_level_for_scenario_maps = none
; types at the start of the game so they behave like normal MGLs spawned during a game.
initialize_preplaced_scenario_leaders_as_mgls = false

enable_unit_counters = true

; Civ 4 style best defender selection.
; When this is on (and enable_unit_counters is also true), every time an attack happens the engine picks the
; defender that is HARDEST for the current attacker to beat (the unit with the highest defender win rate
; once unit-counter rules are applied), instead of the vanilla "highest defense strength" rule. The same
; unit is also forced to the top of the enemy stack on the map whenever a player has an attacker selected,
; so the displayed unit stays in sync with what would actually fight. Re-targeting is automatic: switching
; the selected attacker (different counter match-ups) updates which enemy unit is shown as the best defender.
; Notes:
; - Has no effect when enable_unit_counters = false (then there's nothing to differentiate beyond defense).
; - Applies to both human and AI attackers, so the rule is consistent.
; - Does not change ranged-bombard or defensive-bombard target selection, only normal attacks.
use_civ4_style_best_defender = false

; unit_group allows you gruop your units in a group,please refer to building_prereqs_for_units for the format.
; This function will not work if enable_unit_counters is false.
unit_group = []
; ── counter_rule format ──────────────────────────────────────────────────────────────
;
; counter_rule = [Friendly vs Enemy Effect... Conditions...]
;
; 【Friendly / Enemy】
; This can be one of the following three options:
; · The specific unit type, such as "Archer" (must match the name in BIQ exactly; names containing spaces must be enclosed in quotation marks)
; · A unit group name, such as melee (i.e. the group defined in the unit_group section above)
; · "*" represents any unit type (the asterisk must be enclosed in quotation marks)
;
; 【Effect】(Expressed as a percentage; when multiple rules apply to the same battle, their effects are multiplied.)
;
; "self" always refers to the first unit, and "enemy" always refers to the second unit, regardless of who is attacking whom:
; self-atk value — The unit’s attack power becomes N% of its original value
; Example: self-atk 150 = attack power ×1.5; self-atk 50 = attack power ×0.5
; self-def value — Your defence becomes N% of the original value;
; enemy-atk value — The enemy’s attack becomes N% of the original value;
; enemy-def value — The enemy’s defence becomes N% of the original value;
;
; 【Conditions】(Optional; leaving blank indicates no restrictions; conditions take effect only when all are met simultaneously)
; in-city —— Takes effect only when the enemy is on a city tile
; terrain terrain_name -- Takes effect only when the enemy is on a tile of the specified terrain
; Uses the same lower-case English terrain tokens as districts_config buildable_on, not BIQ/localized names.
; Examples: grassland, hills, coast, snow-forest, snow-mountain, snow-volcano, lake
; ignore-terrain —— Ignores the enemy’s terrain defence bonus (sets their terrain bonus to zero)
; Can be used in conjunction with enemy-def; when used together, ignore-terrain takes precedence
; district district_name -- Only takes effect when the enemy is in a specified district (enable_districts must be enabled)
; District names are resolved after districts_config loads, so dynamic district names are supported.
; self-exp exp_name -- Only takes effect when the self unit has one of the specified combat experiences.
; enemy-exp exp_name -- Only takes effect when the enemy unit has one of the specified combat experiences.
; Accepts one or more scenario experience names, numeric IDs(in standard game, 0 is represents conscript, 1 is represents regular and so on),
; or English aliases: conscript, regular, veteran, elite.
;
; 【Examples】
; counter_rule = [ranged vs melee self-atk 125]
; → When a ranged unit attacks a melee unit, its attack power is multiplied by 1.25
;
; counter_rule = ["Knight" vs melee in-city enemy-def 150]
; → When knights attack melee units within a city, the enemy’s defence is multiplied by 1.5
;
; counter_rule = ["Musketman" vs "Medival Infantry" terrain grassland self-atk 125]
; → When musketeers attack medieval infantry on grassland terrain, their attack power is multiplied by 1.25
;
; counter_rule = ["Knight" vs "*" ignore-terrain self-atk 150]
; → When a Knight attacks any unit, its attack power is multiplied by 1.5 and it ignores the enemy’s terrain bonus.
; counter_rule = [Archer vs Swordsman self-atk 130 self-def 120]
; → When an archer attacks a swordsman: Archer’s attack power ×130%
; When a swordsman attacks an archer: Archer’s defence ×120%
; counter_rule = ["*" vs "*" self-exp veteran enemy-exp regular self-atk 125]
; -> When a veteran self unit attacks a regular enemy unit, its attack power is multiplied by 1.25.
; counter_rule = ["*" vs "*" self-exp 2 3 enemy-exp 0 1 self-atk 125]
; -> When self has experience ID 2 or 3, and enemy has experience ID 0 or 1, self attack is multiplied by 1.25.
;
; ─────────────────────────────────────────────────────────────────────────────

counter_rule = ["*" vs "*" self-exp 2 3 enemy-exp 0 1 self-atk 2000]

[==================]
[=== AESTHETICS ===]
[==================]
Expand Down Expand Up @@ -953,7 +1024,7 @@ pinned_hour_for_day_night_cycle = 0
[=======================]

; Show or hide natural wonders on the map. When disabled, natural wonders will not appear on the map.
enable_natural_wonders = false
enable_natural_wonders = true

; If a new scenario is loaded which has no natural wonders defined, add natural wonders.
add_natural_wonders_to_scenarios_if_none = false
Expand Down
Loading