From 494f460fed977a11fbba575d4527adbff5664c05 Mon Sep 17 00:00:00 2001 From: Philiquaz Date: Mon, 17 Nov 2025 03:18:46 +0000 Subject: [PATCH 1/8] [WIP] Introduce manhatten distance for city clearance, promote foreign city rule to its own set of clearance metrics. --- C3X.h | 10 +++- default.c3x_config.ini | 21 +++++--- injected_code.c | 118 ++++++++++++++++++++--------------------- 3 files changed, 80 insertions(+), 69 deletions(-) diff --git a/C3X.h b/C3X.h index 66f5af79..7c19323e 100644 --- a/C3X.h +++ b/C3X.h @@ -137,6 +137,13 @@ enum perfume_kind { COUNT_PERFUME_KINDS }; +struct minimum_city_separation { + int any_chebyshev; + int any_manhatten; + int foreign_chebyshev; + int foreign_manhatten; +}; + struct c3x_config { bool enable_stack_bombard; bool enable_disorder_warning; @@ -150,8 +157,7 @@ struct c3x_config { bool enable_stack_unit_commands; bool skip_repeated_tile_improv_replacement_asks; bool autofill_best_gold_amount_when_trading; - int minimum_city_separation; - bool disallow_founding_next_to_foreign_city; + struct minimum_city_separation minimum_city_separation; bool enable_trade_screen_scroll; bool group_units_on_right_click_menu; bool gray_out_units_on_menu_with_no_remaining_moves; diff --git a/default.c3x_config.ini b/default.c3x_config.ini index 42eacf65..a313d27a 100644 --- a/default.c3x_config.ini +++ b/default.c3x_config.ini @@ -349,13 +349,20 @@ remove_era_limit = false prevent_autorazing = false prevent_razing_by_players = false -; These options allow you to adjust the minimum allowed distance between cities. minimum_city_separation controls the minimum number of tiles between -; cities so, e.g, if it's set to 1, cities cannot be founded adjacent to one another, there must be at least one open tile separating them. A setting -; of 1 there corresponds to the standard game rules. disallow_founding_next_to_foreign_city is an optional additional restriction. If it's enabled, -; cities may not be founded adjacent to a city belonging to another civ regardless of the minimum separation. It is only relevant when the minimum -; separation is set to zero. -minimum_city_separation = 1 -disallow_founding_next_to_foreign_city = true +; These options allow you to adjust the minimum allowed distance between cities. minimum_city_separation_chebyshev controls the minimum number of tiles +; between cities so, e.g, if it's set to 1, cities cannot be founded adjacent to one another, there must be at least one open tile separating them. A +; setting of 1 there corresponds to the standard game rules. +; minimum_city_separation_manhatten does the same thing, but uses manhatten distance. The clearance is a diamond shape instead of a square (or the +; converse if viewed on the isometric map.) +; Both rules are defined at the same time, so if either distance is enough, the city may be built. This means the clearance area around a city can be +; an octagon (as opposed to a star shape) and you may need to adjust both settings to achieve your desired result. +; Finally, an additional pair of configs are given to define an equivilent clearance area between cities from different civs. Foreign cities are still +; subject to the regular chebyshev and manhatten distance rules, but this means if for example you have a clearance of 0, you may require foreign cities +; to still be placed at a distance of 1 away. +minimum_city_separation_chebyshev = 1 +minimum_city_separation_manhatten = 2 +minimum_foreign_city_separation_chebyshev = 1 +minimum_foreign_city_separation_manhatten = 2 ; Set to a number to limit railroad movement to that many tiles per turn. To return to infinite railroad movement, set to false or a number <=0. By ; default, all units travel the same distance along limited rails like in Civ 4, but this can be changed by toggling the next option below. diff --git a/injected_code.c b/injected_code.c index 89e17abd..141bee6e 100644 --- a/injected_code.c +++ b/injected_code.c @@ -2117,9 +2117,24 @@ load_config (char const * file_path, int path_is_relative_to_mod_dir) cfg->anarchy_length_percent = 100 - ival; else handle_config_error (&p, CPE_BAD_INT_VALUE); - } else if (slice_matches_str (&p.key, "adjust_minimum_city_separation")) { + } else if (slice_matches_str (&p.key, "minimum_city_separation_chebyshev") || slice_matches_str (&p.key, "adjust_minimum_city_separation")) { if (read_int (&value, &ival)) - cfg->minimum_city_separation = ival + 1; + cfg->minimum_city_separation.any_chebyshev = ival; + else + handle_config_error (&p, CPE_BAD_INT_VALUE); + } else if (slice_matches_str (&p.key, "minimum_city_separation_manhatten")) { + if (read_int (&value, &ival)) + cfg->minimum_city_separation.any_manhatten = ival; + else + handle_config_error (&p, CPE_BAD_INT_VALUE); + } else if (slice_matches_str (&p.key, "minimum_foreign_city_separation_chebyshev")) { + if (read_int (&value, &ival)) + cfg->minimum_city_separation.foreign_chebyshev = ival; + else + handle_config_error (&p, CPE_BAD_INT_VALUE); + } else if (slice_matches_str (&p.key, "minimum_foreign_city_separation_manhatten")) { + if (read_int (&value, &ival)) + cfg->minimum_city_separation.foreign_manhatten = ival; else handle_config_error (&p, CPE_BAD_INT_VALUE); } else if (slice_matches_str (&p.key, "reduce_max_escorts_per_ai_transport")) { @@ -10641,7 +10656,6 @@ patch_init_floating_point () {"enable_stack_unit_commands" , true , offsetof (struct c3x_config, enable_stack_unit_commands)}, {"skip_repeated_tile_improv_replacement_asks" , true , offsetof (struct c3x_config, skip_repeated_tile_improv_replacement_asks)}, {"autofill_best_gold_amount_when_trading" , true , offsetof (struct c3x_config, autofill_best_gold_amount_when_trading)}, - {"disallow_founding_next_to_foreign_city" , true , offsetof (struct c3x_config, disallow_founding_next_to_foreign_city)}, {"enable_trade_screen_scroll" , true , offsetof (struct c3x_config, enable_trade_screen_scroll)}, {"group_units_on_right_click_menu" , true , offsetof (struct c3x_config, group_units_on_right_click_menu)}, {"gray_out_units_on_menu_with_no_remaining_moves" , true , offsetof (struct c3x_config, gray_out_units_on_menu_with_no_remaining_moves)}, @@ -10776,7 +10790,10 @@ patch_init_floating_point () int offset; } integer_config_options[] = { {"limit_railroad_movement" , 0, offsetof (struct c3x_config, limit_railroad_movement)}, - {"minimum_city_separation" , 1, offsetof (struct c3x_config, minimum_city_separation)}, + {"minimum_city_separation_chebychev" , 1, offsetof (struct c3x_config, minimum_city_separation.any_chebyshev)}, + {"minimum_city_separation_manhatten" , 2, offsetof (struct c3x_config, minimum_city_separation.any_manhatten)}, + {"minimum_foreign_city_separation_chebychev" , 1, offsetof (struct c3x_config, minimum_city_separation.foreign_chebyshev)}, + {"minimum_foreign_city_separation_manhatten" , 2, offsetof (struct c3x_config, minimum_city_separation.foreign_manhatten)}, {"anarchy_length_percent" , 100, offsetof (struct c3x_config, anarchy_length_percent)}, {"ai_multi_city_start" , 0, offsetof (struct c3x_config, ai_multi_city_start)}, {"max_tries_to_place_fp_city" , 10000, offsetof (struct c3x_config, max_tries_to_place_fp_city)}, @@ -12226,7 +12243,7 @@ patch_Main_GUI_set_up_unit_command_buttons (Main_GUI * this) } // If the minimum city separation is increased, then gray out the found city button if we're too close to another city. - if ((is->current_config.minimum_city_separation > 1) && (p_main_screen_form->Current_Unit != NULL) && (is->disabled_command_img_state == IS_OK)) { + if ((is->current_config.minimum_city_separation.any_chebyshev > 1) && (p_main_screen_form->Current_Unit != NULL) && (is->disabled_command_img_state == IS_OK)) { Unit_Body * selected_unit = &p_main_screen_form->Current_Unit->Body; // For each unit command button @@ -12250,7 +12267,7 @@ patch_Main_GUI_set_up_unit_command_buttons (Main_GUI * this) * to_replace = "$NUM0", * replace_location = strstr (label, to_replace); if (replace_location != NULL) - snprintf (tooltip, sizeof tooltip, "%.*s%d%s", replace_location - label, label, is->current_config.minimum_city_separation, replace_location + strlen (to_replace)); + snprintf (tooltip, sizeof tooltip, "%.*s%d%s", replace_location - label, label, is->current_config.minimum_city_separation.any_chebyshev, replace_location + strlen (to_replace)); else snprintf (tooltip, sizeof tooltip, "%s", label); tooltip[(sizeof tooltip) - 1] = '\0'; @@ -12660,7 +12677,7 @@ patch_Unit_can_do_worker_command_for_button_setup (Unit * this, int edx, int uni // grayed out button image is initialized now so we don't activate the build city button then find out later we can't gray it out. if ((! base) && (unit_command_value == UCV_Build_City) && - (is->current_config.minimum_city_separation > 1) && + //Shortcut removed here due to manhatten dist addition (patch_Map_check_city_location (&p_bic_data->Map, __, this->Body.X, this->Body.Y, this->Body.CivID, false) == CLV_CITY_TOO_CLOSE) && (init_disabled_command_buttons (), is->disabled_command_img_state == IS_OK)) return true; @@ -15167,67 +15184,48 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c } } - int min_sep = is->current_config.minimum_city_separation; + struct minimum_city_separation min_sep = is->current_config.minimum_city_separation; + int min_sep_chebyshev = min_sep.any_chebyshev > min_sep.foreign_chebyshev ? min_sep.any_chebyshev : min_sep.foreign_chebyshev; + int min_sep_manhatten = min_sep.any_manhatten > min_sep.foreign_manhatten ? min_sep.any_manhatten : min_sep.foreign_manhatten; + int min_sep_box = min_sep_manhatten * 2 < min_sep_chebyshev ? min_sep_manhatten * 2 : min_sep_chebyshev; CityLocValidity base_result = Map_check_city_location (this, __, tile_x, tile_y, civ_id, check_for_city_on_tile); - // If minimum separation is one, make no change - if (min_sep == 1) + if (base_result != CLV_OK && base_result != CLV_CITY_TOO_CLOSE) return base_result; - - // If minimum separation is <= 0, ignore the CITY_TOO_CLOSE objection to city placement unless the location is next to a city belonging to - // another civ and the settings forbid founding there. - else if ((min_sep <= 0) && (base_result == CLV_CITY_TOO_CLOSE)) { - if (is->current_config.disallow_founding_next_to_foreign_city) - for (int n = 1; n <= 8; n++) { - int x, y; - get_neighbor_coords (&p_bic_data->Map, tile_x, tile_y, n, &x, &y); - City * city = city_at (x, y); - if ((city != NULL) && (city->Body.CivID != civ_id)) + + // If rules are default, make no change + if (min_sep_chebyshev == 1 && min_sep_manhatten >= 2) + return base_result; + + //Otherwise perform calculation ourselves + //start calcing dx/dy at 45 degrees / on a virtual grid + for (int dx = -min_sep_box; dx <= min_sep_box; dx++) { + for (int dy = -min_sep_box; dy <= min_sep_box; dy++) { + int absx = int_abs(dx); + int absy = int_abs(dy); + int chebyshev = absx > absy ? absx : absy; + int manhatten = absx + absy; + //Shortcut in case the tile is not inside either the global rule or the foreign rule + if (chebyshev > min_sep_chebyshev || manhatten > min_sep_manhatten) + continue; + //transform to map coords + int tx = tile_x + dx-dy; + int ty = tile_y + dy+dx; + wrap_tile_coords (&p_bic_data->Map, &tx, &ty); + City * city = city_at(tx, ty); + if (city != NULL) { + //Apply the global rule + if (chebyshev < min_sep.any_chebyshev || manhatten < min_sep.any_manhatten) return CLV_CITY_TOO_CLOSE; - } - return CLV_OK; - - // If we have an increased separation we might have to exclude some locations the base code allows. - } else if ((min_sep > 1) && (base_result == CLV_OK)) { - // Check tiles around (x, y) for a city. Because the base result is CLV_OK, we don't have to check neighboring tiles, just those at - // distance 2, 3, ... up to (an including) the minimum separation - for (int dist = 2; dist <= min_sep; dist++) { - - // vertices stores the unwrapped coords of the tiles at the vertices of the square of tiles at distance "dist" around - // (tile_x, tile_y). The order of the vertices is north, east, south, west. - struct vertex { - int x, y; - } vertices[4] = { - {tile_x , tile_y - 2*dist}, - {tile_x + 2*dist, tile_y }, - {tile_x , tile_y + 2*dist}, - {tile_x - 2*dist, tile_y } - }; - - // neighbor index for direction of tiles along edge starting from each vertex - // values correspond to directions: southeast, southwest, northwest, northeast - int edge_dirs[4] = {3, 5, 7, 1}; - - // Loop over verts and check tiles along their associated edges. The N vert is associated with the NE edge, the E vert with - // the SE edge, etc. - for (int vert = 0; vert < 4; vert++) { - wrap_tile_coords (&p_bic_data->Map, &vertices[vert].x, &vertices[vert].y); - int dx, dy; - neighbor_index_to_diff (edge_dirs[vert], &dx, &dy); - for (int j = 0; j < 2*dist; j++) { // loop over tiles along this edge - int cx = vertices[vert].x + j * dx, - cy = vertices[vert].y + j * dy; - wrap_tile_coords (&p_bic_data->Map, &cx, &cy); - if (city_at (cx, cy)) + //Apply the foreign rule + if (city->Body.CivID != civ_id) { + if (chebyshev < min_sep.foreign_chebyshev || manhatten < min_sep.foreign_manhatten) return CLV_CITY_TOO_CLOSE; } } - } - return base_result; - - } else - return base_result; + } + return CLV_OK; } bool From b68a0d94108921e13eb1623d0c88dad9b6a3978f Mon Sep 17 00:00:00 2001 From: Philiquaz Date: Mon, 17 Nov 2025 04:41:49 +0000 Subject: [PATCH 2/8] Adjust search box size to be in same coord units --- injected_code.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/injected_code.c b/injected_code.c index 141bee6e..9a04b7a5 100644 --- a/injected_code.c +++ b/injected_code.c @@ -15187,7 +15187,7 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c struct minimum_city_separation min_sep = is->current_config.minimum_city_separation; int min_sep_chebyshev = min_sep.any_chebyshev > min_sep.foreign_chebyshev ? min_sep.any_chebyshev : min_sep.foreign_chebyshev; int min_sep_manhatten = min_sep.any_manhatten > min_sep.foreign_manhatten ? min_sep.any_manhatten : min_sep.foreign_manhatten; - int min_sep_box = min_sep_manhatten * 2 < min_sep_chebyshev ? min_sep_manhatten * 2 : min_sep_chebyshev; + int min_sep_box = min_sep_manhatten < min_sep_chebyshev ? min_sep_manhatten : min_sep_chebyshev; CityLocValidity base_result = Map_check_city_location (this, __, tile_x, tile_y, civ_id, check_for_city_on_tile); if (base_result != CLV_OK && base_result != CLV_CITY_TOO_CLOSE) From 3c0ad9597e343f4be28c9b54103bd380a605cf78 Mon Sep 17 00:00:00 2001 From: Philiquaz Date: Mon, 17 Nov 2025 09:18:18 +0000 Subject: [PATCH 3/8] Fix behaviour off by 1 and intersection logic. Remove additional shortcut that sometimes doesn't apply. --- default.c3x_config.ini | 2 +- injected_code.c | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/default.c3x_config.ini b/default.c3x_config.ini index a313d27a..27baa318 100644 --- a/default.c3x_config.ini +++ b/default.c3x_config.ini @@ -353,7 +353,7 @@ prevent_razing_by_players = false ; between cities so, e.g, if it's set to 1, cities cannot be founded adjacent to one another, there must be at least one open tile separating them. A ; setting of 1 there corresponds to the standard game rules. ; minimum_city_separation_manhatten does the same thing, but uses manhatten distance. The clearance is a diamond shape instead of a square (or the -; converse if viewed on the isometric map.) +; converse if viewed on the isometric map.) As a rule of thumb for disabling this, set it to 2x the chebyshev distance or more. ; Both rules are defined at the same time, so if either distance is enough, the city may be built. This means the clearance area around a city can be ; an octagon (as opposed to a star shape) and you may need to adjust both settings to achieve your desired result. ; Finally, an additional pair of configs are given to define an equivilent clearance area between cities from different civs. Foreign cities are still diff --git a/injected_code.c b/injected_code.c index 9a04b7a5..f217015e 100644 --- a/injected_code.c +++ b/injected_code.c @@ -12243,7 +12243,7 @@ patch_Main_GUI_set_up_unit_command_buttons (Main_GUI * this) } // If the minimum city separation is increased, then gray out the found city button if we're too close to another city. - if ((is->current_config.minimum_city_separation.any_chebyshev > 1) && (p_main_screen_form->Current_Unit != NULL) && (is->disabled_command_img_state == IS_OK)) { + if ((p_main_screen_form->Current_Unit != NULL) && (is->disabled_command_img_state == IS_OK)) { Unit_Body * selected_unit = &p_main_screen_form->Current_Unit->Body; // For each unit command button @@ -15193,10 +15193,6 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c if (base_result != CLV_OK && base_result != CLV_CITY_TOO_CLOSE) return base_result; - // If rules are default, make no change - if (min_sep_chebyshev == 1 && min_sep_manhatten >= 2) - return base_result; - //Otherwise perform calculation ourselves //start calcing dx/dy at 45 degrees / on a virtual grid for (int dx = -min_sep_box; dx <= min_sep_box; dx++) { @@ -15215,11 +15211,11 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c City * city = city_at(tx, ty); if (city != NULL) { //Apply the global rule - if (chebyshev < min_sep.any_chebyshev || manhatten < min_sep.any_manhatten) + if (chebyshev <= min_sep.any_chebyshev && manhatten <= min_sep.any_manhatten) return CLV_CITY_TOO_CLOSE; //Apply the foreign rule if (city->Body.CivID != civ_id) { - if (chebyshev < min_sep.foreign_chebyshev || manhatten < min_sep.foreign_manhatten) + if (chebyshev <= min_sep.foreign_chebyshev && manhatten <= min_sep.foreign_manhatten) return CLV_CITY_TOO_CLOSE; } } From 20237b859bcd8f9f795bb92d926aa2e0488ff35c Mon Sep 17 00:00:00 2001 From: Philiquaz Date: Thu, 20 Nov 2025 17:56:16 +0000 Subject: [PATCH 4/8] Begin migrating to minimum distance rules. Lay out structs, overview of mechanics plan in check_city_location --- C3X.h | 62 ++++++++++++++++++++++++++++++++ injected_code.c | 96 ++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 149 insertions(+), 9 deletions(-) diff --git a/C3X.h b/C3X.h index 7c19323e..9b15d93c 100644 --- a/C3X.h +++ b/C3X.h @@ -137,6 +137,66 @@ enum perfume_kind { COUNT_PERFUME_KINDS }; +enum map_target_type { + CITY,//flag foreign, flag population, flag culture + TERRAIN,//flag food_yield, flag shield_yield, flag commerce_yield + RESOURCE,//flag + UNIT,//flag foreign, flag hostile +} + +struct map_target_city { + int flags; + bool foreign;//flag 0 + bool hostile;//flag 1 + int population_min;//flag 2 + int population_max;//flag 3 + int culture_min;//flag 4 + int culture_max;//flag 5 +} + +struct map_target_terrain { + int flags; + int type;//flag 0 + int food_yield_min;//flag 1 + int food_yield_max;//flag 2 + int shield_yield_min;//flag 3 + int shield_yield_max;//flag 4 + int commerce_yield_min;//flag 5 + int commerce_yield_max;//flag 6 +} + +struct map_target_resource { + int flags; + int type;//flag 0 + bool is_visible;//flag 1 +} + +struct map_target_unit { + int flags; + int type;//flag 0 + bool foreign;//flag 1 + bool hostile;//flag 2 +} + +struct map_target { + enum map_target_type type; + //Could use a union in theory, but size isn't too important here + struct map_target_city map_target_city; + struct map_target_terrain map_target_terrain; + struct map_target_resource map_target_resource; + struct map_target_unit map_target_unit; +} + +struct map_target_separation_rule { + struct map_target target; + bool require; + + int distance_metric_flags; + int chebyshev;//flag 1 + int manhatten;//flag 2 + int euclidean_percent;//flag 3 +} + struct minimum_city_separation { int any_chebyshev; int any_manhatten; @@ -158,6 +218,8 @@ struct c3x_config { bool skip_repeated_tile_improv_replacement_asks; bool autofill_best_gold_amount_when_trading; struct minimum_city_separation minimum_city_separation; + struct map_target_separation_rule * minimum_city_separation_rules; + int count_minimum_city_separation_rules; bool enable_trade_screen_scroll; bool group_units_on_right_click_menu; bool gray_out_units_on_menu_with_no_remaining_moves; diff --git a/injected_code.c b/injected_code.c index f217015e..6b8d8f7f 100644 --- a/injected_code.c +++ b/injected_code.c @@ -15184,15 +15184,37 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c } } - struct minimum_city_separation min_sep = is->current_config.minimum_city_separation; - int min_sep_chebyshev = min_sep.any_chebyshev > min_sep.foreign_chebyshev ? min_sep.any_chebyshev : min_sep.foreign_chebyshev; - int min_sep_manhatten = min_sep.any_manhatten > min_sep.foreign_manhatten ? min_sep.any_manhatten : min_sep.foreign_manhatten; - int min_sep_box = min_sep_manhatten < min_sep_chebyshev ? min_sep_manhatten : min_sep_chebyshev; CityLocValidity base_result = Map_check_city_location (this, __, tile_x, tile_y, civ_id, check_for_city_on_tile); - if (base_result != CLV_OK && base_result != CLV_CITY_TOO_CLOSE) return base_result; + struct map_target_separation_rule * city_separation_rules = is->minimum_city_separation_rules; + //init array of bools for requirements check + + //init bounding box + int min_sep_chebyshev = 0; + int min_sep_manhatten = 0; + int min_sep_euclidean_percent = 0; + for (int i = 0; i < is->count_minimum_city_separation_rules; i++) { + struct map_target_separation_rule * current_rule = city_separation_rules + i * sizeof(map_target_separation_rule); + if (current_rule->distance_metric_flags & 1 != 0 && current_rule->chebyshev > min_sep_chebyshev) + min_sep_chebyshev = current_rule->chebyshev; + if (current_rule->distance_metric_flags & 2 != 0 && current_rule->manhatten > min_sep_manhatten) + min_sep_manhatten = current_rule->manhatten; + if (current_rule->distance_metric_flags & 4 != 0 && current_rule->euclidean_percent > min_sep_euclidean_percent) + min_sep_euclidean_percent = current_rule->euclidean_percent; + } + int min_sep_box = min_sep_chebyshev; + if (min_sep_manhatten > min_sep_box) + min_sep_box = min_sep_manhatten; + if ((min_sep_euclidean_percent + 99) / 100 > min_sep_box) + min_sep_box = (min_sep_euclidean_percent + 99) / 100 + + /*struct minimum_city_separation min_sep = is->current_config.minimum_city_separation; + int min_sep_chebyshev = min_sep.any_chebyshev > min_sep.foreign_chebyshev ? min_sep.any_chebyshev : min_sep.foreign_chebyshev; + int min_sep_manhatten = min_sep.any_manhatten > min_sep.foreign_manhatten ? min_sep.any_manhatten : min_sep.foreign_manhatten; + int min_sep_box = min_sep_manhatten < min_sep_chebyshev ? min_sep_manhatten : min_sep_chebyshev;*/ + //Otherwise perform calculation ourselves //start calcing dx/dy at 45 degrees / on a virtual grid for (int dx = -min_sep_box; dx <= min_sep_box; dx++) { @@ -15201,13 +15223,36 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c int absy = int_abs(dy); int chebyshev = absx > absy ? absx : absy; int manhatten = absx + absy; - //Shortcut in case the tile is not inside either the global rule or the foreign rule - if (chebyshev > min_sep_chebyshev || manhatten > min_sep_manhatten) - continue; + int euclidean_percent_squared = (dx*dx) + (dy*dy) * 10000; //transform to map coords int tx = tile_x + dx-dy; int ty = tile_y + dy+dx; wrap_tile_coords (&p_bic_data->Map, &tx, &ty); + + //Now check each rule + for (int i = 0; i < is->count_minimum_city_separation_rules; i++) { + struct map_target_separation_rule * current_rule = city_separation_rules + i * sizeof(map_target_separation_rule); + //Check tile is within rule's radius + if (current_rule->distance_metric_flags & 1 != 0 && current_rule->chebyshev <= chebyshev) + continue; + if (current_rule->distance_metric_flags & 2 != 0 && current_rule->manhatten <= manhatten) + continue; + if (current_rule->distance_metric_flags & 4 != 0 && (current_rule->euclidean_percent * current_rule->euclidean_percent) <= euclidean_percent_squared) + continue; + //Rule applies + bool matches = match_target(current_rule + offsetof (struct map_target_separation_rule, target)) + if (current_rule->require) { + //not implemented: see array of bools for requirements check + } else { + return CLV_CITY_TOO_CLOSE; //Kinda abusing this name since we now check for other things + } + } + + //Shortcut in case the tile is not inside either the global rule or the foreign rule + //if (chebyshev > min_sep_chebyshev || manhatten > min_sep_manhatten) + // continue; + + /* City * city = city_at(tx, ty); if (city != NULL) { //Apply the global rule @@ -15218,12 +15263,45 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c if (chebyshev <= min_sep.foreign_chebyshev && manhatten <= min_sep.foreign_manhatten) return CLV_CITY_TOO_CLOSE; } - } + }*/ } } + //Now check that all requirements are met - see array of bools requirements check + + return CLV_OK; } +bool +match_target(map_target * target, int tile_x, int tile_y, int civ_id) +{ + if (target->type == CITY) { + City * city = city_at(tile_x, tile_y); + return match_target_city(target + offsetof (struct map_target, map_target_city), city, civ_id); + } else { + //Not implemented + return false; + } +} +match_target_city (map_target_city * target, City * city, int civ_id) +{ + if (city == NULL) + return false; + if (target->flags & 1 != 0 && city->Body.CivId == civ_id) + return false; + if (target->flags & 2 != 0) + return false; //war check not implemented + if (target->flags & 4 != 0 && city->Body.Population.Size < target->population_min) + return false; + if (target->flags & 8 != 0 && city->Body.Population.Size > target->population_max) + return false; + if (target->flags & 16 != 0) + return false; //culture check not implemented + if (target->flags & 32 != 0) + return false; //culture check not implemented + return true; +} + bool is_zero_strength (UnitType * ut) { From 469d783b42e5b672d37b4d724e941d0c25de6b5d Mon Sep 17 00:00:00 2001 From: Philiquaz Date: Fri, 21 Nov 2025 07:06:31 +0000 Subject: [PATCH 5/8] introduce rule min/max, add logic to check these --- C3X.h | 3 ++- injected_code.c | 48 ++++++++++++++++++------------------------------ 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/C3X.h b/C3X.h index 9b15d93c..879a93b7 100644 --- a/C3X.h +++ b/C3X.h @@ -189,7 +189,8 @@ struct map_target { struct map_target_separation_rule { struct map_target target; - bool require; + int min_count; + int max_count; int distance_metric_flags; int chebyshev;//flag 1 diff --git a/injected_code.c b/injected_code.c index 6b8d8f7f..77ba96cc 100644 --- a/injected_code.c +++ b/injected_code.c @@ -15190,6 +15190,7 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c struct map_target_separation_rule * city_separation_rules = is->minimum_city_separation_rules; //init array of bools for requirements check + int * rule_matches = malloc (sizeof(int) * is->count_minimum_city_separation_rules); //init bounding box int min_sep_chebyshev = 0; @@ -15209,11 +15210,6 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c min_sep_box = min_sep_manhatten; if ((min_sep_euclidean_percent + 99) / 100 > min_sep_box) min_sep_box = (min_sep_euclidean_percent + 99) / 100 - - /*struct minimum_city_separation min_sep = is->current_config.minimum_city_separation; - int min_sep_chebyshev = min_sep.any_chebyshev > min_sep.foreign_chebyshev ? min_sep.any_chebyshev : min_sep.foreign_chebyshev; - int min_sep_manhatten = min_sep.any_manhatten > min_sep.foreign_manhatten ? min_sep.any_manhatten : min_sep.foreign_manhatten; - int min_sep_box = min_sep_manhatten < min_sep_chebyshev ? min_sep_manhatten : min_sep_chebyshev;*/ //Otherwise perform calculation ourselves //start calcing dx/dy at 45 degrees / on a virtual grid @@ -15229,7 +15225,7 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c int ty = tile_y + dy+dx; wrap_tile_coords (&p_bic_data->Map, &tx, &ty); - //Now check each rule + //Now check each rule (order things this way to not recalculate positions and distances... is this even sensible?) for (int i = 0; i < is->count_minimum_city_separation_rules; i++) { struct map_target_separation_rule * current_rule = city_separation_rules + i * sizeof(map_target_separation_rule); //Check tile is within rule's radius @@ -15240,35 +15236,27 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c if (current_rule->distance_metric_flags & 4 != 0 && (current_rule->euclidean_percent * current_rule->euclidean_percent) <= euclidean_percent_squared) continue; //Rule applies - bool matches = match_target(current_rule + offsetof (struct map_target_separation_rule, target)) - if (current_rule->require) { - //not implemented: see array of bools for requirements check - } else { + if(!match_target(current_rule + offsetof (struct map_target_separation_rule, target))) + continue; + //Increment rule counter + rule_matches[i] += 1; + //Check if rule max count is exceeded and exit early + if (current_rule->max_count < rule_matches[i]) { + free (rule_matches); return CLV_CITY_TOO_CLOSE; //Kinda abusing this name since we now check for other things } } - - //Shortcut in case the tile is not inside either the global rule or the foreign rule - //if (chebyshev > min_sep_chebyshev || manhatten > min_sep_manhatten) - // continue; - - /* - City * city = city_at(tx, ty); - if (city != NULL) { - //Apply the global rule - if (chebyshev <= min_sep.any_chebyshev && manhatten <= min_sep.any_manhatten) - return CLV_CITY_TOO_CLOSE; - //Apply the foreign rule - if (city->Body.CivID != civ_id) { - if (chebyshev <= min_sep.foreign_chebyshev && manhatten <= min_sep.foreign_manhatten) - return CLV_CITY_TOO_CLOSE; - } - }*/ } } - //Now check that all requirements are met - see array of bools requirements check - - + //Check if each rule minimum count is met - no need to check max count, we do that when incrementing + for (int i = 0; i < is->count_minimum_city_separation_rules; i++) { + struct map_target_separation_rule * current_rule = city_separation_rules + i * sizeof(map_target_separation_rule); + if (rule_matches[i] < current_rule->min_count) { + free (rule_matches); + return CLV_CITY_TOO_CLOSE; //Kinda abusing this name since we now check for other things + } + } + free (rule_matches); return CLV_OK; } From d72e33cb1c363d06254c6949f3cba3c86dbaad95 Mon Sep 17 00:00:00 2001 From: Philiquaz Date: Fri, 21 Nov 2025 10:41:39 +0000 Subject: [PATCH 6/8] Begin config parsing. also relabel flags. --- C3X.h | 38 +++++++-------- injected_code.c | 124 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 142 insertions(+), 20 deletions(-) diff --git a/C3X.h b/C3X.h index 879a93b7..b2f296eb 100644 --- a/C3X.h +++ b/C3X.h @@ -146,36 +146,36 @@ enum map_target_type { struct map_target_city { int flags; - bool foreign;//flag 0 - bool hostile;//flag 1 - int population_min;//flag 2 - int population_max;//flag 3 - int culture_min;//flag 4 - int culture_max;//flag 5 + bool foreign;//flag 1 + bool hostile;//flag 2 + int population_min;//flag 4 + int population_max;//flag 8 + int culture_min;//flag 16 + int culture_max;//flag 32 } struct map_target_terrain { int flags; - int type;//flag 0 - int food_yield_min;//flag 1 - int food_yield_max;//flag 2 - int shield_yield_min;//flag 3 - int shield_yield_max;//flag 4 - int commerce_yield_min;//flag 5 - int commerce_yield_max;//flag 6 + int type;//flag 1 + int food_yield_min;//flag 2 + int food_yield_max;//flag 4 + int shield_yield_min;//flag 8 + int shield_yield_max;//flag 16 + int commerce_yield_min;//flag 32 + int commerce_yield_max;//flag 64 } struct map_target_resource { int flags; - int type;//flag 0 - bool is_visible;//flag 1 + int type;//flag 1 + bool is_visible;//flag 2 } struct map_target_unit { int flags; - int type;//flag 0 - bool foreign;//flag 1 - bool hostile;//flag 2 + int type;//flag 1 + bool foreign;//flag 2 + bool hostile;//flag 4 } struct map_target { @@ -195,7 +195,7 @@ struct map_target_separation_rule { int distance_metric_flags; int chebyshev;//flag 1 int manhatten;//flag 2 - int euclidean_percent;//flag 3 + int euclidean_percent;//flag 4 } struct minimum_city_separation { diff --git a/injected_code.c b/injected_code.c index 77ba96cc..47acfe10 100644 --- a/injected_code.c +++ b/injected_code.c @@ -1359,6 +1359,127 @@ parse_work_area_improvement (char ** p_cursor, struct error_line ** p_unrecogniz return RPR_PARSE_ERROR; } +enum recognizable_parse_result +parse_city_separation_rule (char ** p_cursor, struct error_line ** p_unrecognized_lines, void * out_parsed_city_separation_rule) +{ + char * cur = *p_cursor; + struct map_target_separation_rule * out = out_parsed_city_separation_rule; + + struct string_slice target_type; + if (skip_white_space (&cur) && + parse_map_target (&cur, out + offsetof (struct map_target_separation_rule, target)) && + skip_punctuation (&cur, ':')) { + + string_slice and_separator; + do { + int num; + if (!skip_white_space (&cur) || !parse_int (&cur, &num)) + return RPR_PARSE_ERROR; + + out->flags = 0; + + struct string_slice metric_type; + if (parse_string (&cur, &metric_type)) { + if (slice_matches_str (&metric_type, "chebychev")) { + out->chebychev = num; + out->flags |= 1; + } else if (slice_matches_str (&metric_type, "manhatten")) { + out->manhatten = num; + out->flags |= 2; + } else if (slice_matches_str (&metric_type, "euclidean_percent")) { + out->euclidean_percent = num; + out->flags |= 4; + } else + return RPR_PARSE_ERROR; + } else {//Default to chebychev if nothing stated. + out->chebychev = num; + out->flags |= 1; + } + } while (skip_white_space (&cur) && parse_string(&cur, &and_separator) && slice_matches_str(&and_separator, "and")) + + } else + return RPR_PARSE_ERROR; +} + +bool +parse_map_target (char ** p_cursor, struct map_target * target) +{ + struct string_slice target_type; + if (!parse_string (&cur, &target_type)) + return false; + if (slice_matches_str (&target_type, "city")) { + target->type = CITY; + return parse_map_target_city (char ** p_cursor, target + offsetof (struct map_target, map_target_city)) + } else if (slice_matches_str (&target_type, "terrain")) { + target->type = TERRAIN; + //unimpl + return false; + } else if (slice_matches_str (&target_type, "resource")) { + target->type = RESOURCE; + //unimpl + return false; + } else if (slice_matches_str (&target_type, "unit")) { + target->type = UNIT; + //unimpl + return false; + } else { + return false; + } +} + +bool +parse_map_target_city (char ** p_cursor, struct map_target_city * target) +{ + if (!skip_white_space (&cur)) + return false; + struct string_slice condition_name; + target->flags = 0; + while (parse_string (&cur, &condition_name)) { + if (slice_matches_str(&condition_name, "foreign")) { + target->flags |= 1; + target->foreign = true; + } else if (slice_matches_str(&condition_name, "hostile")) { + target->flags |= 2; + target->hostile = true; + } else if (slice_matches_str(&condition_name, "population")) { + if (!skip_white_space (&cur)) + return false; + int num; + if (!skip_white_space (&cur) || !parse_int (&cur, &num)) + return false; + target->flags |= 4; + target->population_min = num; + if (skip_white_space (&cur) && skip_punctuation(&cur, '-')) { + if (skip_white_space(&cur) && parse_int (&cur, &num)) { + target->flags |= 8; + target->population_max = num; + } else { + return false; + } + } + } else if (slice_matches_str(&condition_name, "culture")) { + if (!skip_white_space (&cur)) + return false; + int num; + if (!skip_white_space (&cur) || !parse_int (&cur, &num)) + return false; + target->flags |= 16; + target->culture_min = num; + if (skip_white_space (&cur) && skip_punctuation(&cur, '-')) { + if (skip_white_space(&cur) && parse_int (&cur, &num)) { + target->flags |= 32; + target->culture_max = num; + } else { + return false; + } + } + } else { + return false; + } + } + +} + // Recognizable items are appended to out_list/count, which must have been previously initialized (NULL/0 is valid for an empty list). // If an error occurs while reading, returns the location of the error inside the slice, specifically the number of characters before the unreadable // item. If no error occurs, returns -1. @@ -15190,6 +15311,7 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c struct map_target_separation_rule * city_separation_rules = is->minimum_city_separation_rules; //init array of bools for requirements check + //dislike reallocating each time, but injected_state is a pain and I also don't want to bleed state into the config structs. int * rule_matches = malloc (sizeof(int) * is->count_minimum_city_separation_rules); //init bounding box @@ -15209,7 +15331,7 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c if (min_sep_manhatten > min_sep_box) min_sep_box = min_sep_manhatten; if ((min_sep_euclidean_percent + 99) / 100 > min_sep_box) - min_sep_box = (min_sep_euclidean_percent + 99) / 100 + min_sep_box = (min_sep_euclidean_percent + 99) / 100; //Otherwise perform calculation ourselves //start calcing dx/dy at 45 degrees / on a virtual grid From 6ea1304880e974c2403690b7b33f08409b0a2f8f Mon Sep 17 00:00:00 2001 From: Philiquaz Date: Sat, 22 Nov 2025 22:39:53 +0000 Subject: [PATCH 7/8] Compile fixes --- C3X.h | 14 ++-- injected_code.c | 218 ++++++++++++++++++++++++------------------------ 2 files changed, 118 insertions(+), 114 deletions(-) diff --git a/C3X.h b/C3X.h index b2f296eb..5416385e 100644 --- a/C3X.h +++ b/C3X.h @@ -142,7 +142,7 @@ enum map_target_type { TERRAIN,//flag food_yield, flag shield_yield, flag commerce_yield RESOURCE,//flag UNIT,//flag foreign, flag hostile -} +}; struct map_target_city { int flags; @@ -152,7 +152,7 @@ struct map_target_city { int population_max;//flag 8 int culture_min;//flag 16 int culture_max;//flag 32 -} +}; struct map_target_terrain { int flags; @@ -163,20 +163,20 @@ struct map_target_terrain { int shield_yield_max;//flag 16 int commerce_yield_min;//flag 32 int commerce_yield_max;//flag 64 -} +}; struct map_target_resource { int flags; int type;//flag 1 bool is_visible;//flag 2 -} +}; struct map_target_unit { int flags; int type;//flag 1 bool foreign;//flag 2 bool hostile;//flag 4 -} +}; struct map_target { enum map_target_type type; @@ -185,7 +185,7 @@ struct map_target { struct map_target_terrain map_target_terrain; struct map_target_resource map_target_resource; struct map_target_unit map_target_unit; -} +}; struct map_target_separation_rule { struct map_target target; @@ -196,7 +196,7 @@ struct map_target_separation_rule { int chebyshev;//flag 1 int manhatten;//flag 2 int euclidean_percent;//flag 4 -} +}; struct minimum_city_separation { int any_chebyshev; diff --git a/injected_code.c b/injected_code.c index 47acfe10..ffd825a4 100644 --- a/injected_code.c +++ b/injected_code.c @@ -1359,77 +1359,10 @@ parse_work_area_improvement (char ** p_cursor, struct error_line ** p_unrecogniz return RPR_PARSE_ERROR; } -enum recognizable_parse_result -parse_city_separation_rule (char ** p_cursor, struct error_line ** p_unrecognized_lines, void * out_parsed_city_separation_rule) -{ - char * cur = *p_cursor; - struct map_target_separation_rule * out = out_parsed_city_separation_rule; - - struct string_slice target_type; - if (skip_white_space (&cur) && - parse_map_target (&cur, out + offsetof (struct map_target_separation_rule, target)) && - skip_punctuation (&cur, ':')) { - - string_slice and_separator; - do { - int num; - if (!skip_white_space (&cur) || !parse_int (&cur, &num)) - return RPR_PARSE_ERROR; - - out->flags = 0; - - struct string_slice metric_type; - if (parse_string (&cur, &metric_type)) { - if (slice_matches_str (&metric_type, "chebychev")) { - out->chebychev = num; - out->flags |= 1; - } else if (slice_matches_str (&metric_type, "manhatten")) { - out->manhatten = num; - out->flags |= 2; - } else if (slice_matches_str (&metric_type, "euclidean_percent")) { - out->euclidean_percent = num; - out->flags |= 4; - } else - return RPR_PARSE_ERROR; - } else {//Default to chebychev if nothing stated. - out->chebychev = num; - out->flags |= 1; - } - } while (skip_white_space (&cur) && parse_string(&cur, &and_separator) && slice_matches_str(&and_separator, "and")) - - } else - return RPR_PARSE_ERROR; -} - -bool -parse_map_target (char ** p_cursor, struct map_target * target) -{ - struct string_slice target_type; - if (!parse_string (&cur, &target_type)) - return false; - if (slice_matches_str (&target_type, "city")) { - target->type = CITY; - return parse_map_target_city (char ** p_cursor, target + offsetof (struct map_target, map_target_city)) - } else if (slice_matches_str (&target_type, "terrain")) { - target->type = TERRAIN; - //unimpl - return false; - } else if (slice_matches_str (&target_type, "resource")) { - target->type = RESOURCE; - //unimpl - return false; - } else if (slice_matches_str (&target_type, "unit")) { - target->type = UNIT; - //unimpl - return false; - } else { - return false; - } -} - bool parse_map_target_city (char ** p_cursor, struct map_target_city * target) { + char * cur = *p_cursor; if (!skip_white_space (&cur)) return false; struct string_slice condition_name; @@ -1477,7 +1410,77 @@ parse_map_target_city (char ** p_cursor, struct map_target_city * target) return false; } } + return true; + +} + +bool +parse_map_target (char ** p_cursor, struct map_target * target) +{ + char * cur = *p_cursor; + struct string_slice target_type; + if (!parse_string (&cur, &target_type)) + return false; + if (slice_matches_str (&target_type, "city")) { + target->type = CITY; + return parse_map_target_city (p_cursor, (struct map_target_city *) (target + offsetof (struct map_target, map_target_city))); + } else if (slice_matches_str (&target_type, "terrain")) { + target->type = TERRAIN; + //unimpl + return false; + } else if (slice_matches_str (&target_type, "resource")) { + target->type = RESOURCE; + //unimpl + return false; + } else if (slice_matches_str (&target_type, "unit")) { + target->type = UNIT; + //unimpl + return false; + } else { + return false; + } +} +enum recognizable_parse_result +parse_city_separation_rule (char ** p_cursor, struct error_line ** p_unrecognized_lines, void * out_parsed_city_separation_rule) +{ + char * cur = *p_cursor; + struct map_target_separation_rule * out = out_parsed_city_separation_rule; + + struct string_slice target_type; + if (skip_white_space (&cur) && + parse_map_target (&cur, (struct map_target *) (out + offsetof (struct map_target_separation_rule, target))) && + skip_punctuation (&cur, ':')) { + + struct string_slice and_separator; + do { + int num; + if (!skip_white_space (&cur) || !parse_int (&cur, &num)) + return RPR_PARSE_ERROR; + + out->distance_metric_flags = 0; + + struct string_slice metric_type; + if (parse_string (&cur, &metric_type)) { + if (slice_matches_str (&metric_type, "chebychev")) { + out->chebyshev = num; + out->distance_metric_flags |= 1; + } else if (slice_matches_str (&metric_type, "manhatten")) { + out->manhatten = num; + out->distance_metric_flags |= 2; + } else if (slice_matches_str (&metric_type, "euclidean_percent")) { + out->euclidean_percent = num; + out->distance_metric_flags |= 4; + } else + return RPR_PARSE_ERROR; + } else {//Default to chebyshev if nothing stated. + out->chebyshev = num; + out->distance_metric_flags |= 1; + } + } while (skip_white_space (&cur) && parse_string(&cur, &and_separator) && slice_matches_str(&and_separator, "and")); + return RPR_OK; + } else + return RPR_PARSE_ERROR; } // Recognizable items are appended to out_list/count, which must have been previously initialized (NULL/0 is valid for an empty list). @@ -15291,6 +15294,37 @@ patch_PopupForm_set_text_key_and_flags (PopupForm * this, int edx, char * script PopupForm_set_text_key_and_flags (this, __, script_path, text_key, param_3, param_4, param_5, param_6); } +bool +match_target_city (struct map_target_city * target, City * city, int civ_id) +{ + if (city == NULL) + return false; + if (target->flags & 1 != 0 && city->Body.CivID == civ_id) + return false; + if (target->flags & 2 != 0) + return false; //war check not implemented + if (target->flags & 4 != 0 && city->Body.Population.Size < target->population_min) + return false; + if (target->flags & 8 != 0 && city->Body.Population.Size > target->population_max) + return false; + if (target->flags & 16 != 0) + return false; //culture check not implemented + if (target->flags & 32 != 0) + return false; //culture check not implemented + return true; +} +bool +match_target(struct map_target * target, int tile_x, int tile_y, int civ_id) +{ + if (target->type == CITY) { + City * city = city_at(tile_x, tile_y); + return match_target_city((struct map_target_city *)(target + offsetof (struct map_target, map_target_city)), city, civ_id); + } else { + //Not implemented + return false; + } +} + CityLocValidity __fastcall patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int civ_id, bool check_for_city_on_tile) { @@ -15309,17 +15343,17 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c if (base_result != CLV_OK && base_result != CLV_CITY_TOO_CLOSE) return base_result; - struct map_target_separation_rule * city_separation_rules = is->minimum_city_separation_rules; + struct map_target_separation_rule * city_separation_rules = is->current_config.minimum_city_separation_rules; //init array of bools for requirements check //dislike reallocating each time, but injected_state is a pain and I also don't want to bleed state into the config structs. - int * rule_matches = malloc (sizeof(int) * is->count_minimum_city_separation_rules); + int * rule_matches = malloc (sizeof(int) * is->current_config.count_minimum_city_separation_rules); //init bounding box int min_sep_chebyshev = 0; int min_sep_manhatten = 0; int min_sep_euclidean_percent = 0; - for (int i = 0; i < is->count_minimum_city_separation_rules; i++) { - struct map_target_separation_rule * current_rule = city_separation_rules + i * sizeof(map_target_separation_rule); + for (int i = 0; i < is->current_config.count_minimum_city_separation_rules; i++) { + struct map_target_separation_rule * current_rule = city_separation_rules + i * sizeof(struct map_target_separation_rule); if (current_rule->distance_metric_flags & 1 != 0 && current_rule->chebyshev > min_sep_chebyshev) min_sep_chebyshev = current_rule->chebyshev; if (current_rule->distance_metric_flags & 2 != 0 && current_rule->manhatten > min_sep_manhatten) @@ -15348,8 +15382,8 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c wrap_tile_coords (&p_bic_data->Map, &tx, &ty); //Now check each rule (order things this way to not recalculate positions and distances... is this even sensible?) - for (int i = 0; i < is->count_minimum_city_separation_rules; i++) { - struct map_target_separation_rule * current_rule = city_separation_rules + i * sizeof(map_target_separation_rule); + for (int i = 0; i < is->current_config.count_minimum_city_separation_rules; i++) { + struct map_target_separation_rule * current_rule = city_separation_rules + i * sizeof(struct map_target_separation_rule); //Check tile is within rule's radius if (current_rule->distance_metric_flags & 1 != 0 && current_rule->chebyshev <= chebyshev) continue; @@ -15358,7 +15392,7 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c if (current_rule->distance_metric_flags & 4 != 0 && (current_rule->euclidean_percent * current_rule->euclidean_percent) <= euclidean_percent_squared) continue; //Rule applies - if(!match_target(current_rule + offsetof (struct map_target_separation_rule, target))) + if(!match_target((struct map_target *)(current_rule + offsetof (struct map_target_separation_rule, target)), tx, ty, civ_id)) continue; //Increment rule counter rule_matches[i] += 1; @@ -15371,8 +15405,8 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c } } //Check if each rule minimum count is met - no need to check max count, we do that when incrementing - for (int i = 0; i < is->count_minimum_city_separation_rules; i++) { - struct map_target_separation_rule * current_rule = city_separation_rules + i * sizeof(map_target_separation_rule); + for (int i = 0; i < is->current_config.count_minimum_city_separation_rules; i++) { + struct map_target_separation_rule * current_rule = city_separation_rules + i * sizeof(struct map_target_separation_rule); if (rule_matches[i] < current_rule->min_count) { free (rule_matches); return CLV_CITY_TOO_CLOSE; //Kinda abusing this name since we now check for other things @@ -15382,36 +15416,6 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c return CLV_OK; } -bool -match_target(map_target * target, int tile_x, int tile_y, int civ_id) -{ - if (target->type == CITY) { - City * city = city_at(tile_x, tile_y); - return match_target_city(target + offsetof (struct map_target, map_target_city), city, civ_id); - } else { - //Not implemented - return false; - } -} -match_target_city (map_target_city * target, City * city, int civ_id) -{ - if (city == NULL) - return false; - if (target->flags & 1 != 0 && city->Body.CivId == civ_id) - return false; - if (target->flags & 2 != 0) - return false; //war check not implemented - if (target->flags & 4 != 0 && city->Body.Population.Size < target->population_min) - return false; - if (target->flags & 8 != 0 && city->Body.Population.Size > target->population_max) - return false; - if (target->flags & 16 != 0) - return false; //culture check not implemented - if (target->flags & 32 != 0) - return false; //culture check not implemented - return true; -} - bool is_zero_strength (UnitType * ut) { From 404b4d5688ddef850e178de9fd856bba81838951 Mon Sep 17 00:00:00 2001 From: Philiquaz Date: Sun, 23 Nov 2025 07:40:51 +0000 Subject: [PATCH 8/8] Add rule enable/disable by tech. No parser yet. --- C3X.h | 2 ++ injected_code.c | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/C3X.h b/C3X.h index 5416385e..bbf3ac29 100644 --- a/C3X.h +++ b/C3X.h @@ -191,6 +191,8 @@ struct map_target_separation_rule { struct map_target target; int min_count; int max_count; + int enable_tech; + int disable_tech; int distance_metric_flags; int chebyshev;//flag 1 diff --git a/injected_code.c b/injected_code.c index ffd825a4..d09edf37 100644 --- a/injected_code.c +++ b/injected_code.c @@ -15325,6 +15325,18 @@ match_target(struct map_target * target, int tile_x, int tile_y, int civ_id) } } +bool +map_target_separation_rule_active(struct map_target_separation_rule * rule, int civ_id) +{ + if (rule == NULL) + return false; + if (rule->enable_tech != 0 && !Leader_has_tech (&leaders[civ_id], __, rule->enable_tech) + return false; + if (rule->disable_tech != 0 && Leader_has_tech (&leaders[civ_id], __, rule->disable_tech)) + return false; + return true; +} + CityLocValidity __fastcall patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int civ_id, bool check_for_city_on_tile) { @@ -15354,6 +15366,8 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c int min_sep_euclidean_percent = 0; for (int i = 0; i < is->current_config.count_minimum_city_separation_rules; i++) { struct map_target_separation_rule * current_rule = city_separation_rules + i * sizeof(struct map_target_separation_rule); + if (!map_target_separation_rule_active(current_rule, civ_id)) + continue; if (current_rule->distance_metric_flags & 1 != 0 && current_rule->chebyshev > min_sep_chebyshev) min_sep_chebyshev = current_rule->chebyshev; if (current_rule->distance_metric_flags & 2 != 0 && current_rule->manhatten > min_sep_manhatten) @@ -15384,6 +15398,8 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c //Now check each rule (order things this way to not recalculate positions and distances... is this even sensible?) for (int i = 0; i < is->current_config.count_minimum_city_separation_rules; i++) { struct map_target_separation_rule * current_rule = city_separation_rules + i * sizeof(struct map_target_separation_rule); + if (!map_target_separation_rule_active(current_rule, civ_id)) + continue; //Check tile is within rule's radius if (current_rule->distance_metric_flags & 1 != 0 && current_rule->chebyshev <= chebyshev) continue; @@ -15407,6 +15423,8 @@ patch_Map_check_city_location (Map *this, int edx, int tile_x, int tile_y, int c //Check if each rule minimum count is met - no need to check max count, we do that when incrementing for (int i = 0; i < is->current_config.count_minimum_city_separation_rules; i++) { struct map_target_separation_rule * current_rule = city_separation_rules + i * sizeof(struct map_target_separation_rule); + if (!map_target_separation_rule_active(current_rule, civ_id)) + continue; if (rule_matches[i] < current_rule->min_count) { free (rule_matches); return CLV_CITY_TOO_CLOSE; //Kinda abusing this name since we now check for other things