Complete.Org:
Mailing Lists:
Archives:
freeciv-ai:
March 2003: [freeciv-ai] AI Diplomacy v8 (PR#2413) |
[freeciv-ai] AI Diplomacy v8 (PR#2413)[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
This is a new version of the AI Diplomacy patch with substantial rewrites of some portions of the patch. Thanks to Jordi, Richard and Chris for playtesting the previous version (I hope I didn't forget anyone now). CHANGES: - treaties now based on trust, not gold alone - allow space victory for team members - make hatred count more - disallow negative hatred - ensure ally patience is correctly zeroed when war declared - if you get a "No" from a human player, don't ask again - fixed insane gold equivalence of techs when there is a huge research disparity - fixed many other bugs - you can give the AI gifts, it will like you more (more trust, but hatred remains unchanged) - you can no longer agree with AI to lower your treaty level - lots of other stuff I am sure Please playtest. I will now start working on the diplomacy rules that I discussed earlier. It will make AI Diplomacy so much more enjoyable. Also included is a savegame for testing. - Per
warofallies.sav.gz
diplomacy8.diff.gz /********************************************************************** Freeciv - Copyright (C) 2003 - 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. ***********************************************************************/ #ifndef FC__ADVDIPLOMACY_H #define FC__ADVDIPLOMACY_H struct player; struct ai_choice; struct Treaty; struct Clause; struct ai_data; void ai_diplomacy_calculate(struct player *pplayer, struct ai_data *ai); void ai_diplomacy_actions(struct player *pplayer); void ai_treaty_evaluate(struct player *pplayer, struct player *aplayer, struct Treaty *ptreaty); void ai_treaty_accepted(struct player *pplayer, struct player *aplayer, struct Treaty *ptreaty); #endif /********************************************************************** Freeciv - Copyright (C) 2003 - 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 <config.h> #endif #include "city.h" #include "diptreaty.h" #include "events.h" #include "game.h" #include "packets.h" #include "player.h" #include "rand.h" #include "log.h" #include "mem.h" #include "nation.h" #include "shared.h" #include "support.h" #include "tech.h" #include "citytools.h" #include "diplhand.h" #include "plrhand.h" #include "maphand.h" #include "settlers.h" /* amortize */ #include "spaceship.h" #include "aidata.h" #include "aitools.h" #include "advmilitary.h" #include "advdiplomacy.h" #define LOG_DIPL LOG_DEBUG #define LOG_DIPL2 LOG_NORMAL /* Prototypes */ static int ai_goldequiv_city(struct city *pcity); static int ai_goldequiv_tech(struct player *pplayer, Tech_Type_id tech); static int ai_war_desire(struct player *pplayer, struct player *aplayer, struct ai_data *ai); /* one hundred thousand */ #define BIG_NUMBER 100000 static bool diplomacy_verbose = TRUE; /********************************************************************** Calculate average distances to other players' empires. A possibly unwarranted assumption here is that our palace will be located in the middle of our empire. If we don't have one, punt. ***********************************************************************/ static int dist_to_empire(struct player *pplayer, struct player *target) { int cities = 0; int dists = 0; struct city *palace = find_palace(pplayer); if (pplayer == target || !palace || !target->is_alive) { return 1; } city_list_iterate(target->cities, pcity) { cities++; dists += real_map_distance(palace->x, palace->y, pcity->x, pcity->y); } city_list_iterate_end; if (cities > 0) { return MAX(dists / cities, 1); } else { return 1; } } /********************************************************************** Returns NULL iff aplayer is not allied to someone we are at war with. Otherwise returns a player struct pointer to first player that is a problem. ***********************************************************************/ static struct player *pplayer_allied_with_enemy(struct player *pplayer, struct player *aplayer) { assert(pplayer->is_alive && aplayer->is_alive); players_iterate(check_pl) { if (check_pl == pplayer || check_pl == aplayer || !check_pl->is_alive) { continue; } if (pplayers_allied(check_pl, aplayer) && pplayers_at_war(pplayer, check_pl)) { return check_pl; } } players_iterate_end; return NULL; } /********************************************************************** Returns NULL iff aplayer is not at war to someone we are allied with. Otherwise returns a player struct pointer to first player that is a problem. ***********************************************************************/ static struct player *pplayer_war_with_ally(struct player *pplayer, struct player *aplayer) { assert(pplayer->is_alive && aplayer->is_alive); players_iterate(check_pl) { if (check_pl == pplayer || check_pl == aplayer || !check_pl->is_alive) { continue; } if (pplayers_allied(check_pl, pplayer) && pplayers_at_war(aplayer, check_pl)) { return check_pl; } } players_iterate_end; return NULL; } /********************************************************************** Returns TRUE iff aplayer is allied to one of our allies and is not allied to someone we are at war with. Otherwise FALSE. ***********************************************************************/ static bool pplayer_is_allied_with_ally(struct player *pplayer, struct player *aplayer) { bool value = FALSE; assert(pplayer->is_alive && aplayer->is_alive); players_iterate(check_pl) { if (check_pl == pplayer || check_pl == aplayer || !check_pl->is_alive) { continue; } if (pplayers_allied(pplayer, check_pl) && pplayers_allied(check_pl, aplayer)) { value = TRUE; } else if (pplayers_allied(check_pl, aplayer) && pplayers_at_war(pplayer, check_pl)) { return FALSE; } } players_iterate_end; return value; } /********************************************************************** Send a diplomatic message. Use this instead of notify directly because we may want to highligh/present these messages differently in the future. ***********************************************************************/ #define diplo_notify(pplayer, text, ...) \ if (diplomacy_verbose) { \ notify_player_ex(pplayer, -1, -1, E_DIPLOMACY, text, __VA_ARGS__); \ } #define TALK(x) "*" #x "(AI)* " /********************************************************************** Convert lack of trust into demand for gold. (I leave it up to our math nerds to convert this function into something more elegant - Per) ***********************************************************************/ static int mistrust_to_gold(int missing_trust) { if (missing_trust >= 0) { return 0; } else if (missing_trust < 0 && missing_trust > -3) { return missing_trust * 10; } else if (missing_trust <= -3 && missing_trust > -5) { return missing_trust * 50; } else if (missing_trust <= -5 && missing_trust > -7) { return missing_trust * 100; } else if (missing_trust <= -7 && missing_trust > -9) { return missing_trust * 200; } else if (missing_trust <= -9 && missing_trust > -11) { return missing_trust * 500; } else if (missing_trust <= -11 && missing_trust > -13) { return missing_trust * 1000; } else { return -BIG_NUMBER; } } /********************************************************************** Evaluate gold worth of a single clause in a treaty. Note that it sometimes matter a great deal who is giving what to whom, and sometimes (such as with treaties) it does not matter at all. ***********************************************************************/ static int ai_goldequiv_clause(struct player *pplayer, struct player *aplayer, struct Clause *pclause, struct ai_data *ai, bool verbose) { int worth = 0; /* worth for pplayer of what aplayer gives */ bool give = (pplayer == pclause->from); int receiver, giver; struct ai_diplomacy *adip = &ai->diplomacy.other[aplayer->player_no]; diplomacy_verbose = verbose; giver = pclause->from->player_no; if (give) { receiver = aplayer->player_no; } else { receiver = pplayer->player_no; } switch (pclause->type) { case CLAUSE_ADVANCE: if (give) { worth -= ai_goldequiv_tech(aplayer, pclause->value); } else if (get_invention(pplayer, pclause->value) != TECH_KNOWN) { worth += ai_goldequiv_tech(pplayer, pclause->value); } /* Share and expect being shared brotherly between allies */ if (pplayers_allied(pplayer, aplayer)) { worth = 0; break; } /* Calculate in tech leak to our opponents, guess 50% chance */ players_iterate(eplayer) { if (eplayer == aplayer || eplayer == pplayer || !eplayer->is_alive || get_invention(eplayer, pclause->value) == TECH_KNOWN) { continue; } if (give && pplayers_allied(aplayer, eplayer)) { if (pplayers_at_war(pplayer, eplayer)) { /* Don't risk it falling into enemy hands */ worth = -BIG_NUMBER; break; } worth -= ai_goldequiv_tech(eplayer, pclause->value) / 2; } else if (!give && pplayers_allied(pplayer, eplayer)) { /* We can enrichen our side with this tech */ worth += ai_goldequiv_tech(eplayer, pclause->value) / 4; } } players_iterate_end; break; case CLAUSE_ALLIANCE: case CLAUSE_PEACE: case CLAUSE_CEASEFIRE: { struct player *alliedenemy = pplayer_allied_with_enemy(pplayer, aplayer); struct player *warally = pplayer_war_with_ally(pplayer, aplayer); /* This guy is at war with one of our allies. */ if (warally) { diplo_notify(aplayer, TALK(%s) "First make peace with %s, %s", pplayer->name, warally->name, aplayer->name); worth = -BIG_NUMBER; break; } /* And this guy is allied to one of our enemies. */ if (alliedenemy) { diplo_notify(aplayer, TALK(%s) "First break alliance with %s, %s", pplayer->name, alliedenemy->name, aplayer->name); worth = -BIG_NUMBER; break; } } /* Check if we can trust this guy. If we want to blast off to space, * we don't care, though. */ if ((GAME_DEFAULT_REPUTATION / 2 > aplayer->reputation || adip->hatred > 2) && ai->diplomacy.strategy != WIN_SPACE) { worth = -BIG_NUMBER; break; } /* Reduce treaty level? */ { enum diplstate_type ds = pplayer_get_diplstate(pplayer, aplayer)->type; if ((pclause->type == CLAUSE_PEACE && ds > DS_PEACE) || (pclause->type == CLAUSE_CEASEFIRE && ds > DS_CEASEFIRE)) { diplo_notify(aplayer, TALK(%s) "I will not let you go that easy, %s.", pplayer->name, aplayer->name); worth = -BIG_NUMBER; break; } } /* Let's all hold hands in one happy family */ if (pplayer_is_allied_with_ally(pplayer, aplayer)) { worth = 0; break; } /* Breaking treaties give us penalties on future diplomacy, so * avoid flip-flopping treaty/war with our chosen enemy. */ if (aplayer == ai->diplomacy.target) { worth = -BIG_NUMBER; break; } /* Steps of the ladder */ if (pclause->type == CLAUSE_PEACE) { if (!pplayers_non_attack(pplayer, aplayer)) { diplo_notify(aplayer, TALK(%s) "Let us first cease hostilies, %s", pplayer->name, aplayer->name); worth = -BIG_NUMBER; } else { worth = mistrust_to_gold(adip->trust - 10); } } else if (pclause->type == CLAUSE_ALLIANCE) { if (!pplayers_in_peace(pplayer, aplayer)) { diplo_notify(aplayer, TALK(%s) "Let us first make peace, %s", pplayer->name, aplayer->name); worth = -BIG_NUMBER; } else { worth = mistrust_to_gold(adip->trust - 25); } /* Ceasefire */ } else { worth = mistrust_to_gold(adip->trust); } break; case CLAUSE_GOLD: if (give) { worth -= pclause->value; } else { worth += pclause->value; } break; case CLAUSE_SEAMAP: if (!give || pplayers_allied(pplayer, aplayer)) { /* Useless to us - we're omniscient! And allies get it for free! */ worth = 0; } else { /* Very silly algorithm 1: Sea map more worth if enemy has more cities. Reasoning is he has more use of seamap for settling new areas the more cities he has already. */ worth -= 25 * city_list_size(&aplayer->cities); } break; case CLAUSE_MAP: if (!give || pplayers_allied(pplayer, aplayer)) { /* Useless to us - we're omniscient! And allies get it for free! */ worth = 0; } else { /* Very silly algorithm 2: Land map more worth the more cities we have, since we expose all of these to the enemy. */ worth -= 75 * city_list_size(&pplayer->cities); /* Inflate numbers if not peace */ if (!pplayers_in_peace(pplayer, aplayer)) { worth *= 3; } } break; case CLAUSE_CITY: { struct city *offer = city_list_find_id(&(pclause->from)->cities, pclause->value); if (!offer || offer->owner != giver) { /* City destroyed or taken during negotiations */ diplo_notify(aplayer, TALK(%s) "You don't have the offered city!", pplayer->name); worth = 0; } else if (give) { /* AI must be crazy to trade away its cities */ worth -= ai_goldequiv_city(offer); if (city_got_building(offer, B_PALACE)) { worth = -BIG_NUMBER; /* Never! Ever! */ } else { worth *= 15; } } else { worth = ai_goldequiv_city(offer); } break; } case CLAUSE_VISION: if (give) { if (pplayers_allied(pplayer, aplayer)) { worth = 0; } else { /* so out of the question */ worth = -BIG_NUMBER; } } else { worth = 0; /* We are omniscient, so... */ } break; } /* end of switch */ diplomacy_verbose = TRUE; return worth; } /********************************************************************** pplayer is AI player, aplayer is the other player involved, treaty is the treaty being considered. It is all a question about money :-) ***********************************************************************/ void ai_treaty_evaluate(struct player *pplayer, struct player *aplayer, struct Treaty *ptreaty) { struct packet_diplomacy_info packet; int total_balance = 0; bool has_treaty = FALSE; struct ai_data *ai = ai_data_get(pplayer); assert(!is_barbarian(pplayer)); packet.plrno0 = pplayer->player_no; packet.plrno1 = aplayer->player_no; packet.plrno_from = pplayer->player_no; /* Evaluate clauses */ clause_list_iterate(ptreaty->clauses, pclause) { total_balance += ai_goldequiv_clause(pplayer, aplayer, pclause, ai, TRUE); if (is_pact_clause(pclause->type)) { has_treaty = TRUE; } } clause_list_iterate_end; /* If we are at war, and no peace is offered, then no deal */ if (pplayers_at_war(pplayer, aplayer) && !has_treaty) { return; } /* Accept if balance is good */ if (total_balance >= 0) { handle_diplomacy_accept_treaty(pplayer, &packet); } } /********************************************************************** Comments to player from AI on clauses being agreed on. Does not alter any state. ***********************************************************************/ static void ai_treaty_react(struct player *pplayer, struct player *aplayer, struct Clause *pclause) { switch (pclause->type) { case CLAUSE_ALLIANCE: if (pplayer_is_allied_with_ally(pplayer, aplayer)) { diplo_notify(aplayer, TALK(%s) "Welcome into our alliance %s!", pplayer->name, aplayer->name); } else { diplo_notify(aplayer, TALK(%s) "Yes, may we forever stand united, %s", pplayer->name, aplayer->name); } freelog(LOG_DIPL2, "(%s ai diplo) %s allies with %s!", pplayer->name, pplayer->name, aplayer->name); break; case CLAUSE_PEACE: diplo_notify(aplayer, TALK(%s) "Yes, peace in our time!", pplayer->name); freelog(LOG_DIPL2, "(%s ai diplo) %s makes peace with %s", pplayer->name, pplayer->name, aplayer->name); break; case CLAUSE_CEASEFIRE: diplo_notify(aplayer, TALK(%s) "Agreed. No more hostilities, %s", pplayer->name, aplayer->name); freelog(LOG_DIPL2, "(%s ai diplo) %s agrees to ceasefire with %s", pplayer->name, pplayer->name, aplayer->name); break; default: break; } } /********************************************************************** pplayer is AI player, aplayer is the other player involved, ptreaty is the treaty accepted. ***********************************************************************/ void ai_treaty_accepted(struct player *pplayer, struct player *aplayer, struct Treaty *ptreaty) { int total_balance = 0; bool gift = TRUE; struct ai_data *ai = ai_data_get(pplayer); struct ai_diplomacy *adip = &ai->diplomacy.other[aplayer->player_no]; /* Evaluate clauses */ clause_list_iterate(ptreaty->clauses, pclause) { int balance = ai_goldequiv_clause(pplayer, aplayer, pclause, ai, TRUE); total_balance += balance; gift = (gift && (balance >= 0)); ai_treaty_react(pplayer, aplayer, pclause); } clause_list_iterate_end; /* Rather arbitrary algorithm */ if (total_balance > 0 && gift) { int i = total_balance / (city_list_size(&pplayer->cities) * 50); adip->trust += i; freelog(LOG_DIPL, "%s's gift to %s increased trust by %d", aplayer->name, pplayer->name, i); } } /********************************************************************** Check if we can win by space race victory. We can do this if we have a spacerace lead. ***********************************************************************/ static bool ai_space_victory(struct player *pplayer) { struct player_spaceship *our_ship = &pplayer->spaceship; int our_arrival = MAX((int) our_ship->travel_time - - (game.year - our_ship->launch_year), 1); if (game.spacerace == FALSE || our_ship->state == SSHIP_NONE) { return FALSE; } players_iterate(aplayer) { struct player_spaceship *enemy_ship = &aplayer->spaceship; int enemy_arrival = MAX((int) enemy_ship->travel_time - - (game.year - enemy_ship->launch_year), 1); if (!aplayer->is_alive) { continue; } if (enemy_ship->state > our_ship->state) { return FALSE; } if (enemy_ship->state == SSHIP_LAUNCHED && our_ship->state == SSHIP_LAUNCHED && enemy_arrival >= our_arrival) { return FALSE; } } players_iterate_end; return TRUE; } /********************************************************************** Calculate our desire to go to war against aplayer. ***********************************************************************/ static int ai_war_desire(struct player *pplayer, struct player *aplayer, struct ai_data *ai) { int kill_desire, arrival, our_arrival; struct player_spaceship *ship = &aplayer->spaceship; struct player_spaceship *our_ship = &pplayer->spaceship; enum diplstate_type ds = pplayer_get_diplstate(pplayer, aplayer)->type; bool cancel_excuse = pplayer->diplstates[aplayer->player_no].has_reason_to_cancel; struct ai_diplomacy *adip = &ai->diplomacy.other[aplayer->player_no]; /* Number of cities is a player's base potential. */ kill_desire = city_list_size(&aplayer->cities); /* Count settlers in production for us, indicating our expansionism, * while counting all enemy settlers as (worst case) indicators of * enemy expansionism */ city_list_iterate(pplayer->cities, pcity) { if (pcity->is_building_unit && unit_type_flag(pcity->currently_building, F_CITIES)) { kill_desire -= 1; } } city_list_iterate_end; unit_list_iterate(aplayer->units, punit) { if (unit_flag(punit, F_CITIES)) { kill_desire += 1; } } unit_list_iterate_end; /* Count big cities as twice the threat */ city_list_iterate(aplayer->cities, pcity) { kill_desire += pcity->size > 8 ? 1 : 0; } city_list_iterate_end; /* Tech lead is worrisome */ kill_desire += MAX(aplayer->research.techs_researched - pplayer->research.techs_researched, 0); /* Spacerace loss we will not allow! */ if (ship->state >= SSHIP_STARTED) { /* add potential */ kill_desire += city_list_size(&aplayer->cities); } if (our_ship->state >= SSHIP_LAUNCHED) { our_arrival = MAX((int) our_ship->travel_time - (game.year - our_ship->launch_year), 1); } else { our_arrival = FC_INFINITY; } if (ship->state >= SSHIP_LAUNCHED) { arrival = MAX((int) ship->travel_time - (game.year - ship->launch_year), 1); if (arrival < our_arrival) { /* The division is for the unlikely case of several simultaneous ship launches. */ kill_desire = BIG_NUMBER / arrival / 2; ai->diplomacy.timer = BIG_NUMBER; ai->diplomacy.strategy = WIN_CAPITAL; } } /* Modify by which treaties we would have to break, and what * excuses we have to do so. */ if (!cancel_excuse) { if (ds == DS_CEASEFIRE) { kill_desire -= kill_desire / 10; /* 10% off */ } else if (ds == DS_NEUTRAL) { kill_desire -= kill_desire / 7; /* 15% off */ } else if (ds == DS_PEACE) { kill_desire -= kill_desire / 5; /* 20% off */ } else if (ds == DS_ALLIANCE) { kill_desire -= kill_desire / 3; /* 33% off here, more later */ } } /* Modify by hatred */ kill_desire += kill_desire / 100 * adip->hatred * 10; /* Amortize by distance */ return amortize(kill_desire, adip->distance); } /********************************************************************** How much is a tech worth to player measured in gold ***********************************************************************/ static int ai_goldequiv_tech(struct player *pplayer, Tech_Type_id tech) { int worth; if (get_invention(pplayer, tech) == TECH_KNOWN) { return 0; } worth = total_bulbs_required_for_goal(pplayer, tech) * 3; worth += pplayer->ai.tech_want[tech] / 3; if (get_invention(pplayer, tech) == TECH_REACHABLE) { worth /= 2; } freelog(LOG_DIPL, "(%s ai diplo) %s goldequiv'ed to %d (had want %d)", pplayer->name, get_tech_name(pplayer, tech), worth, pplayer->ai.tech_want[tech]); return worth; } /********************************************************************** How much is city worth measured in gold ***********************************************************************/ static int ai_goldequiv_city(struct city *pcity) { int worth; struct player *pplayer = city_owner(pcity); worth = pcity->size * 150; /* reasonable base cost */ built_impr_iterate(pcity, impr) { if (improvement_types[impr].is_wonder && !wonder_obsolete(impr)) { worth += improvement_types[impr].build_cost; } else { worth += (improvement_types[impr].build_cost / 4); } } built_impr_iterate_end; if (city_unhappy(pcity)) { worth *= 0.75; } freelog(LOG_DIPL, "(%s ai diplo) city %s goldequiv'ed to %d", pplayer->name, pcity->name, worth); return worth; } /********************************************************************** Suggest a treaty from pplayer to aplayer ***********************************************************************/ static void ai_diplomacy_suggest(struct player *pplayer, struct player *aplayer, enum clause_type what, int value) { struct packet_diplomacy_info packet; if (!player_has_embassy(pplayer, aplayer)) { return; } packet.plrno_from = pplayer->player_no; packet.plrno0 = pplayer->player_no; packet.plrno1 = aplayer->player_no; packet.clause_type = what; packet.value = value; handle_diplomacy_init(pplayer, &packet); handle_diplomacy_create_clause(pplayer, &packet); } /********************************************************************** Calculate our diplomatic predispositions here. Don't do anything. Only ever called for AI players and never for barbarians. ***********************************************************************/ void ai_diplomacy_calculate(struct player *pplayer, struct ai_data *ai) { int war_desire[MAX_NUM_PLAYERS]; int best_desire = 0; struct player *target = NULL; memset(war_desire, 0, sizeof(war_desire)); assert(pplayer->ai.control); if (!pplayer->is_alive) { return; /* duh */ } /* If we have been wronged, crank up hatred. If allied, may reduce. * Increase trust in peace and conditions are right. */ players_iterate(aplayer) { int a = aplayer->player_no; struct ai_diplomacy *adip = &ai->diplomacy.other[a]; if (pplayer == aplayer || !aplayer->is_alive) { continue; } adip->hatred += pplayer->diplstates[a].has_reason_to_cancel; if (pplayers_allied(pplayer, aplayer) && myrand(4) == 0 && adip->hatred > 0) { adip->hatred--; } if (pplayers_non_attack(pplayer, aplayer) && !pplayer->diplstates[a].has_reason_to_cancel && !pplayer_allied_with_enemy(pplayer, aplayer) && !pplayer_war_with_ally(pplayer, aplayer) && adip->ally_patience >= 0) { adip->trust++; freelog(LOG_DIPL, "(%s ai diplo) Increased trust in %s by one", pplayer->name, aplayer->name); } else if (pplayers_at_war(pplayer, aplayer) || pplayer->diplstates[a].has_reason_to_cancel) { adip->trust = 0; freelog(LOG_DIPL, "(%s ai diplo) Trust in %s crushed to zero", pplayer->name, aplayer->name); } else { freelog(LOG_DEBUG, "(%s ai diplo) Trust in %s is unchanged", pplayer->name, aplayer->name); } } players_iterate_end; /* Stop war against a dead player */ if (ai->diplomacy.target && !ai->diplomacy.target->is_alive) { freelog(LOG_DIPL2, "(%s ai diplo) Target player %s is dead. Victory!", pplayer->name, ai->diplomacy.target->name); ai->diplomacy.timer = 0; ai->diplomacy.target = NULL; if (ai->diplomacy.strategy == WIN_CAPITAL) { ai->diplomacy.strategy = WIN_OPEN; } } /* Ensure that we don't prematurely end an ongoing war */ if (ai->diplomacy.timer-- > 0) { return; } /* Calculate average distances to other players' empires. */ players_iterate(aplayer) { ai->diplomacy.other[aplayer->player_no].distance = dist_to_empire(pplayer, aplayer); } players_iterate_end; /* Can we win by space race? */ if (ai_space_victory(pplayer)) { freelog(LOG_DIPL2, "%s going for space race victory!", pplayer->name); ai->diplomacy.strategy = WIN_SPACE; /* Yes! */ } else { if (ai->diplomacy.strategy == WIN_SPACE) { ai->diplomacy.strategy = WIN_OPEN; } } /* Calculate our desires, and find desired war target */ players_iterate(aplayer) { enum diplstate_type ds = pplayer_get_diplstate(pplayer, aplayer)->type; /* We don't hate ourselves, those we don't know and those we're * allied to or team members. */ if (aplayer == pplayer || !aplayer->is_alive || ds == DS_NO_CONTACT || (pplayer->team != TEAM_NONE && pplayer->team == aplayer->team) || ds == DS_ALLIANCE) { continue; } war_desire[aplayer->player_no] = ai_war_desire(pplayer, aplayer, ai); /* We don't want war if we can win through the space race. */ if (ai->diplomacy.strategy == WIN_SPACE && !pplayer_war_with_ally(pplayer, aplayer)) { continue; } /* Strongly prefer players we are at war with already */ if (pplayers_non_attack(pplayer, aplayer)) { war_desire[aplayer->player_no] /= 2; } freelog(LOG_DEBUG, "(%s ai diplo) Against %s we have war desire " "%d ", pplayer->name, aplayer->name, war_desire[aplayer->player_no]); /* Find best target */ if (war_desire[aplayer->player_no] > best_desire) { target = aplayer; best_desire = war_desire[aplayer->player_no]; } } players_iterate_end; if (!target) { freelog(LOG_DEBUG, "(%s ai diplo) Found no target.", pplayer->name); ai->diplomacy.target = NULL; return; } /* Switch to target */ if (target != ai->diplomacy.target) { freelog(LOG_DIPL, "(%s ai diplo) Setting target to %s", pplayer->name, target->name); ai->diplomacy.target = target; players_iterate(aplayer) { ai->diplomacy.other[aplayer->player_no].ally_patience = 0; } players_iterate_end; } } /********************************************************************** Offer techs to other player and ask for techs we need. ***********************************************************************/ static void ai_share_techs(struct player *pplayer, struct player *aplayer) { int index; for (index = A_FIRST; index < game.num_tech_types; index++) { if ((get_invention(pplayer, index) != TECH_KNOWN) && (get_invention(aplayer, index) == TECH_KNOWN)) { ai_diplomacy_suggest(aplayer, pplayer, CLAUSE_ADVANCE, index); } else if ((get_invention(pplayer, index) == TECH_KNOWN) && (get_invention(aplayer, index) != TECH_KNOWN)) { ai_diplomacy_suggest(pplayer, aplayer, CLAUSE_ADVANCE, index); } } if (!gives_shared_vision(pplayer, aplayer)) { ai_diplomacy_suggest(pplayer, aplayer, CLAUSE_VISION, 0); } if (!gives_shared_vision(aplayer, pplayer)) { ai_diplomacy_suggest(aplayer, pplayer, CLAUSE_VISION, 0); } } /********************************************************************** Go to war. ***********************************************************************/ static void ai_go_to_war(struct player *pplayer, struct ai_data *ai, struct player *target) { if (gives_shared_vision(pplayer, target)) { remove_shared_vision(pplayer, target); } while (!pplayers_at_war(pplayer, target)) { handle_player_cancel_pact(pplayer, target->player_no); } /* Continue war at least in this arbitrary number of turns to show * some spine */ ai->diplomacy.timer = myrand(4) + 3 + ai->diplomacy.other[target->player_no].hatred; ai->diplomacy.other[target->player_no].trust = 0; } /********************************************************************** Do diplomatic actions. Must be called only after calculate function above has been run for _all_ AI players. Only ever called for AI players and never for barbarians. ***********************************************************************/ void ai_diplomacy_actions(struct player *pplayer) { struct ai_data *ai = ai_data_get(pplayer); struct player *target = ai->diplomacy.target; assert(pplayer->ai.control); if (!pplayer->is_alive) { return; } /*** If we are greviously insulted, go to war. ***/ players_iterate(aplayer) { if (aplayer->reputation < GAME_DEFAULT_REPUTATION / 2 && ai->diplomacy.other[aplayer->player_no].hatred > 2 && pplayer->diplstates[aplayer->player_no].has_reason_to_cancel == 2) { freelog(LOG_DIPL2, "(%s ai diplo) Declaring war on %s in revenge", pplayer->name, target->name); diplo_notify(target, TALK(%s) "I will NOT accept such behaviour! This " "means WAR!", pplayer->name); ai_go_to_war(pplayer, ai, aplayer); } } players_iterate_end; /*** Stop other players from winning by space race ***/ if (ai->diplomacy.strategy != WIN_SPACE) { players_iterate(aplayer) { if (!aplayer->is_alive || aplayer == pplayer || (pplayer->team != TEAM_NONE && aplayer->team == pplayer->team)) { continue; } /* A spaceship victory is always one single player's or team's victory */ if (ai_space_victory(aplayer)) { if (pplayer->spaceship.state == SSHIP_LAUNCHED && pplayers_allied(pplayer, aplayer)) { diplo_notify(aplayer, TALK(%s) "Your attempt to conquer space for " "yourself alone betray your true intentions, and I " "will have no more of our alliance!", pplayer->name); handle_player_cancel_pact(pplayer, aplayer->player_no); if (gives_shared_vision(pplayer, aplayer)) { remove_shared_vision(pplayer, aplayer); } } else { diplo_notify(aplayer, TALK(%s) "Your attempt to unilaterally " "dominate outer space is highly offensive. If you " "do not stop constructing your spaceship, " "I may be forced to take action!", pplayer->name); } } } players_iterate_end; } /*** Declare war ***/ if (target && !pplayers_at_war(pplayer, target)) { freelog(LOG_DIPL2, "(%s ai diplo) Declaring war on %s", pplayer->name, target->name); if (pplayer->diplstates[target->player_no].has_reason_to_cancel > 0) { /* We have good reason */ diplo_notify(target, TALK(%s) "Your despicable actions will not go " "unpunished!", pplayer->name); } if (ai->diplomacy.other[target->player_no].hatred > 2) { /* We once had good reason */ diplo_notify(target, TALK(%s) "Finally I get around to taking " "revenge for the things you have done to me!", pplayer->name); } else { /* We have no good reason... so what? */ diplo_notify(target, TALK(%s) "Peace in ... some other time", pplayer->name); } ai_go_to_war(pplayer, ai, target); } /*** Opportunism, Inc. Try to make peace with everyone else ***/ players_iterate(aplayer) { enum diplstate_type ds = pplayer_get_diplstate(pplayer, aplayer)->type; bool transitive_war = FALSE; struct ai_diplomacy *adip = &ai->diplomacy.other[aplayer->player_no]; struct Clause clause; /* Meaningless values, but rather not have them unset. */ clause.from = pplayer; clause.value = 0; if (is_barbarian(aplayer) /* no barbarism */ || aplayer == pplayer /* no self-indulgence */ || aplayer == target /* no mercy */ || !aplayer->is_alive) { /* and no necromancy! */ continue; /* hah! */ } /* Is there a player that we are allied to which is at * war with this player? If so, keep war going. */ players_iterate(eplayer) { if (pplayers_allied(pplayer, eplayer) && eplayer->is_alive && pplayers_at_war(eplayer, aplayer)) { transitive_war = TRUE; } } players_iterate_end; if (transitive_war) { continue; } /* Spam control */ if (adip->spam > 0) { adip->spam--; } if ((!player_has_embassy(pplayer, aplayer) && !player_has_embassy(aplayer, pplayer)) || ds == DS_NO_CONTACT) { continue; } if (adip->spam > 0) { /* Don't spam */ continue; } /* Canvass support from existing friends for our war, and try to * make friends with enemies. Then we wait some turns until next time * we spam them with our gibbering chatter. */ if (!pplayers_allied(pplayer, aplayer)) { adip->spam = myrand(4) + 3; } else { /* Less important. */ adip->spam = myrand(8) + 6 * adip->hatred + 1; } switch (ds) { case DS_ALLIANCE: if ((pplayer->team != TEAM_NONE && aplayer->team == pplayer->team) || (target && pplayers_at_war(aplayer, target))) { /* Share techs only with team mates and _reliable_ allies */ ai_share_techs(pplayer, aplayer); adip->ally_patience = 0; break; } else if (!target) { adip->ally_patience = 0; break; } freelog(LOG_DIPL, "(%s ai diplo) demanding support from %s to crush %s", pplayer->name, aplayer->name, target->name); switch (adip->ally_patience--) { case 0: diplo_notify(aplayer, TALK(%s) "Greetings our most trustworthy ally, " "we call upon you to destroy our enemy, %s", pplayer->name, target->name); break; case -1: diplo_notify(aplayer, TALK(%s) "Greetings ally, I see you have not yet " "made war with our enemy, %s. Why do I need to remind " "you of your promises?", pplayer->name, target->name); break; case -2: diplo_notify(aplayer, TALK(%s) "Dishonoured one, we made a pact of " "alliance, and yet you remain at peace with our mortal " "enemy, %s! This is unacceptable, our alliance is no " "more!", pplayer->name, target->name); freelog(LOG_DIPL2, "(%s ai diplo) breaking useless alliance with %s", pplayer->name, aplayer->name); handle_player_cancel_pact(pplayer, aplayer->player_no); /* peace */ adip->hatred++; adip->trust = 0; if (pplayer->diplstates[aplayer->player_no].has_reason_to_cancel > 0) { handle_player_cancel_pact(pplayer, aplayer->player_no); /* neutral */ } if (gives_shared_vision(pplayer, aplayer)) { remove_shared_vision(pplayer, aplayer); } break; } break; case DS_PEACE: clause.type = CLAUSE_ALLIANCE; if (ai_goldequiv_clause(pplayer, aplayer, &clause, ai, FALSE) < 0 || (adip->asked_about_alliance && !aplayer->ai.control)) { break; /* never */ } ai_diplomacy_suggest(pplayer, aplayer, CLAUSE_ALLIANCE, 0); adip->asked_about_alliance = !aplayer->ai.control; if (target) { diplo_notify(aplayer, TALK(%s) "Greetings friend, may we suggest " "a joint campaign against %s?", pplayer->name, target->name); freelog(LOG_DIPL, "(%s ai diplo) requesting support from %s to crush %s", pplayer->name, aplayer->name, target->name); } else { diplo_notify(aplayer, TALK(%s) "You have proven a good friend, %s. How " "about going into an alliance?", pplayer->name, aplayer->name); freelog(LOG_DIPL, "(%s ai diplo) requesting alliance from %s", pplayer->name, aplayer->name); } break; case DS_CEASEFIRE: case DS_NEUTRAL: clause.type = CLAUSE_PEACE; if (ai_goldequiv_clause(pplayer, aplayer, &clause, ai, FALSE) < 0 || (adip->asked_about_peace && !aplayer->ai.control)) { break; /* never */ } ai_diplomacy_suggest(pplayer, aplayer, CLAUSE_PEACE, 0); adip->asked_about_peace = !aplayer->ai.control; if (target) { diplo_notify(aplayer, TALK(%s) "Greetings friend, may we suggest " "a joint campaign against %s?", pplayer->name, target->name); freelog(LOG_DIPL, "(%s ai diplo) requesting peace from %s to crush %s", pplayer->name, aplayer->name, target->name); } else { diplo_notify(aplayer, TALK(%s) "I have seen little aggression from you, " "%s. Maybe you can be trusted. Shall we sign a peace treaty?", pplayer->name, aplayer->name); freelog(LOG_DIPL, "(%s ai diplo) requesting peace from %s", pplayer->name, aplayer->name); } break; case DS_WAR: clause.type = CLAUSE_CEASEFIRE; if (ai_goldequiv_clause(pplayer, aplayer, &clause, ai, FALSE) < 0 || (adip->asked_about_ceasefire && !aplayer->ai.control)) { break; /* never */ } ai_diplomacy_suggest(pplayer, aplayer, CLAUSE_CEASEFIRE, 0); adip->asked_about_ceasefire = !aplayer->ai.control; if (target) { diplo_notify(aplayer, TALK(%s) "%s is threatening us both, may we suggest" " a cessation of hostilities?", pplayer->name, target->name); freelog(LOG_DIPL, "(%s ai diplo) requesting ceasefire from %s to crush %s", pplayer->name, aplayer->name, target->name); } else { diplo_notify(aplayer, TALK(%s) "War is such a terrible waste. Perhaps we " "can find a more civilized way of dealing with each other?", pplayer->name); freelog(LOG_DIPL, "(%s ai diplo) requesting ceasefire from %s", pplayer->name, aplayer->name); } break; default: assert(FALSE); break; } } players_iterate_end; }
|