Skip to content
Draft
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
10 changes: 8 additions & 2 deletions C3X.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
21 changes: 14 additions & 7 deletions default.c3x_config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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.) 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
; 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.
Expand Down
114 changes: 54 additions & 60 deletions injected_code.c
Original file line number Diff line number Diff line change
Expand Up @@ -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")) {
Expand Down Expand Up @@ -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)},
Expand Down Expand Up @@ -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)},
Expand Down Expand Up @@ -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 ((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
Expand All @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -15167,67 +15184,44 @@ 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 < 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 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))

//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
Expand Down