/**************************************************************************** Set the given terrain at the specified tile. ****************************************************************************/ void tile_set_terrain(struct tile *ptile, struct terrain *pterrain) { /* The terrain change is valid if one of the following is TRUE: * - pterrain is NULL (= unknown terrain) * - ptile is a virtual tile * - pterrain does not has the flag TER_NO_CITIES * - there is no city on ptile. * This should be read as: The terrain change is INVALID if a terrain with * the flag TER_NO_CITIES is given for a real tile with a city (i.e. all * check evaluate to TRUE). */ fc_assert_msg(NULL == pterrain || tile_virtual_check(ptile) || !terrain_has_flag(pterrain, TER_NO_CITIES) || NULL == tile_city(ptile), "At (%d, %d), the terrain \"%s\" (nb %d) doesn't " "support cities, whereas \"%s\" (nb %d) is built there.", TILE_XY(ptile), terrain_rule_name(pterrain), terrain_number(pterrain), city_name(tile_city(ptile)), tile_city(ptile)->id); ptile->terrain = pterrain; if (NULL != pterrain && NULL != ptile->resource && terrain_has_resource(pterrain, ptile->resource)) { ptile->resource_valid = TRUE; } else { ptile->resource_valid = FALSE; } }
/************************************************************************** Log city messages, they will appear like this 2: Polish Romenna(5,35) [s1 d106 u11 g1] must have Archers ... **************************************************************************/ void real_city_log(const char *file, const char *function, int line, enum log_level level, bool notify, const struct city *pcity, const char *msg, ...) { char buffer[500]; char buffer2[500]; va_list ap; char aibuf[500] = "\0"; CALL_PLR_AI_FUNC(log_fragment_city, city_owner(pcity), aibuf, sizeof(aibuf), pcity); fc_snprintf(buffer, sizeof(buffer), "%s %s(%d,%d) [s%d] {%s} ", nation_rule_name(nation_of_city(pcity)), city_name(pcity), TILE_XY(pcity->tile), city_size_get(pcity), aibuf); va_start(ap, msg); fc_vsnprintf(buffer2, sizeof(buffer2), msg, ap); va_end(ap); cat_snprintf(buffer, sizeof(buffer), "%s", buffer2); if (notify) { notify_conn(NULL, NULL, E_AI_DEBUG, ftc_log, "%s", buffer); } do_log(file, function, line, FALSE, level, "%s", buffer); }
/************************************************************************** Log city messages, they will appear like this 2: Polish Romenna(5,35) [s1 d106 u11 g1] must have Archers ... **************************************************************************/ void CITY_LOG(int level, const struct city *pcity, const char *msg, ...) { char buffer[500]; char buffer2[500]; va_list ap; int minlevel = MIN(LOGLEVEL_CITY, level); if (pcity->debug) { minlevel = LOG_TEST; } else if (minlevel > fc_log_level) { return; } my_snprintf(buffer, sizeof(buffer), "%s %s(%d,%d) [s%d d%d u%d g%d] ", nation_rule_name(nation_of_city(pcity)), city_name(pcity), TILE_XY(pcity->tile), pcity->size, pcity->ai->danger, pcity->ai->urgency, pcity->ai->grave_danger); va_start(ap, msg); my_vsnprintf(buffer2, sizeof(buffer2), msg, ap); va_end(ap); cat_snprintf(buffer, sizeof(buffer), "%s", buffer2); if (pcity->debug) { notify_conn(NULL, NULL, E_AI_DEBUG, ftc_log, "%s", buffer); } freelog(minlevel, "%s", buffer); }
void fc_mysql_log_nuke(const struct tile* ptile) { fc_mysql_query( "INSERT INTO `" FC__MYSQL_NUKELOG_TABLE "` " "(" "created," "tile_x," "tile_y) " "VALUES (" "NOW()," "%d," "%d)", TILE_XY(ptile) ); }
/************************************************************************** ... **************************************************************************/ static void sha_tile_update(struct tile *ptile) { freelog(LOG_DEBUG, "sha got tile: %d ~= (%d, %d)", tile_index(ptile), TILE_XY(ptile)); #if 0 previous_tiles[tile_index(ptile)] = *ptile; #endif }
void fc_mysql_log_chat(const char *message, const struct tile* ptile, enum event_type event, int conn_id) { fc_mysql_query( "INSERT INTO `" FC__MYSQL_CHATLOG_TABLE "` " "(" "created," "tile_x," "tile_y," "message," "event) " "VALUES (" "NOW()," "%d," "%d," "%s," "%s)", TILE_XY(ptile), message, get_event_message_text(event) ); }
/********************************************************************** Find something to bomb Air-units specific victim search Returns the want for the best target. The targets are stored in the path and pptile arguments if not NULL. TODO: take counterattack dangers into account TODO: make separate handicaps for air units seeing targets IMO should be more restrictive than general H_MAP, H_FOG *********************************************************************/ static int find_something_to_bomb(struct ai_type *ait, struct unit *punit, struct pf_path **path, struct tile **pptile) { struct player *pplayer = unit_owner(punit); struct pf_parameter parameter; struct pf_map *pfm; struct tile *best_tile = NULL; int best = 0; pft_fill_unit_parameter(¶meter, punit); pfm = pf_map_new(¶meter); /* Let's find something to bomb */ pf_map_move_costs_iterate(pfm, ptile, move_cost, FALSE) { if (move_cost >= punit->moves_left) { /* Too far! */ break; } if (ai_handicap(pplayer, H_MAP) && !map_is_known(ptile, pplayer)) { /* The target tile is unknown */ continue; } if (ai_handicap(pplayer, H_FOG) && !map_is_known_and_seen(ptile, pplayer, V_MAIN)) { /* The tile is fogged */ continue; } if (is_enemy_unit_tile(ptile, pplayer) && dai_should_we_air_attack_tile(ait, punit, ptile) && can_unit_attack_tile(punit, ptile)) { int new_best = dai_evaluate_tile_for_air_attack(punit, ptile); if (new_best > best) { best_tile = ptile; best = new_best; log_debug("%s wants to attack tile (%d, %d)", unit_rule_name(punit), TILE_XY(ptile)); } } } pf_map_positions_iterate_end; /* Return the best values. */ if (pptile) { *pptile = best_tile; } if (path) { *path = best_tile ? pf_map_path(pfm, best_tile) : NULL; } pf_map_destroy(pfm); return best; }
/***************************************************************************** Find best tile the paratrooper should jump to. *****************************************************************************/ static struct tile *find_best_tile_to_paradrop_to(struct ai_type *ait, struct unit *punit) { int best = 0; int val; struct tile* best_tile = NULL; int range = unit_type(punit)->paratroopers_range; struct city* acity; struct player* pplayer = unit_owner(punit); /* First, we search for undefended cities in danger */ square_iterate(unit_tile(punit), range, ptile) { if (!map_is_known(ptile, pplayer)) { continue; } acity = tile_city(ptile); if (acity && city_owner(acity) == unit_owner(punit) && unit_list_size(ptile->units) == 0) { val = city_size_get(acity) * def_ai_city_data(acity, ait)->urgency; if (val > best) { best = val; best_tile = ptile; } } } square_iterate_end; if (best_tile != NULL) { acity = tile_city(best_tile); UNIT_LOG(LOGLEVEL_PARATROOPER, punit, "Choose to jump in order to protect allied city %s (%d %d). " "Benefit: %d", city_name(acity), TILE_XY(best_tile), best); return best_tile; } /* Second, we search for undefended enemy cities */ square_iterate(unit_tile(punit), range, ptile) { acity = tile_city(ptile); if (acity && pplayers_at_war(unit_owner(punit), city_owner(acity)) && (unit_list_size(ptile->units) == 0)) { if (!map_is_known_and_seen(ptile, pplayer, V_MAIN) && ai_handicap(pplayer, H_FOG)) { continue; } /* Prefer big cities on other continents */ val = city_size_get(acity) + (tile_continent(unit_tile(punit)) != tile_continent(ptile)); if (val > best) { best = val; best_tile = ptile; } } } square_iterate_end;
void fc_mysql_log_combat(const struct unit* punit0, const struct unit* punit1) { fc_mysql_query( "INSERT INTO `" FC__MYSQL_COMBATLOG_TABLE "` " "(" "created," "tile_x," "tile_y," "attacker_unit_id," "defender_unit_id) " "VALUES (" "NOW()," "%d," "%d," "%d," "%d)", TILE_XY(punit1->tile), punit0->id, punit1->id ); fc_mysql_log_unit(punit0); fc_mysql_log_unit(punit1); }
/************************************************************************** Log unit messages, they will appear like this 2: Polish Archers[139] (5,35)->(0,0){0,0} stays to defend city where [] is unit id, ()->() are coordinates present and goto, and {,} contains bodyguard and ferryboat ids. **************************************************************************/ void UNIT_LOG(int level, const struct unit *punit, const char *msg, ...) { char buffer[500]; char buffer2[500]; va_list ap; int minlevel = MIN(LOGLEVEL_UNIT, level); int gx, gy; bool messwin = FALSE; /* output to message window */ if (punit->debug) { minlevel = LOG_TEST; } else { /* Are we a virtual unit evaluated in a debug city?. */ if (punit->id == 0) { struct city *pcity = tile_city(punit->tile); if (pcity && pcity->debug) { minlevel = LOG_TEST; messwin = TRUE; } } if (minlevel > fc_log_level) { return; } } if (punit->goto_tile) { gx = punit->goto_tile->x; gy = punit->goto_tile->y; } else { gx = gy = -1; } my_snprintf(buffer, sizeof(buffer), "%s %s[%d] %s (%d,%d)->(%d,%d){%d,%d} ", nation_rule_name(nation_of_unit(punit)), unit_rule_name(punit), punit->id, get_activity_text(punit->activity), TILE_XY(punit->tile), gx, gy, punit->ai.bodyguard, punit->ai.ferryboat); va_start(ap, msg); my_vsnprintf(buffer2, sizeof(buffer2), msg, ap); va_end(ap); cat_snprintf(buffer, sizeof(buffer), "%s", buffer2); if (punit->debug || messwin) { notify_conn(NULL, NULL, E_AI_DEBUG, ftc_log, "%s", buffer); } freelog(minlevel, "%s", buffer); }
void fc_mysql_log_unit_removed(const struct unit* punit) { fc_mysql_query( "INSERT INTO `" FC__MYSQL_UNITLOG_TABLE "` " "(" "created," "unit_name," "tile_x," "tile_y," "owner_name," "unit_hp," "unit_veteran," "unit_id," "removed) " "VALUES (" "NOW()," "%s," "%d," "%d," "%s," "%d," "%d," "%d," "1)", unit_rule_name(punit), TILE_XY(punit->tile), punit->owner->name, punit->hp, punit->veteran, punit->id ); }
void fc_mysql_log_city_removed(const struct city* pcity) { fc_mysql_query( "INSERT INTO `" FC__MYSQL_CITYLOG_TABLE "` " "(" "created," "city_id," "city_name," "tile_x," "tile_y," "owner_name," "city_size," "city_food_stock," "city_shield_stock," "removed) " "VALUES (" "NOW()," "%d," "%s," "%d," "%d," "%s," "%d," "%d," "%d," "1)", pcity->id, pcity->name, TILE_XY(pcity->tile), pcity->owner->name, pcity->size, pcity->food_stock, pcity->shield_stock ); }
/************************************************************************** Log message for bodyguards. They will appear like this 2: Polish Mech. Inf.[485] bodyguard (38,22){Riflemen:574@37,23} was ... note that these messages are likely to wrap if long. **************************************************************************/ void BODYGUARD_LOG(int level, const struct unit *punit, const char *msg) { char buffer[500]; int minlevel = MIN(LOGLEVEL_BODYGUARD, level); const struct unit *pcharge; const struct city *pcity; int id = -1; int charge_x = -1; int charge_y = -1; const char *type = "guard"; const char *s = "none"; if (punit->debug) { minlevel = LOG_TEST; } else if (minlevel > fc_log_level) { return; } pcity = game_find_city_by_number(punit->ai.charge); pcharge = game_find_unit_by_number(punit->ai.charge); if (pcharge) { charge_x = pcharge->tile->x; charge_y = pcharge->tile->y; id = pcharge->id; type = "bodyguard"; s = unit_rule_name(pcharge); } else if (pcity) { charge_x = pcity->tile->x; charge_y = pcity->tile->y; id = pcity->id; type = "cityguard"; s = city_name(pcity); } /* else perhaps the charge died */ my_snprintf(buffer, sizeof(buffer), "%s %s[%d] %s (%d,%d){%s:%d@%d,%d} ", nation_rule_name(nation_of_unit(punit)), unit_rule_name(punit), punit->id, type, TILE_XY(punit->tile), s, id, charge_x, charge_y); cat_snprintf(buffer, sizeof(buffer), "%s", msg); if (punit->debug) { notify_conn(NULL, NULL, E_AI_DEBUG, ftc_log, "%s", buffer); } freelog(minlevel, "%s", buffer); }
/************************************************************************** Log unit messages, they will appear like this 2: Polish Archers[139] (5,35)->(0,0){0,0} stays to defend city where [] is unit id, ()->() are coordinates present and goto, and {,} contains bodyguard and ferryboat ids. **************************************************************************/ void real_unit_log(const char *file, const char *function, int line, enum log_level level, bool notify, const struct unit *punit, const char *msg, ...) { char buffer[500]; char buffer2[500]; va_list ap; int gx, gy; char aibuf[500] = "\0"; CALL_PLR_AI_FUNC(log_fragment_unit, unit_owner(punit), aibuf, sizeof(aibuf), punit); if (punit->goto_tile) { index_to_map_pos(&gx, &gy, tile_index(punit->goto_tile)); } else { gx = gy = -1; } fc_snprintf(buffer, sizeof(buffer), "%s %s[%d] %s (%d,%d)->(%d,%d){%s} ", nation_rule_name(nation_of_unit(punit)), unit_rule_name(punit), punit->id, get_activity_text(punit->activity), TILE_XY(unit_tile(punit)), gx, gy, aibuf); va_start(ap, msg); fc_vsnprintf(buffer2, sizeof(buffer2), msg, ap); va_end(ap); cat_snprintf(buffer, sizeof(buffer), "%s", buffer2); if (notify) { notify_conn(NULL, NULL, E_AI_DEBUG, ftc_log, "%s", buffer); } do_log(file, function, line, FALSE, level, "%s", buffer); }
/************************************************************************ Trying to manage bombers and stuff. If we are in the open { if moving intelligently on a valid GOTO, { carry on doing it. } else { go refuel } } else { try to attack something } TODO: distant target selection, support for fuel > 2 ***********************************************************************/ void dai_manage_airunit(struct ai_type *ait, struct player *pplayer, struct unit *punit) { struct tile *dst_tile = unit_tile(punit); /* Loop prevention */ int moves = punit->moves_left; int id = punit->id; struct pf_parameter parameter; struct pf_map *pfm; struct pf_path *path; CHECK_UNIT(punit); if (!is_unit_being_refueled(punit)) { /* We are out in the open, what shall we do? */ if (punit->activity == ACTIVITY_GOTO /* We are on a GOTO. Check if it will get us anywhere */ && NULL != punit->goto_tile && !same_pos(unit_tile(punit), punit->goto_tile) && is_airunit_refuel_point(punit->goto_tile, pplayer, unit_type(punit), FALSE)) { pfm = pf_map_new(¶meter); path = pf_map_path(pfm, punit->goto_tile); if (path) { bool alive = adv_follow_path(punit, path, punit->goto_tile); pf_path_destroy(path); pf_map_destroy(pfm); if (alive && punit->moves_left > 0) { /* Maybe do something else. */ dai_manage_airunit(ait, pplayer, punit); } return; } pf_map_destroy(pfm); } else if ((dst_tile = find_nearest_airbase(punit, &path))) { /* Go refuelling */ if (!adv_follow_path(punit, path, dst_tile)) { pf_path_destroy(path); return; /* The unit died. */ } pf_path_destroy(path); } else { if (punit->fuel == 1) { UNIT_LOG(LOG_DEBUG, punit, "Oops, fallin outta the sky"); } def_ai_unit_data(punit, ait)->done = TRUE; /* Won't help trying again */ return; } } else if (punit->fuel == unit_type(punit)->fuel) { /* We only leave a refuel point when we are on full fuel */ if (find_something_to_bomb(ait, punit, &path, &dst_tile) > 0) { /* Found target, coordinates are in punit's goto_dest. * TODO: separate attacking into a function, check for the best * tile to attack from */ fc_assert_ret(path != NULL && dst_tile != NULL); if (!adv_follow_path(punit, path, dst_tile)) { pf_path_destroy(path); return; /* The unit died. */ } pf_path_destroy(path); /* goto would be aborted: "Aborting GOTO for AI attack procedures" * now actually need to attack */ /* We could use ai_military_findvictim here, but I don't trust it... */ unit_activity_handling(punit, ACTIVITY_IDLE); if (is_tiles_adjacent(unit_tile(punit), dst_tile)) { (void) unit_move_handling(punit, dst_tile, TRUE, FALSE); } } else if ((dst_tile = dai_find_strategic_airbase(ait, punit, &path))) { log_debug("%s will fly to (%i, %i) (%s) to fight there", unit_rule_name(punit), TILE_XY(dst_tile), tile_city(dst_tile) ? city_name(tile_city(dst_tile)) : ""); def_ai_unit_data(punit, ait)->done = TRUE; /* Wait for next turn */ if (!adv_follow_path(punit, path, dst_tile)) { pf_path_destroy(path); return; /* The unit died. */ } pf_path_destroy(path); } else { log_debug("%s cannot find anything to kill and is staying put", unit_rule_name(punit)); def_ai_unit_data(punit, ait)->done = TRUE; unit_activity_handling(punit, ACTIVITY_IDLE); } } if ((punit = game_unit_by_number(id)) != NULL && punit->moves_left > 0 && punit->moves_left != moves) { /* We have moved this turn, might have ended up stuck out in the fields * so, as a safety measure, let's manage again */ dai_manage_airunit(ait, pplayer, punit); } }
/********************************************************************** Returns an estimate for the profit gained through attack. Assumes that the victim is within one day's flight **********************************************************************/ static int dai_evaluate_tile_for_air_attack(struct unit *punit, struct tile *dst_tile) { struct unit *pdefender; /* unit costs in shields */ int balanced_cost, unit_cost, victim_cost = 0; /* unit stats */ int unit_attack, victim_defence; /* final answer */ int profit; /* time spent in the air */ int sortie_time; #define PROB_MULTIPLIER 100 /* should unify with those in combat.c */ if (!can_unit_attack_tile(punit, dst_tile) || !(pdefender = get_defender(punit, dst_tile))) { return 0; } /* Ok, we can attack, but is it worth it? */ /* Cost of our unit */ unit_cost = unit_build_shield_cost(punit); /* This is to say "wait, ill unit will get better!" */ unit_cost = unit_cost * unit_type(punit)->hp / punit->hp; /* Determine cost of enemy units */ victim_cost = stack_cost(punit, pdefender); if (0 == victim_cost) { return 0; } /* Missile would die 100% so we adjust the victim_cost -- GB */ if (uclass_has_flag(unit_class(punit), UCF_MISSILE)) { victim_cost -= unit_build_shield_cost(punit); } unit_attack = (int) (PROB_MULTIPLIER * unit_win_chance(punit, pdefender)); victim_defence = PROB_MULTIPLIER - unit_attack; balanced_cost = build_cost_balanced(unit_type(punit)); sortie_time = (unit_has_type_flag(punit, UTYF_ONEATTACK) ? 1 : 0); profit = kill_desire(victim_cost, unit_attack, unit_cost, victim_defence, 1) - SHIELD_WEIGHTING + 2 * TRADE_WEIGHTING; if (profit > 0) { profit = military_amortize(unit_owner(punit), game_city_by_number(punit->homecity), profit, sortie_time, balanced_cost); log_debug("%s at (%d, %d) is a worthy target with profit %d", unit_rule_name(pdefender), TILE_XY(dst_tile), profit); } else { log_debug("%s(%d, %d): %s at (%d, %d) is unworthy with profit %d", unit_rule_name(punit), TILE_XY(unit_tile(punit)), unit_rule_name(pdefender), TILE_XY(dst_tile), profit); profit = 0; } return profit; }