Project

General

Profile

Feature #654 » 0007-Add-Region-Surrounded-TileRel-requirement.patch

main v3 - Alina Lenk, 06/02/2024 12:36 PM

View differences:

client/packhand.c
static inline int *continent_adjacency_count(Continent_id cont1,
Continent_id cont2)
{
if (cont1 > 0) {
if (cont2 == 0) {
return &wld.map.client.continent_unknown_adj_counts[cont1];
}
} else if (cont1 < 0) {
if (cont2 == 0) {
return &wld.map.client.ocean_unknown_adj_counts[-cont1];
}
} else {
if (cont2 > 0) {
return &wld.map.client.continent_unknown_adj_counts[cont2];
} else if (cont2 < 0) {
return &wld.map.client.ocean_unknown_adj_counts[-cont2];
}
if (cont1 == cont2) {
return nullptr;
} else if (cont1 >= 0 && cont2 <= 0) {
return &wld.map.client.adj_matrix[cont1][-cont2];
} else if (cont1 <= 0 && cont2 >= 0) {
return &wld.map.client.adj_matrix[cont2][-cont1];
}
return nullptr;
}
......
{
/* Update known continents */
if (new_cont > wld.map.num_continents) {
int i;
int cont;
/* Expand sizes array */
wld.map.continent_sizes = fc_realloc(wld.map.continent_sizes,
(new_cont + 1) * sizeof(*wld.map.continent_sizes));
/* Expand unknown tile adjacency counts array */
wld.map.client.continent_unknown_adj_counts = fc_realloc(
wld.map.client.continent_unknown_adj_counts,
(new_cont + 1) * sizeof(*wld.map.client.continent_unknown_adj_counts)
);
/* Expand adjacency matrix */
wld.map.client.adj_matrix = fc_realloc(wld.map.client.adj_matrix,
(new_cont + 1) * sizeof(*wld.map.client.adj_matrix));
/* Fill new spots with zeros */
for (i = wld.map.num_continents + 1; i <= new_cont; i++) {
wld.map.continent_sizes[i] = 0;
wld.map.client.continent_unknown_adj_counts[i] = 0;
/* Initialize new spots */
for (cont = wld.map.num_continents + 1; cont <= new_cont; cont++) {
wld.map.continent_sizes[cont] = 0;
wld.map.client.adj_matrix[cont] = fc_calloc(wld.map.num_oceans + 1,
sizeof(*wld.map.client.adj_matrix[cont]));
}
wld.map.num_continents = new_cont;
} else if (new_cont < -wld.map.num_oceans) {
int i;
int cont, ocean;
/* Expand sizes array */
wld.map.ocean_sizes = fc_realloc(wld.map.ocean_sizes,
(-new_cont + 1) * sizeof(*wld.map.ocean_sizes));
/* Expand unknown tile adjacency counts array */
wld.map.client.ocean_unknown_adj_counts = fc_realloc(
wld.map.client.ocean_unknown_adj_counts,
(-new_cont + 1) * sizeof(*wld.map.client.ocean_unknown_adj_counts)
);
/* Initialize new spots */
for (ocean = wld.map.num_oceans + 1; ocean <= -new_cont; ocean++) {
wld.map.ocean_sizes[ocean] = 0;
}
/* Expand adjacency matrix */
for (cont = 0; cont <= wld.map.num_continents; cont++) {
wld.map.client.adj_matrix[cont] = fc_realloc(
wld.map.client.adj_matrix[cont],
(-new_cont + 1) * sizeof(*wld.map.client.adj_matrix[cont])
);
/* Fill new spots with zeros */
for (i = wld.map.num_oceans + 1; i <= -new_cont; i++) {
wld.map.ocean_sizes[i] = 0;
wld.map.client.ocean_unknown_adj_counts[i] = 0;
for (ocean = wld.map.num_oceans + 1; ocean <= -new_cont; ocean++) {
wld.map.client.adj_matrix[cont][ocean] = 0;
}
}
wld.map.num_oceans = -new_cont;
client/text.c
if (cont > 0) {
int size = get_continent_size(cont);
if (wld.map.client.continent_unknown_adj_counts[cont] > 0) {
astr_add_line(&str, _("Continent size: at least %d"), size);
} else {
Continent_id surrounder = get_island_surrounder(cont);
if (is_whole_continent_known(cont)) {
astr_add_line(&str, _("Continent size: %d"), size);
if (surrounder < 0) {
astr_add_line(&str, _("Surrounded by ocean %d"), -surrounder);
}
} else {
astr_add_line(&str, _("Continent size: at least %d"), size);
if (surrounder < 0) {
astr_add_line(&str, _("Maybe surrounded by ocean %d"), -surrounder);
}
}
} else if (cont < 0) {
int size = get_ocean_size(-cont);
if (wld.map.client.ocean_unknown_adj_counts[-cont] > 0) {
astr_add_line(&str, _("Ocean size: at least %d"), size);
} else {
Continent_id surrounder = get_lake_surrounder(cont);
if (is_whole_ocean_known(-cont)) {
astr_add_line(&str, _("Ocean size: %d"), size);
if (surrounder > 0) {
astr_add_line(&str, _("Surrounded by continent %d"), surrounder);
}
} else {
astr_add_line(&str, _("Ocean size: at least %d"), size);
if (surrounder > 0) {
astr_add_line(&str, _("Maybe surrounded by continent %d"),
surrounder);
}
}
}
astr_add_line(&str, _("Latitude: %d"),
common/map.c
imap->continent_sizes = nullptr;
imap->ocean_sizes = nullptr;
imap->lake_surrounders = nullptr;
if (server_side) {
imap->server.mapsize = MAP_DEFAULT_MAPSIZE;
......
imap->server.have_huts = FALSE;
imap->server.have_resources = FALSE;
imap->server.team_placement = MAP_DEFAULT_TEAM_PLACEMENT;
imap->server.island_surrounders = nullptr;
imap->server.lake_surrounders = nullptr;
} else {
imap->client.continent_unknown_adj_counts = nullptr;
imap->client.ocean_unknown_adj_counts = nullptr;
imap->client.adj_matrix = fc_malloc(sizeof(*imap->client.adj_matrix));
*imap->client.adj_matrix = fc_malloc(sizeof(**imap->client.adj_matrix));
}
}
......
}
if (fmap->continent_sizes) {
FC_FREE(fmap->continent_sizes);
fmap->num_continents = 0;
}
if (fmap->ocean_sizes) {
FC_FREE(fmap->ocean_sizes);
fmap->num_oceans = 0;
}
if (fmap->lake_surrounders) {
FC_FREE(fmap->lake_surrounders);
fmap->num_oceans = 0;
}
if (!server_side) {
if (fmap->client.continent_unknown_adj_counts) {
FC_FREE(fmap->client.continent_unknown_adj_counts);
fmap->num_continents = 0;
if (server_side) {
if (fmap->server.island_surrounders) {
FC_FREE(fmap->server.island_surrounders);
}
if (fmap->server.lake_surrounders) {
FC_FREE(fmap->server.lake_surrounders);
}
if (fmap->client.ocean_unknown_adj_counts) {
FC_FREE(fmap->client.ocean_unknown_adj_counts);
fmap->num_oceans = 0;
} else {
if (fmap->client.adj_matrix) {
int i;
for (i = 0; i <= fmap->num_continents; i++) {
if (fmap->client.adj_matrix[i]) {
free(fmap->client.adj_matrix[i]);
}
}
FC_FREE(fmap->client.adj_matrix);
}
}
/* For any code that tries accessing sizes, surrounders etc. */
fmap->num_continents = fmap->num_oceans = 0;
}
/*******************************************************************//**
......
}
/**********************************************************************//**
Get continent surrounding lake, or -1 if there is multiple continents.
Get the single ocean surrounding a given island, a positive value if
there isn't one single surrounding ocean, or 0 if client-side and there
could still be one but we don't know any concrete candidate.
**************************************************************************/
int get_lake_surrounders(Continent_id id)
int get_island_surrounder(Continent_id id)
{
fc_assert_ret_val(id > 0, +1);
fc_assert_ret_val(id <= wld.map.num_continents, +1);
if (is_server()) {
return -wld.map.server.island_surrounders[id];
} else {
Continent_id ocean, surrounder = 0;
for (ocean = 1; ocean <= wld.map.num_oceans; ocean++) {
if (wld.map.client.adj_matrix[id][ocean] > 0) {
if (surrounder == 0) {
surrounder = ocean;
} else if (surrounder != ocean) {
/* More than one adjacent ocean */
return +1;
}
}
}
if (surrounder == 0 && is_whole_continent_known(id)) {
return +1;
} else {
return -surrounder;
}
}
}
/**********************************************************************//**
Get the single continent surrounding a given lake, a negative value if
there isn't one single surrounding continent, or 0 if client-side and
there could still be one but we don't know any concrete candidate.
**************************************************************************/
int get_lake_surrounder(Continent_id id)
{
fc_assert_ret_val(id < 0, -1);
fc_assert_ret_val(id >= -wld.map.num_oceans, -1);
/* Client updates num_oceans, but not lake_surrounders */
fc_assert_ret_val(is_server(), -1);
return wld.map.lake_surrounders[-id];
if (is_server()) {
return wld.map.server.lake_surrounders[-id];
} else {
Continent_id cont, surrounder = 0;
for (cont = 1; cont <= wld.map.num_continents; cont++) {
if (wld.map.client.adj_matrix[cont][-id] > 0) {
if (surrounder == 0) {
surrounder = cont;
} else if (surrounder != cont) {
/* More than one adjacent continent */
return -1;
}
}
}
if (surrounder == 0 && is_whole_ocean_known(-id)) {
return -1;
} else {
return surrounder;
}
}
}
/*******************************************************************//**
common/map.h
int get_continent_size(Continent_id id);
int get_ocean_size(Continent_id id);
int get_lake_surrounders(Continent_id id);
int get_island_surrounder(Continent_id id);
int get_lake_surrounder(Continent_id id);
#define is_whole_continent_known(cont) \
(is_server() || wld.map.client.adj_matrix[cont][0] == 0)
/* Use positive ocean ID */
#define is_whole_ocean_known(ocean) \
(is_server() || wld.map.client.adj_matrix[0][ocean] == 0)
/* Specific functions for start positions. */
struct startpos *map_startpos_by_number(int id);
common/map_types.h
*
* The _sizes arrays give the sizes (in tiles) of each continent and
* ocean.
*
* The lake_surrounders array tells which single continent surrounds each
* ocean; or -1 if there's more than one adjacent continent.
*/
int *continent_sizes;
int *ocean_sizes;
Continent_id *lake_surrounders; /* Not updated at the client */
struct tile *tiles;
struct startpos_hash *startpos_table;
union {
struct {
/* Server settings */
enum mapsize_type mapsize; /* How the map size is defined */
int size; /* Used to calculate [xy]size */
int tilesperplayer; /* Tiles per player; used to calculate size */
......
bool have_huts;
bool have_resources;
enum team_placement team_placement;
/* These arrays are indexed by continent number (or negative of the
* ocean number) so the 0th element is unused and the array is 1 element
* larger than you'd expect.
*
* The _surrounders arrays tell which single continent surrounds each
* ocean, or which single ocean (positive ocean number) surrounds each
* continent; or -1 if there's more than one adjacent region.
*/
Continent_id *island_surrounders;
Continent_id *lake_surrounders;
} server;
struct {
/* These arrays count how many adjacencies there are between known
* tiles of a given continent or ocean and unknown tiles, i.e. if a
* single known tile is adjacent to multiple unknowns (or vice versa)
* it gets counted multiple times.
/* This matrix counts how many adjacencies there are between known
* tiles of each continent and each ocean, as well as between both of
* those and unknown tiles. If a single tile of one region is
* adjacent to multiple tiles of another (or multiple unknowns), it
* gets counted multiple times.
*
* The matrix is continent-major, i.e.
* - adj_matrix[continent][ocean] is the adjacency count between a
* continent and an ocean
* - adj_matrix[continent][0] is the unknown adjacency count for a
* continent
* - adj_matrix[0][ocean] is the unknown adjacency count for an ocean
* - adj_matrix[0][0] is unused
*
* If this is 0 for a continent or ocean, we know for sure that its
* size in continent/ocean_sizes is accurate. */
int *continent_unknown_adj_counts;
int *ocean_unknown_adj_counts;
* If an unknown adjacency count is 0, we know for sure that the
* known size of that continent or ocean is accurate. */
int **adj_matrix;
} client;
};
};
common/metaknowledge.c
if (tile_get_known(other_context->tile, pov_player) == TILE_UNKNOWN) {
return FALSE;
}
if (req->source.value.tilerel == TREL_REGION_SURROUNDED) {
/* Too complicated to figure out */
return FALSE;
}
switch (req->range) {
case REQ_RANGE_TILE:
common/reqtext.c
break;
}
break;
case TREL_REGION_SURROUNDED:
switch (preq->range) {
case REQ_RANGE_TILE:
fc_strlcat(buf, prefix, bufsz);
if (preq->present) {
fc_strlcat(buf, _("Must be on a lake or island surrounded by "
"this continent or ocean."),
bufsz);
} else {
fc_strlcat(buf, _("Must not be on a lake or island surrounded "
"by this continent or ocean."),
bufsz);
}
return TRUE;
case REQ_RANGE_CADJACENT:
fc_strlcat(buf, prefix, bufsz);
if (preq->present) {
fc_strlcat(buf, _("Must be on or cardinally adjacent to a lake "
"or island surrounded by this continent or "
"ocean."),
bufsz);
} else {
fc_strlcat(buf, _("Must not be on nor cardinally adjacent to a "
"lake or island surrounded by this continent "
"or ocean."),
bufsz);
}
return TRUE;
case REQ_RANGE_ADJACENT:
fc_strlcat(buf, prefix, bufsz);
if (preq->present) {
fc_strlcat(buf, _("Must be on or adjacent to a lake or island "
"surrounded by this continent or ocean."),
bufsz);
} else {
fc_strlcat(buf, _("Must not be on nor adjacent to a lake or "
"island surrounded by this continent or "
"ocean."),
bufsz);
}
return TRUE;
case REQ_RANGE_CITY:
case REQ_RANGE_TRADE_ROUTE:
case REQ_RANGE_CONTINENT:
case REQ_RANGE_PLAYER:
case REQ_RANGE_TEAM:
case REQ_RANGE_ALLIANCE:
case REQ_RANGE_WORLD:
case REQ_RANGE_LOCAL:
case REQ_RANGE_COUNT:
/* Not supported. */
break;
}
break;
case TREL_COUNT:
/* Invalid. */
common/requirements.c
* already covered by are_requirements_opposites() above. */
switch (req1->source.value.tilerel) {
case TREL_SAME_REGION:
case TREL_REGION_SURROUNDED:
/* Negated req at larger range contradicts present req at
* smaller range. */
if (req1->range > req2->range) {
......
return FALSE;
}
}
if (req1->source.value.tilerel == TREL_REGION_SURROUNDED
|| req2->source.value.tilerel == TREL_REGION_SURROUNDED) {
const struct requirement *surr, *other;
if (req1->source.value.tilerel == TREL_REGION_SURROUNDED) {
surr = req1;
other = req2;
} else {
surr = req2;
other = req1;
}
if (surr->present && surr->range == REQ_RANGE_TILE) {
/* Target tile must be part of a surrounded region
* ~> not the same region
* ~> not touched by a third region */
switch (other->source.value.tilerel) {
case TREL_SAME_REGION:
return (other->present && other->range == REQ_RANGE_TILE);
case TREL_ONLY_OTHER_REGION:
return (!other->present);
default:
break;
}
}
}
/* No further contradictions we can detect */
return FALSE;
default:
......
}
}
/**********************************************************************//**
Determine whether the given continent or ocean might be surrounded by a
specific desired surrounder.
**************************************************************************/
static inline enum fc_tristate
does_region_surrounder_match(Continent_id cont, Continent_id surrounder)
{
Continent_id actual_surrounder;
bool whole_known;
if (cont > 0) {
actual_surrounder = get_island_surrounder(cont);
whole_known = is_whole_continent_known(cont);
if (actual_surrounder > 0) {
return TRI_NO;
}
} else if (cont < 0) {
actual_surrounder = get_lake_surrounder(cont);
whole_known = is_whole_ocean_known(-cont);
if (actual_surrounder < 0) {
return TRI_NO;
}
} else {
return TRI_MAYBE;
}
if (actual_surrounder == 0 || surrounder == 0) {
return TRI_MAYBE;
} else if (actual_surrounder != surrounder) {
return TRI_NO;
} else if (!whole_known) {
return TRI_MAYBE;
} else {
return TRI_YES;
}
}
/**********************************************************************//**
Determine whether a tile relationship requirement is satisfied in a given
context, ignoring parts of the requirement that can be handled uniformly
......
}
}
break;
case TREL_REGION_SURROUNDED:
fc_assert_ret_val_msg((req->range == REQ_RANGE_TILE
|| req->range == REQ_RANGE_CADJACENT
|| req->range == REQ_RANGE_ADJACENT),
TRI_MAYBE,
"Invalid range %d for tile relation \"%s\" req",
req->range,
tilerel_type_name(TREL_REGION_SURROUNDED));
{
bool seen_maybe = FALSE;
Continent_id wanted = tile_continent(other_context->tile);
switch (does_region_surrounder_match(tile_continent(context->tile),
wanted)) {
case TRI_YES:
return TRI_YES;
case TRI_MAYBE:
seen_maybe = TRUE;
break;
default:
break;
}
range_adjc_iterate(nmap, context->tile, req->range, adj_tile) {
switch (does_region_surrounder_match(tile_continent(adj_tile),
wanted)) {
case TRI_YES:
return TRI_YES;
case TRI_MAYBE:
seen_maybe = TRUE;
break;
default:
break;
}
} range_adjc_iterate_end;
if (seen_maybe) {
return TRI_MAYBE;
} else {
return TRI_NO;
}
}
break;
default:
break;
}
......
if (cont > 0) {
min_tiles = nmap->continent_sizes[cont];
if (is_server() || (nmap->client.continent_unknown_adj_counts[cont]
== 0)) {
if (is_whole_continent_known(cont)) {
max_tiles = min_tiles;
}
} else if (cont < 0) {
min_tiles = nmap->ocean_sizes[-cont];
if (is_server() || (nmap->client.ocean_unknown_adj_counts[-cont]
== 0)) {
if (is_whole_ocean_known(-cont)) {
max_tiles = min_tiles;
}
}
......
case TREL_ONLY_OTHER_REGION:
fc_strlcat(buf, _("Only other continent/ocean"), bufsz);
break;
case TREL_REGION_SURROUNDED:
fc_strlcat(buf, _("Lake/island surrounded"), bufsz);
break;
case TREL_COUNT:
fc_assert(psource->value.tilerel != TREL_COUNT);
fc_strlcat(buf, "error", bufsz);
doc/README.effects
TileRel is about the relation to a specific other tile; currently only available
for action enablers (see also README.actions) and a select few effects. It is
one of "Same Region" (on or adjacent to the same continent/ocean as the other
tile, depending on range) or "Only Other Region" (only adjacent to tiles of its
tile, depending on range), "Only Other Region" (only adjacent to tiles of its
own or the other tile's continent/ocean, not a third one; not available at Tile
range).
range), or "Region Surrounded" (on or adjacent to a lake/island touching only
the other tile's continent/ocean).
MaxDistanceSq is about the (squared) distance between two tiles; currently
only available for action enablers (see also README.actions) and a select
few effects.
gen_headers/enums/fc_types_enums.def
style identifiers sorted
values
ONLY_OTHER_REGION "Only Other Region"
REGION_SURROUNDED "Region Surrounded"
SAME_REGION "Same Region"
end
server/generator/mapgen_utils.c
}
/**********************************************************************//**
Calculate wld.map.lake_surrounders[] array
Calculate wld.map.*_surrounders[] arrays
**************************************************************************/
static void recalculate_lake_surrounders(void)
static void recalculate_surrounders(void)
{
size_t size;
size = (wld.map.num_oceans + 1) * sizeof(*wld.map.lake_surrounders);
wld.map.lake_surrounders = fc_realloc(wld.map.lake_surrounders, size);
memset(wld.map.lake_surrounders, 0, size);
size = (wld.map.num_continents + 1) * sizeof(*wld.map.server.island_surrounders);
wld.map.server.island_surrounders = fc_realloc(wld.map.server.island_surrounders, size);
memset(wld.map.server.island_surrounders, 0, size);
size = (wld.map.num_oceans + 1) * sizeof(*wld.map.server.lake_surrounders);
wld.map.server.lake_surrounders = fc_realloc(wld.map.server.lake_surrounders, size);
memset(wld.map.server.lake_surrounders, 0, size);
whole_map_iterate(&(wld.map), ptile) {
const struct terrain *pterrain = tile_terrain(ptile);
......
continue;
}
if (!is_ocean(pterrain)) {
if (is_ocean(pterrain)) {
fc_assert_action(cont < 0, continue);
adjc_iterate(&(wld.map), ptile, adj_tile) {
Continent_id adj_cont = tile_continent(adj_tile);
if (!is_ocean_tile(adj_tile)) {
fc_assert_action(adj_cont > 0, continue);
if (wld.map.server.island_surrounders[adj_cont] == 0) {
wld.map.server.island_surrounders[adj_cont] = -cont;
} else if (wld.map.server.island_surrounders[adj_cont] != -cont) {
wld.map.server.island_surrounders[adj_cont] = -1;
}
}
} adjc_iterate_end;
} else {
fc_assert_action(cont > 0, continue);
adjc_iterate(&(wld.map), ptile, adj_tile) {
Continent_id adj_cont = tile_continent(adj_tile);
if (is_ocean_tile(adj_tile)) {
fc_assert_action(adj_cont < 0, continue);
if (wld.map.lake_surrounders[-adj_cont] == 0) {
wld.map.lake_surrounders[-adj_cont] = cont;
} else if (wld.map.lake_surrounders[-adj_cont] != cont) {
wld.map.lake_surrounders[-adj_cont] = -1;
if (wld.map.server.lake_surrounders[-adj_cont] == 0) {
wld.map.server.lake_surrounders[-adj_cont] = cont;
} else if (wld.map.server.lake_surrounders[-adj_cont] != cont) {
wld.map.server.lake_surrounders[-adj_cont] = -1;
}
}
} adjc_iterate_end;
......
/**********************************************************************//**
Regenerate all oceanic tiles for small water bodies as lakes.
Assumes assign_continent_numbers() and recalculate_lake_surrounders()
have already been done!
Assumes assign_continent_numbers() has already been called!
FIXME: insufficiently generalized, use terrain property.
**************************************************************************/
void regenerate_lakes(void)
......
if (terrain_type_terrain_class(pterrain) != TC_OCEAN) {
continue;
}
if (0 < wld.map.lake_surrounders[-here]) {
if (0 < wld.map.server.lake_surrounders[-here]) {
if (terrain_control.lake_max_size >= wld.map.ocean_sizes[-here]) {
int frozen = terrain_has_flag(pterrain, TER_FROZEN);
tile_change_terrain(ptile, lake_for_ocean[frozen][-here-1]);
......
/**********************************************************************//**
Assigns continent and ocean numbers to all tiles, and set
map.num_continents and map.num_oceans. Recalculates continent and
ocean sizes, and lake_surrounders[] arrays.
ocean sizes and surrounders.
Continents have numbers 1 to map.num_continents _inclusive_.
Oceans have (negative) numbers -1 to -map.num_oceans _inclusive_.
......
}
} whole_map_iterate_end;
recalculate_lake_surrounders();
recalculate_surrounders();
log_verbose("Map has %d continents and %d oceans",
wld.map.num_continents, wld.map.num_oceans);
server/maphand.c
bool other_continent;
if (get_ocean_size(-cont) <= MAXIMUM_CLAIMED_OCEAN_SIZE
&& get_lake_surrounders(cont) == source_cont) {
&& get_lake_surrounder(cont) == source_cont) {
return TRUE;
}
(3-3/3)