Complete.Org: Mailing Lists: Archives: freeciv-ai: March 2003:
[freeciv-ai] AI Diplomacy v8 (PR#2413)
Home

[freeciv-ai] AI Diplomacy v8 (PR#2413)

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: undisclosed-recipients:;
Subject: [freeciv-ai] AI Diplomacy v8 (PR#2413)
From: "Per I. Mathisen" <per@xxxxxxxxxxx>
Date: Tue, 4 Mar 2003 15:59:55 -0800
Reply-to: rt@xxxxxxxxxxxxxx

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

Attachment: warofallies.sav.gz
Description: warofallies.sav.gz

Attachment: diplomacy8.diff.gz
Description: 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;
}

[Prev in Thread] Current Thread [Next in Thread]