/********************************************************************** Freeciv - Copyright (C) 2002 - The Freeciv Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ***********************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "city.h" #include "game.h" #include "government.h" #include "log.h" #include "map.h" #include "mem.h" #include "packets.h" #include "path_finding.h" #include "pf_tools.h" #include "player.h" #include "support.h" #include "timing.h" #include "citytools.h" #include "gotohand.h" #include "settlers.h" #include "unittools.h" #include "aicity.h" #include "aidata.h" #include "ailog.h" #include "aitools.h" #include "aiunit.h" #include "citymap.h" #include "aisettler.h" /* COMMENTS */ /* This code tries hard to do the right thing, including looking into the future (wrt to government), and also doing this in a modpack friendly manner. However, there are some pieces missing. * A tighter integration into the city management code would give more optimal city placements, since existing cities could move their workers around to give a new city better placement. Occasionally you will see cities being placed sub-optimally because the best city center tile is taken when another tile could have been worked instead by the city that took it. */ #define LOG_SETTLER LOG_NORMAL #ifdef NEVER /************************************************************************** Fill citycache struct with useful info about the city spot. Contains a lot of kludges that are hardcoded amortize calls. ***************************************************************************/ static void citycache_print(struct citycache *cache, int level) { int i; freelog(level, "TILE(%d, %d) Food | Shield | Trade (%d center, %d other, " "%d remaining", cache->x, cache->y, cache->city_center, cache->best_other, cache->remaining); for (i = 0; i < CITY_MAP_SIZE; i++) { freelog(level, "[%d|%d|%d][%d|%d|%d][%d|%d|%d][%d|%d|%d][%d|%d|%d]", cache->tile[0][i].food, cache->tile[0][i].shield, cache->tile[0][i].trade, cache->tile[1][i].food, cache->tile[1][i].shield, cache->tile[1][i].trade, cache->tile[2][i].food, cache->tile[2][i].shield, cache->tile[2][i].trade, cache->tile[3][i].food, cache->tile[3][i].shield, cache->tile[3][i].trade, cache->tile[4][i].food, cache->tile[4][i].shield, cache->tile[4][i].trade); } } #endif /************************************************************************** Fill citycache struct with useful info about the city spot. cache must contain valid x, y coordinates. We assume whatever best government we are aiming for. We always return valid other_x and other_y if total > 0. **************************************************************************/ static void citycache_fill(struct player *pplayer, struct citycache *cache) { struct city *pcity = map_get_city(cache->x, cache->y); struct tile *ptile = NULL; int sum = 0; bool virtual_city = FALSE; struct ai_data *ai = ai_data_get(pplayer); int curr_govt = pplayer->government; pplayer->government = ai->goal.govt.idx; cache->best_other = -1; cache->other_x = -1; cache->other_y = -1; cache->remaining = 0; if (!pcity) { pcity = create_city_virtual(pplayer, cache->x, cache->y, "Virtuaville"); virtual_city = TRUE; } city_map_checked_iterate(cache->x, cache->y, i, j, map_x, map_y) { int reserved = citymap_read(map_x, map_y); ptile = map_get_tile(map_x, map_y); if (reserved < 0) { /* Tile is reserved */ sum = 0; } else { /* Food */ cache->tile[i][j].food = base_city_get_food_tile(i, j, pcity, FALSE); /* Shields */ cache->tile[i][j].shield = base_city_get_shields_tile(i, j, pcity, FALSE); /* Trade */ cache->tile[i][j].trade = base_city_get_trade_tile(i, j, pcity, FALSE); sum = (cache->tile[i][j].food * ai->food_priority + cache->tile[i][j].trade * ai->science_priority + cache->tile[i][j].shield * ai->shield_priority); if (cache->tile[i][j].food >= 2) { sum *= 2; /* we need this to grow */ } /* If this tile is in proximity of a city, we don't want it this much. * The more cities it overlaps with, the less we want it. */ sum /= (reserved + 1); } /* Calculate city center and best other than city center */ if (is_city_center(i, j)) { cache->city_center = sum; } else if (sum > cache->best_other) { cache->best_other = sum; cache->other_x = map_x; cache->other_y = map_y; } else { /* Save total remaining calculation */ cache->remaining += sum; } } city_map_checked_iterate_end; pplayer->government = curr_govt; if (virtual_city) { remove_city_virtual(pcity); } /* Baseline is a size one city */ cache->total = cache->city_center + cache->best_other; if (cache->best_other == 0) { /* Everything taken! */ cache->total = 0; return; } assert(cache->best_other != -1 && cache->other_x != -1 && cache->other_y != -1); assert(cache->city_center >= 0); assert(cache->remaining >= 0); assert(cache->total >= 0); } /************************************************************************** Calculates the desire for founding a new city at (x, y). The citymap ensures that we do not build cities too close to each other. If we return cache->total == 0, then no place was found. **************************************************************************/ static void city_desirability(struct player *pplayer, struct unit *punit, int x, int y, struct citycache *cache) { struct tile *ptile = map_get_tile(x, y); bool ocean_adjacent = is_terrain_near_tile(x, y, T_OCEAN); struct city *pcity = map_get_city(x, y); int defense_bonus; assert(punit); assert(pplayer); assert(cache); cache->x = x; cache->y = y; cache->total = 0; if (ptile->terrain == T_OCEAN) { return; } if (enemies_at(punit, x, y)) { return; } if (pcity && pcity->size >= game.add_to_size_limit) { return; } if (!pcity && citymap_is_reserved(x, y)) { return; /* reserved, go away */ } citycache_fill(pplayer, cache); /* Burn CPU, burn! */ if (cache->total == 0) { /* Failed to find a good spot */ return; } /* If (x, y) is an existing city, consider immigration */ if (pcity) { /* cache->total = cache->best_other; <<-- buggy */ cache->total = 0; return; } /*** Alright: Now consider building a new city ***/ /* Avoid starvation: We must have enough food at city center */ if (cache->tile[2][2].food < 2) { cache->total = 0; return; } /* Defense modification (as tie breaker mostly) */ defense_bonus = get_tile_type(map_get_terrain(x, y))->defense_bonus; if (map_has_special(x, y, S_RIVER)) { defense_bonus += (defense_bonus * terrain_control.river_defense_bonus) / 100; } cache->total += cache->total * defense_bonus / 4000; /* Adjust for ocean adjacency, which is nice, by 20% */ if (ocean_adjacent) { cache->total += (cache->total * 20) / 100; } /* Add remaining points, which is our potential */ cache->total += (cache->remaining / 8); assert(cache->total >= 0); return; } /************************************************************************** Find nearest and best city placement. Transparently checks if we should add ourselves to an existing city (TODO). **************************************************************************/ void find_best_city_placement(struct unit *punit, struct citycache *best) { struct player *pplayer = unit_owner(punit); struct ai_data *ai = ai_data_get(pplayer); struct citycache cache; assert(pplayer && pplayer->ai.control); best->x = -1; best->y = -1; best->total = 0; /* Uh, we're on board a ship... */ if (map_get_tile(punit->x, punit->y)->terrain == T_OCEAN) { UNIT_LOG(LOG_ERROR, punit, "We're trying to find a city while on board " "a ship..."); return; } simple_unit_path_iterator(punit, pos) { int turns; /* Calculate worth */ city_desirability(pplayer, punit, pos.x, pos.y, &cache); /* This ugly algorithm punishes long treks exponentially. */ turns = pos.total_MC / unit_type(punit)->move_rate; if (turns > 1) { int i, d = ai->perfection; for (i = 0; i < turns; i++) { cache.total -= d; d += ai->perfection / 2; } } /* Find best spot */ if (cache.total > best->total) { *best = cache; } } simple_unit_path_iterator_end; }