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

[freeciv-ai] AI Diplomacy v9 (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 v9 (PR#2413)
From: "Per I. Mathisen" <per@xxxxxxxxxxx>
Date: Fri, 25 Apr 2003 09:36:28 -0700
Reply-to: rt@xxxxxxxxxxxxxx

CHANGES:
 - We no longer relate to other players in terms of 'trust' and 'hatred',
but instead use a single metric called 'love'. (No, the AI has not turned
into a hippie. It is still its same old warmongering self.) We reduce this
metric with a % of itself each turn to keep it sane.
 - We now save our primary metric (ie love) to savegames.
 - No longer spam players building spaceships (only once in a while).
 - Distance to other empires are now calculated as an average between all
our cities and the closest enemy city. This avoids the problem of "is our
capital really in the middle", and makes AI opportunistic in terms of
grabbing Falklands Island cities.
  - Do ask more than once about treaties, but wait quite a while if we get
a 'no'. We hatess spam.
  - Sell at half price when selling a player a city that used to be his.
  - Don't wait for other wars to finish before starting another if we see
a space launch.
 - Added missing headers.
 - We now use the new function player_is_dangerous() to figure out who to
trust and who to not.
 - Added concept of an 'alliance leader' who can make ceasefires on
behalf of an alliance. This way we can break the "we're at war with two
allied enemies and neither wants to let his ally down but both want peace"
deadlock.
 - Respect ceasefires, unless we have an alliance leader who doesn't. Then
we follow the mob.
 - Lots of smaller changes all over the code that I can't really remember.

Thanks to Raimar, Chris, Thomas, Jordi, Greg and Banjo for invaluable
comments and bug reports on the previous version.

Due to the massive amounts of changes, it is guaranteed to be full of
entertaining and exciting bugs. Playtesters very much wanted.

(I already know there is a bug when teleporting units when an alliance is
broken. It can give an assertion failure when compiled with
--enable-debug=yes in some rare cases. I don't know why or how to fix,
though.)

  - Per

? ai/advdiplomacy.c
? ai/advdiplomacy.h
Index: ai/Makefile.am
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/Makefile.am,v
retrieving revision 1.13
diff -u -r1.13 Makefile.am
--- ai/Makefile.am      2003/03/11 17:59:26     1.13
+++ ai/Makefile.am      2003/04/25 16:29:22
@@ -21,6 +21,8 @@
                advmilitary.h   \
                advscience.c    \
                advscience.h    \
+               advdiplomacy.c  \
+               advdiplomacy.h  \
                advspace.c      \
                advspace.h      \
                advtrade.c      \
Index: ai/advmilitary.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/advmilitary.c,v
retrieving revision 1.140
diff -u -r1.140 advmilitary.c
--- ai/advmilitary.c    2003/04/20 11:22:59     1.140
+++ ai/advmilitary.c    2003/04/25 16:29:22
@@ -29,6 +29,7 @@
 #include "gotohand.h"          /* warmap has been redeployed */
 #include "settlers.h"
 
+#include "advdiplomacy.h"
 #include "aiair.h"
 #include "aicity.h"
 #include "aidiplomat.h"
Index: ai/aidata.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aidata.c,v
retrieving revision 1.12
diff -u -r1.12 aidata.c
--- ai/aidata.c 2003/04/20 11:22:59     1.12
+++ ai/aidata.c 2003/04/25 16:29:22
@@ -23,13 +23,16 @@
 #include "government.h"
 #include "map.h"
 #include "mem.h"
+#include "rand.h"
 #include "unit.h"
 
 #include "citytools.h"
+#include "diplhand.h"
 #include "maphand.h"
 #include "settlers.h"
 #include "unittools.h"
 
+#include "advdiplomacy.h"
 #include "advmilitary.h"
 #include "aicity.h"
 #include "aitools.h"
@@ -61,6 +64,8 @@
   bool can_build_antiair =  can_player_build_improvement(pplayer, B_SAM);
   bool can_build_antinuke = can_player_build_improvement(pplayer, B_SDI);
   bool can_build_antimissile = can_player_build_improvement(pplayer, B_SDI);
+  int ally_strength = -1;
+  struct player *ally_strongest = NULL;
 
   /* Threats */
 
@@ -198,6 +203,10 @@
 
   /* Diplomacy */
 
+  if (pplayer->ai.control && !is_barbarian(pplayer)) {
+    ai_diplomacy_calculate(pplayer, ai);
+  }
+
   /* Question: What can we accept as the reputation of a player before
    * we start taking action to prevent us from being suckered?
    * Answer: Very little. */
@@ -211,9 +220,17 @@
   for (i = 0; i < MAX_NUM_PLAYERS; i++) {
     struct player *aplayer = get_player(i);
 
-    ai->diplomacy.player_intel[i].is_allied_with_enemy = FALSE;
-    ai->diplomacy.player_intel[i].at_war_with_ally = FALSE;
-    ai->diplomacy.player_intel[i].is_allied_with_ally = FALSE;
+    ai->diplomacy.player_intel[i].is_allied_with_enemy = NULL;
+    ai->diplomacy.player_intel[i].at_war_with_ally = NULL;
+    ai->diplomacy.player_intel[i].is_allied_with_ally = NULL;
+
+    /* Determine who is the leader of our alliance. That is,
+     * whoever has the more cities. */
+    if (pplayers_allied(pplayer, aplayer)
+        && city_list_size(&aplayer->cities) > ally_strength) {
+      ally_strength = city_list_size(&aplayer->cities);
+      ally_strongest = aplayer;
+    }
 
     players_iterate(check_pl) {
       if (check_pl == pplayer || check_pl == aplayer
@@ -222,18 +239,21 @@
       }
       if (pplayers_allied(aplayer, check_pl)
           && pplayers_at_war(pplayer, check_pl)) {
-       ai->diplomacy.player_intel[i].is_allied_with_enemy = TRUE;
+       ai->diplomacy.player_intel[i].is_allied_with_enemy = check_pl;
       }
       if (pplayers_allied(pplayer, check_pl)
           && pplayers_at_war(aplayer, check_pl)) {
-        ai->diplomacy.player_intel[i].at_war_with_ally = TRUE;
+        ai->diplomacy.player_intel[i].at_war_with_ally = check_pl;
       }
       if (pplayers_allied(aplayer, check_pl)
           && pplayers_allied(pplayer, check_pl)) {
-        ai->diplomacy.player_intel[i].is_allied_with_ally = TRUE;
+        ai->diplomacy.player_intel[i].is_allied_with_ally = check_pl;
       }
     } players_iterate_end;
   }
+  if (ally_strongest != ai->diplomacy.alliance_leader) {
+    ai->diplomacy.alliance_leader = ally_strongest;
+  }
 
   /* 
    * Priorities. NEVER set these to zero! Weight values are usually
@@ -263,6 +283,32 @@
   free(ai->threats.continent); ai->threats.continent = NULL;
   free(ai->stats.workers);     ai->stats.workers = NULL;
   free(ai->stats.cities);      ai->stats.cities = NULL;
+}
+
+/**************************************************************************
+  Initialize with sane values.
+**************************************************************************/
+void ai_data_init(struct player *pplayer) {
+  struct ai_data *ai = &aidata[pplayer->player_no];
+  int i;
+
+  ai->diplomacy.target = NULL;
+  ai->diplomacy.strategy = WIN_OPEN;
+  ai->diplomacy.timer = 0;
+  ai->diplomacy.love_coeff = 5; /* 5% */
+  ai->diplomacy.love_incr = 4;
+  ai->diplomacy.alliance_leader = pplayer;
+
+  for (i = 0; i < MAX_NUM_PLAYERS; i++) {
+    ai->diplomacy.player_intel[i].spam = i; /* pseudorandom */
+    ai->diplomacy.player_intel[i].distance = 1;
+    ai->diplomacy.player_intel[i].ally_patience = 0;
+    ai->diplomacy.player_intel[i].love = 1;
+    ai->diplomacy.player_intel[i].asked_about_peace = 0;
+    ai->diplomacy.player_intel[i].asked_about_alliance = 0;
+    ai->diplomacy.player_intel[i].asked_about_ceasefire = 0;
+    ai->diplomacy.player_intel[i].warned_about_space = 0;
+  }
 }
 
 /**************************************************************************
Index: ai/aidata.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aidata.h,v
retrieving revision 1.6
diff -u -r1.6 aidata.h
--- ai/aidata.h 2003/04/20 11:22:59     1.6
+++ ai/aidata.h 2003/04/25 16:29:22
@@ -24,10 +24,27 @@
  * start of every turn. 
  */
 
+enum winning_strategy {
+  WIN_OPEN,     /* still undetermined */
+  WIN_WAR,      /* we have no other choice than to crush all opposition */
+  WIN_SPACE,    /* we will race for space, peace very important */
+  WIN_CAPITAL   /* we cannot win unless we take war_target's capital */
+};
+
 struct ai_dip_intel {
-  bool is_allied_with_enemy;
-  bool at_war_with_ally;
-  bool is_allied_with_ally;
+  /* Remember one example of each for text spam purposes. */
+  struct player *is_allied_with_enemy;
+  struct player *at_war_with_ally;
+  struct player *is_allied_with_ally;
+
+  char spam;      /* timer to avoid spamming a player with chat */
+  int distance;   /* average distance to that player's cities */
+  char ally_patience; /* we EXPECT our allies to help us! */
+  char love;      /* basic player <-> player relation */
+  char asked_about_peace;     /* don't ask again */
+  char asked_about_alliance;  /* don't nag! */
+  char asked_about_ceasefire; /* don't ... you get the point */
+  char warned_about_space;
 };
 
 struct ai_data {
@@ -35,6 +52,12 @@
   struct {
     int acceptable_reputation;
     struct ai_dip_intel player_intel[MAX_NUM_PLAYERS];
+    enum winning_strategy strategy;
+    int timer; /* pursue our goals with some stubbornness, in turns */
+    struct player *target;    /* Concentrate on this player */
+    char love_coeff;          /* Reduce love with this % each turn */
+    char love_incr;           /* Modify love with this fixed amount */
+    struct player *alliance_leader; /* Who is leading our alliance */
   } diplomacy;
 
   /* Long-term threats, not to be confused with short-term danger */
@@ -75,6 +98,8 @@
   int angry_priority;
   int pollution_priority;
 };
+
+void ai_data_init(struct player *pplayer);
 
 void ai_data_turn_init(struct player *pplayer);
 void ai_data_turn_done(struct player *pplayer);
Index: client/options.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/client/options.c,v
retrieving revision 1.80
diff -u -r1.80 options.c
--- client/options.c    2003/04/12 18:24:41     1.80
+++ client/options.c    2003/04/25 16:29:22
@@ -281,6 +281,7 @@
   GEN_EV(N_("Wonder: Started"),                       E_WONDER_STARTED),
   GEN_EV(N_("Wonder: Stopped"),                       E_WONDER_STOPPED),
   GEN_EV(N_("Wonder: Will Finish Next Turn"),         E_WONDER_WILL_BE_BUILT),
+  GEN_EV(N_("Diplomatic Message"),                    E_DIPLOMACY),
   GEN_EV_TERMINATOR
 };
 
Index: common/diptreaty.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/common/diptreaty.c,v
retrieving revision 1.15
diff -u -r1.15 diptreaty.c
--- common/diptreaty.c  2003/04/17 20:06:36     1.15
+++ common/diptreaty.c  2003/04/25 16:29:22
@@ -40,8 +40,8 @@
               || player_has_embassy(pplayer, aplayer)
               || pplayer->diplstates[aplayer->player_no].contact_turns_left > 0
               || aplayer->diplstates[pplayer->player_no].contact_turns_left > 
0)
-          && aplayer->is_connected
-          && pplayer->is_connected);
+          && (aplayer->is_connected || aplayer->ai.control)
+          && (pplayer->is_connected || pplayer->ai.control));
 }
 
 /****************************************************************
Index: common/events.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/common/events.h,v
retrieving revision 1.22
diff -u -r1.22 events.h
--- common/events.h     2002/05/07 07:40:53     1.22
+++ common/events.h     2003/04/25 16:29:22
@@ -102,6 +102,7 @@
   E_WONDER_STARTED,
   E_WONDER_STOPPED,
   E_WONDER_WILL_BE_BUILT,
+  E_DIPLOMACY,
   /* 
    * Note: If you add a new event, make sure you make a similar change
    * to the events array in client/options.c using GEN_EV and to
Index: common/player.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/common/player.c,v
retrieving revision 1.117
diff -u -r1.117 player.c
--- common/player.c     2003/04/18 10:08:53     1.117
+++ common/player.c     2003/04/25 16:29:22
@@ -553,6 +553,23 @@
 }
 
 /***************************************************************
+  Returns true iff players are allied or at peace.
+***************************************************************/
+bool pplayers_in_peace(const struct player *pplayer,
+                       const struct player *pplayer2)
+{
+  enum diplstate_type ds = pplayer_get_diplstate(pplayer, pplayer2)->type;
+
+  if (pplayer == pplayer2) {
+    return TRUE;
+  }
+  if (is_barbarian(pplayer) || is_barbarian(pplayer2)) {
+    return FALSE;
+  }
+  return (ds == DS_PEACE || ds == DS_ALLIANCE);
+}
+
+/***************************************************************
   Returns true iff players have peace or cease-fire.
 ***************************************************************/
 bool pplayers_non_attack(const struct player *pplayer,
Index: common/player.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/common/player.h,v
retrieving revision 1.98
diff -u -r1.98 player.h
--- common/player.h     2003/04/17 20:06:36     1.98
+++ common/player.h     2003/04/25 16:29:22
@@ -259,6 +259,8 @@
                    const struct player *pplayer2);
 bool pplayers_allied(const struct player *pplayer,
                    const struct player *pplayer2);
+bool pplayers_in_peace(const struct player *pplayer,
+                    const struct player *pplayer2);
 bool pplayers_non_attack(const struct player *pplayer,
                        const struct player *pplayer2);
 
Index: server/diplhand.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/server/diplhand.c,v
retrieving revision 1.68
diff -u -r1.68 diplhand.c
--- server/diplhand.c   2003/04/17 20:06:36     1.68
+++ server/diplhand.c   2003/04/25 16:29:22
@@ -37,6 +37,8 @@
 #include "settlers.h"
 #include "unittools.h"
 
+#include "advdiplomacy.h"
+
 #include "diplhand.h"
 
 #define SPECLIST_TAG treaty
@@ -282,6 +284,13 @@
       }
     } clause_list_iterate_end;
 
+    if (plr0->ai.control) {
+      ai_treaty_accepted(plr0, plr1, ptreaty);
+    }
+    if (plr1->ai.control) {
+      ai_treaty_accepted(plr1, plr0, ptreaty);
+    }
+
     clause_list_iterate(ptreaty->clauses, pclause) {
       pgiver = pclause->from;
       pdest = (plr0==pgiver) ? plr1 : plr0;
@@ -292,6 +301,11 @@
          * and try to give us the same tech at the same time. This
          * should be handled discreetly instead of giving a core dump. */
         if (get_invention(pdest, pclause->value) == TECH_KNOWN) {
+         freelog(LOG_VERBOSE,
+                  "The %s already know tech %s, that %s want to give them.",
+                 get_nation_name_plural(pdest->nation),
+                 advances[pclause->value].name,
+                 get_nation_name_plural(pgiver->nation));
           break;
         }
        notify_player_ex(pdest, -1, -1, E_TECH_GAIN,
@@ -400,7 +414,7 @@
       }
 
     } clause_list_iterate_end;
-  cleanup:      
+  cleanup:
     treaty_list_unlink(&treaties, ptreaty);
     free(ptreaty);
     send_player_info(plr0, NULL);
@@ -431,9 +445,14 @@
                                 PACKET_DIPLOMACY_REMOVE_CLAUSE, packet);
       lsend_packet_diplomacy_info(&plr1->connections, 
                                 PACKET_DIPLOMACY_REMOVE_CLAUSE, packet);
+      if (plr0->ai.control) {
+        ai_treaty_evaluate(plr0, plr1, ptreaty);
+      }
+      if (plr1->ai.control) {
+        ai_treaty_evaluate(plr1, plr0, ptreaty);
+      }
     }
   }
-
 }
 
 /**************************************************************************
@@ -474,6 +493,12 @@
       lsend_packet_diplomacy_info(&plr1->connections, 
                                 PACKET_DIPLOMACY_CREATE_CLAUSE, 
                                 packet);
+      if (plr0->ai.control) {
+        ai_treaty_evaluate(plr0, plr1, ptreaty);
+      }
+      if (plr1->ai.control) {
+        ai_treaty_evaluate(plr1, plr0, ptreaty);
+      }
     }
   }
 }
@@ -542,10 +567,11 @@
   plr0=&game.players[packet->plrno0];
   plr1=&game.players[packet->plrno1];
 
+  assert(plr0 != plr1);
+
   if (!find_treaty(plr0, plr1)) {
-    if (plr0->ai.control || plr1->ai.control) {
-      notify_player(plr0, _("AI controlled players cannot participate in "
-                           "diplomatic meetings."));
+    if (is_barbarian(plr0) || is_barbarian(plr1)) {
+      notify_player(plr0, _("Your diplomatic envoy was decapitated!"));
       return;
     }
 
@@ -587,6 +613,7 @@
     return;
   }
   players_iterate(other_player) {
+
     if ( (ptreaty=find_treaty(pplayer, other_player))) {
       struct packet_diplomacy_info packet;
       
Index: server/plrhand.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/server/plrhand.c,v
retrieving revision 1.267
diff -u -r1.267 plrhand.c
--- server/plrhand.c    2003/04/17 20:06:36     1.267
+++ server/plrhand.c    2003/04/25 16:29:22
@@ -1277,6 +1277,7 @@
     player_map_allocate(pplayer);
   }
   player_init(pplayer);
+  ai_data_init(pplayer);
 }
 
 /********************************************************************** 
@@ -1328,12 +1329,10 @@
     return;
   }
 
-  /* FIXME: Always declaring war for the AI is a kludge until AI
-     diplomacy is implemented. */
   if (pplayer_get_diplstate(pplayer1, pplayer2)->type == DS_NO_CONTACT) {
     pplayer1->diplstates[player2].type
       = pplayer2->diplstates[player1].type
-      = pplayer1->ai.control || pplayer2->ai.control ? DS_WAR : DS_NEUTRAL;
+      = DS_NEUTRAL;
     notify_player_ex(pplayer1, x, y,
                     E_FIRST_CONTACT,
                     _("Game: You have made contact with the %s, ruled by %s."),
@@ -1489,10 +1488,10 @@
    * but for now AI players are always at war.
    */
   players_iterate(other_player) {
-    cplayer->diplstates[other_player->player_no].type = DS_WAR;
+    cplayer->diplstates[other_player->player_no].type = DS_NEUTRAL;
     cplayer->diplstates[other_player->player_no].has_reason_to_cancel = 0;
     cplayer->diplstates[other_player->player_no].turns_left = 0;
-    other_player->diplstates[cplayer->player_no].type = DS_WAR;
+    other_player->diplstates[cplayer->player_no].type = DS_NEUTRAL;
     other_player->diplstates[cplayer->player_no].has_reason_to_cancel = 0;
     other_player->diplstates[cplayer->player_no].turns_left = 0;
     
@@ -1518,7 +1517,7 @@
   for(i = 0; i<game.num_tech_types ; i++)
     cplayer->research.inventions[i] = pplayer->research.inventions[i];
   cplayer->turn_done = TRUE; /* Have other things to think about - paralysis*/
-  cplayer->embassy = 0;   /* all embassys destroyed */
+  cplayer->embassy = 0;   /* all embassies destroyed */
 
   /* Do the ai */
 
Index: server/sanitycheck.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/server/sanitycheck.c,v
retrieving revision 1.28
diff -u -r1.28 sanitycheck.c
--- server/sanitycheck.c        2003/02/17 02:11:27     1.28
+++ server/sanitycheck.c        2003/04/25 16:29:22
@@ -236,8 +236,10 @@
       }
 
       pcity = map_get_city(x, y);
-      if (pcity) {
-       assert(pplayers_allied(city_owner(pcity), pplayer));
+      if (pcity && !pplayers_allied(city_owner(pcity), pplayer)) {
+        die("%s has non-allied unit in %s's city %s at (%d, %d)",
+            pplayer->name, city_owner(pcity)->name, pcity->name,
+            pcity->x, pcity->y);
       }
 
       if (is_ocean(map_get_terrain(x, y))) {
Index: server/savegame.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/server/savegame.c,v
retrieving revision 1.116
diff -u -r1.116 savegame.c
--- server/savegame.c   2003/04/22 20:50:56     1.116
+++ server/savegame.c   2003/04/25 16:29:22
@@ -48,6 +48,7 @@
 #include "unittools.h"
 
 #include "aicity.h"
+#include "aidata.h"
 #include "aiunit.h"
 
 #include "savegame.h"
@@ -684,7 +685,13 @@
 
   plr->reputation=secfile_lookup_int_default(file, GAME_DEFAULT_REPUTATION,
                                             "player%d.reputation", plrno);
-  for(i=0; i<game.nplayers; i++) {
+  for (i=0; i < game.nplayers; i++) {
+    struct ai_data *ai = ai_data_get(plr);
+
+    ai->diplomacy.player_intel[i].love =
+      secfile_lookup_int_default(file, 0,
+                                "player%d.ai_diplomacy%d.love", plrno, i);
+
     plr->diplstates[i].type = 
       secfile_lookup_int_default(file, DS_WAR,
                                 "player%d.diplstate%d.type", plrno, i);
@@ -1329,6 +1336,10 @@
 
   secfile_insert_int(file, plr->reputation, "player%d.reputation", plrno);
   for (i = 0; i < MAX_NUM_PLAYERS+MAX_NUM_BARBARIANS; i++) {
+    struct ai_data *ai = ai_data_get(plr);
+
+    secfile_insert_int(file, ai->diplomacy.player_intel[i].love,
+                       "player%d.ai_diplomacy%d.love", plrno, i);
     secfile_insert_int(file, plr->diplstates[i].type,
                       "player%d.diplstate%d.type", plrno, i);
     secfile_insert_int(file, plr->diplstates[i].turns_left,
Index: server/srv_main.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/server/srv_main.c,v
retrieving revision 1.123
diff -u -r1.123 srv_main.c
--- server/srv_main.c   2003/04/17 20:06:37     1.123
+++ server/srv_main.c   2003/04/25 16:29:23
@@ -91,6 +91,7 @@
 #include "unithand.h"
 #include "unittools.h"
 
+#include "advdiplomacy.h"
 #include "advmilitary.h"
 #include "aidata.h"
 #include "aihand.h"
@@ -467,9 +468,15 @@
     send_player_cities(pplayer);
   } players_iterate_end;
 
-  flush_packets();                     /* to curb major city spam */
-
+  flush_packets();  /* to curb major city spam */
   conn_list_do_unbuffer(&game.game_connections);
+
+  /* Try to avoid hiding events under a diplomacy dialog */
+  players_iterate(pplayer) {
+    if (pplayer->ai.control && !is_barbarian(pplayer)) {
+      ai_diplomacy_actions(pplayer);
+    }
+  } players_iterate_end;
 }
 
 /**************************************************************************
/********************************************************************** 
 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 <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#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"

/*

  "When a lobsterman leaves a trap out in the sea and can't get
  to it for a while, he will find the remains of many lobsters,
  but only one survivor. You might think that the survivor of the 
  lobster-battles would be the biggest lobster, but actually it 
  will always be the SECOND-SMALLEST. That's because when there 
  are a bunch of lobsters in the tank, they always gang up on the 
  biggest one first, until there are only two left, and then the 
  bigger one wins."
  (Anecdote by banjo@xxxxxxxxxx)

Although the AIs are not this flawlessly deterministic in choosing their
enemies (they respect alliances and try to preserve treaties among other
things), this is the basic premise. Gang up on the biggest threat and
bring it down. Don't be too afraid to ally up with the others and don't
worry about our own safety. If everyone think the same way, there'll be
safety in numbers :-) If not, well, at least it makes a good show.

*/

#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

/* turn this off when we don't want functions to message players */
static bool diplomacy_verbose = TRUE;

/********************************************************************** 
  Calculate average distances to other players' empires. We calculate
  the average distance from all of our cities to the closest enemy
  city (except we do it in reverse).
***********************************************************************/
static int dist_to_empire(struct player *pplayer, struct player *target)
{
  int cities = 0;
  int dists = 0;

  if (pplayer == target
      || !target->is_alive
      || city_list_size(&pplayer->cities) == 0
      || city_list_size(&target->cities) == 0) {
    return 1;
  }

  /* For all enemy cities, find the closest distance from
   * one of our cities to it. */
  city_list_iterate(target->cities, pcity) {
    int min_dist = BIG_NUMBER;

    city_list_iterate(pplayer->cities, p2) {
      int dist = real_map_distance(p2->x, p2->y, pcity->x, pcity->y);

      if (min_dist > dist) {
        min_dist = dist;
      }
    } city_list_iterate_end;
    dists += min_dist;
    cities++;
  } city_list_iterate_end;

  if (cities > 0) {
    return MAX(dists / cities, 1);
  } else {
    return 1;
  }
}

/********************************************************************** 
  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)* "

/********************************************************************** 
  This is your typical human reaction. Convert lack of love into 
  lust for gold.
***********************************************************************/
static int greed(int missing_love)
{
  if (missing_love >= 0) {
    return 0;
  } else if (missing_love < 0 && missing_love > -5) {
    return missing_love * 10;
  } else if (missing_love <= -5 && missing_love > -10) {
    return missing_love * 25;
  } else if (missing_love <= -10 && missing_love > -30) {
    return missing_love * 50;
  } else if (missing_love <= -30 && missing_love > -50) {
    return missing_love * 100;
  } else if (missing_love <= -50 && missing_love > -70) {
    return missing_love * 250;
  } else {
    return missing_love * 500;
  }
}

/********************************************************************** 
  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_dip_intel *adip = &ai->diplomacy.player_intel[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 (is_player_dangerous(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:
    {
      /* This guy is at war with our alliance and we're not alliance
       * leader. */
      if (pplayer != ai->diplomacy.alliance_leader
          && pplayers_at_war(aplayer, ai->diplomacy.alliance_leader)) {
        diplo_notify(aplayer, TALK(%s) "%s leads our alliance. Make "
                     "peace with him first.", pplayer->name, 
                     ai->diplomacy.alliance_leader->name);
        worth = -BIG_NUMBER;
        break;
      }

      /* And this guy is allied to one of our enemies. Only accept
       * ceasefire. */
      if (adip->is_allied_with_enemy
          && pclause->type != CLAUSE_CEASEFIRE) {
        diplo_notify(aplayer, TALK(%s) "First break alliance with %s, %s",
                     pplayer->name, adip->is_allied_with_enemy->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 (ai->diplomacy.acceptable_reputation > aplayer->reputation
        && ai->diplomacy.strategy != WIN_SPACE) {
      diplo_notify(aplayer, TALK(%s) "Begone scroundel, we all know that"
                   " you cannot be trusted!", pplayer->name);
      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 (adip->is_allied_with_ally) {
      worth = 0;
      break;
    }

    /* If this lucky fella got a ceasefire with da boss, then
     * let him live. */
    if (pplayer_get_diplstate(aplayer, ai->diplomacy.alliance_leader)->type
        == DS_CEASEFIRE && pclause->type == CLAUSE_CEASEFIRE) {
        diplo_notify(aplayer, TALK(%s) "%s recommended that I give you a 
ceasefire."
                     " This is your lucky day.", pplayer->name,
                     ai->diplomacy.alliance_leader->name);
        if (ai->diplomacy.target == aplayer) {
          /* Damn, we lost our target, too! Stupid boss! */
          ai->diplomacy.target = NULL;
          ai->diplomacy.timer = 0;
        }
        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 = greed(adip->love - (ai->diplomacy.love_incr 
                                    + ai->diplomacy.love_coeff));
      }
    } 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 = greed(adip->love - (ai->diplomacy.love_incr
                                    * ai->diplomacy.love_coeff));
      }
    /* Ceasefire. Always grant ceasefire when he isn't on our hitlist. */
    } else {
      worth = 0;
    }
  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;
      }
      if (aplayer->player_no == offer->original) {
        /* Let them buy back their own city cheaper. */
        worth /= 2;
      }
    } 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)
{
  struct ai_data *ai = ai_data_get(pplayer);
  struct ai_dip_intel *adip = &ai->diplomacy.player_intel[aplayer->player_no];

  switch (pclause->type) {
    case CLAUSE_ALLIANCE:
      if (adip->is_allied_with_ally) {
        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_dip_intel *adip = &ai->diplomacy.player_intel[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) + 1;

    adip->love += i;
    freelog(LOG_DIPL2, "%s's gift to %s increased love 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_dip_intel *adip = &ai->diplomacy.player_intel[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 */
  if (adip->love < 0) {
    kill_desire += kill_desire / 100 * adip->love;
  }

  /* 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)
      && !player_has_embassy(aplayer, pplayer)
      && pplayer->diplstates[aplayer->player_no].contact_turns_left
         <= 0) {
    freelog(LOG_DIPL2, "%s tries to do diplomacy to %s without contact",
            pplayer->name, aplayer->name);
    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 */
  }

  /* Time to make love. If we've been wronged, hold off that love
   * for a while. Also, cool our head each turn with love_coeff. */
  players_iterate(aplayer) {
    int a = aplayer->player_no;
    struct ai_dip_intel *adip = &ai->diplomacy.player_intel[a];

    if (pplayer == aplayer || !aplayer->is_alive) {
      continue;
    }
    adip->love -= pplayer->diplstates[a].has_reason_to_cancel;
    if (pplayers_non_attack(pplayer, aplayer)
        && !pplayer->diplstates[a].has_reason_to_cancel
        && !adip->is_allied_with_enemy
        && !adip->at_war_with_ally
        && adip->ally_patience >= 0) {
      adip->love += ai->diplomacy.love_incr;
      freelog(LOG_DEBUG, "(%s ai diplo) Increased love for %s (now %d)",
              pplayer->name, aplayer->name, adip->love);
    } else if (pplayer->diplstates[aplayer->player_no].type == DS_WAR) {
      adip->love -= ai->diplomacy.love_incr;
      freelog(LOG_DEBUG, "(%s ai diplo) Reduced love for %s (now %d) ",
              pplayer->name, aplayer->name, adip->love);
    } else if (pplayer->diplstates[a].has_reason_to_cancel) {
      /* Provoked in time of peace */
      if (adip->love > 0) {
        freelog(LOG_DIPL2, "(%s ai diplo) Love for %s halved (was %d)",
                pplayer->name, aplayer->name, adip->love);
        adip->love /= 2;
      }
      adip->love -= ai->diplomacy.love_incr;
    }
    /* Massage our numbers to keep love and its opposite on the ground. 
     * Gravitate towards zero. */
    adip->love -= (adip->love * ai->diplomacy.love_coeff / 100);
    if (adip->love > 0) {
      adip->love--;
    } else if (adip->love < 0) {
      adip->love++;
    }
  } 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;
  }

  /* 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 average distances to other players' empires. */
  players_iterate(aplayer) {
    ai->diplomacy.player_intel[aplayer->player_no].distance = 
          dist_to_empire(pplayer, aplayer);
  } players_iterate_end;

  /* Calculate our desires, and find desired war target */
  players_iterate(aplayer) {
    enum diplstate_type ds = pplayer_get_diplstate(pplayer, aplayer)->type;
    struct ai_dip_intel *adip = &ai->diplomacy.player_intel[aplayer->player_no];

    /* We don't hate ourselves, those we don't know and those we're
     * allied to or team members. Defer judgement on alliance members
     * we're not (yet) allied to to the alliance leader. Always respect
     * ceasefires the boss has signed. */
    if (aplayer == pplayer
        || !aplayer->is_alive
        || ds == DS_NO_CONTACT
        || (pplayer->team != TEAM_NONE && pplayer->team == aplayer->team)
        || ds == DS_ALLIANCE
        || (pplayer != ai->diplomacy.alliance_leader 
            && adip->is_allied_with_ally)
        || (pplayer_get_diplstate(aplayer, ai->diplomacy.alliance_leader)->type
            == DS_CEASEFIRE)) {
      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 && !adip->at_war_with_ally) {
      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;
    ai->diplomacy.timer = myrand(4) + 3; /* Don't reevaluate too often. */
    players_iterate(aplayer) {
      ai->diplomacy.player_intel[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)
{
  struct ai_dip_intel *adip = &ai->diplomacy.player_intel[target->player_no];

  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;
  if (adip->love < 0) {
    ai->diplomacy.timer += adip->love / 10;
  } else {
    adip->love = -1; /* We DON'T love our enemies! AIs are heatens! */
  }
}

/********************************************************************** 
  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 (ai->diplomacy.acceptable_reputation > aplayer->reputation
        && ai->diplomacy.player_intel[aplayer->player_no].love < 0
        && 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) {
      struct ai_dip_intel *adip =
                         &ai->diplomacy.player_intel[aplayer->player_no];
      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);
          freelog(LOG_DIPL2, "(%s ai diplo) SPACERACE: Breaking alliance to %s",
                  pplayer->name, target->name);
          handle_player_cancel_pact(pplayer, aplayer->player_no);
          if (gives_shared_vision(pplayer, aplayer)) {
            remove_shared_vision(pplayer, aplayer);
          }
          ai->diplomacy.timer = 0;
        } else if (!adip->warned_about_space) {
          /* Listen carefully, for I will say this only once... in a
           * while. */
          adip->warned_about_space = 11; /* random number */
          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);
          freelog(LOG_DIPL2, "(%s ai diplo) SPACERACE: Warned %s",
                  pplayer->name, target->name);
        }
        if (pplayer->spaceship.state == SSHIP_LAUNCHED) {
          ai->diplomacy.timer = 0; /* Force reevaluation */
        }
      }
    } 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.player_intel[target->player_no].love < 0) {
      /* We have a reason of sorts from way back. */
      diplo_notify(target, TALK(%s) "Finally I get around to you! Did "
                   "you really think you could get away with your crimes?",
                   pplayer->name);
    } else {
      /* We have no legimitate reason... So what? */
      diplo_notify(target, TALK(%s) "Peace in ... some other time",
                   pplayer->name);
    }
    ai_go_to_war(pplayer, ai, target);
  }

  /*** Allied warfare ***/

  if (!target && pplayer != ai->diplomacy.alliance_leader) {
    players_iterate(aplayer) {
      if (!aplayer->is_alive
          || pplayer == aplayer
          || pplayer_get_diplstate(pplayer, aplayer)->type == DS_NO_CONTACT) {
        continue;
      }
      if (pplayers_at_war(aplayer, ai->diplomacy.alliance_leader)
          && !pplayers_at_war(pplayer, ai->diplomacy.alliance_leader)) {
        /* It is theoretically possible that we're allied to this sucker */
        if (pplayers_allied(pplayer, aplayer)) {
          /* We side with the alliance leader */
          freelog(LOG_DIPL2, "(%s ai diplo) INTER-ALLIANCE WAR! Siding with"
                  " %s and declaring war on %s", pplayer->name,
                  ai->diplomacy.alliance_leader->name, aplayer->name);
          diplo_notify(target, TALK(%s) "I don't know how you came to be at"
                       " war with %s, but I have chosen which side to be on"
                       ", and that is not with you. Goodbye.",
                       pplayer->name, ai->diplomacy.alliance_leader->name);
          ai_go_to_war(pplayer, ai, aplayer);
        } else if (!pplayers_at_war(pplayer, aplayer)) {
          /* Ok, so the pathological case above didn't strike. Go war! */
          diplo_notify(aplayer, TALK(%s) "Sorry, my alliance has declared "
                       " war on you. You'll be dead soon.", pplayer->name);
          freelog(LOG_DIPL2, "(%s ai diplo) ALLIANCE WAR on %s (led by %s)",
                  pplayer->name, aplayer->name,
                  ai->diplomacy.alliance_leader->name);
          ai_go_to_war(pplayer, ai, aplayer);
        }
      }
    } players_iterate_end;
  }

  /*** Opportunism, Inc. Try to make peace with everyone else ***/

  players_iterate(aplayer) {
    enum diplstate_type ds = pplayer_get_diplstate(pplayer, aplayer)->type;
    struct ai_dip_intel *adip = &ai->diplomacy.player_intel[aplayer->player_no];
    struct Clause clause;

    /* Meaningless values, but rather not have them unset. */
    clause.from = pplayer;
    clause.value = 0;

    /* No peace to enemies of our allies... or pointless peace. */
    if (is_barbarian(aplayer)    /* no barbarism */
        || aplayer == pplayer    /* no self-indulgence */
        || aplayer == target     /* no mercy */
        || !aplayer->is_alive    /* no necromancy */
        || adip->at_war_with_ally) {
      continue;
    }

    /* Spam control */
    adip->spam = MAX(adip->spam - 1, 0);
    adip->asked_about_peace = MAX(adip->asked_about_peace - 1, 0);
    adip->asked_about_alliance = MAX(adip->asked_about_alliance - 1, 0);
    adip->asked_about_ceasefire = MAX(adip->asked_about_ceasefire - 1, 0);
    adip->warned_about_space = MAX(adip->warned_about_space - 1, 0);
    if (adip->spam > 0) {
      /* Don't spam */
      continue;
    }

    /* Don't try to contact anyone if we can't. */
    if (ds == DS_NO_CONTACT
        || (!player_has_embassy(pplayer, aplayer)
            && !player_has_embassy(aplayer, pplayer)
            && pplayer->diplstates[aplayer->player_no].contact_turns_left 
               <= 0)) {
      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 (!aplayer->ai.control) {
      if (!pplayers_allied(pplayer, aplayer)) {
        adip->spam = myrand(4) + 3; /* Bugger allies often. */
      } else {
        adip->spam = myrand(8) + 6; /* Others are less important. */
      }
    }
    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 */
        if (adip->love > 0) {
          adip->love /= 4; /* Betrayal! */
        } else {
          adip->love *= 4; /* Should have known... */
        }
        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 > 0 && !aplayer->ai.control)) {
        break; /* never */
      }
      ai_diplomacy_suggest(pplayer, aplayer, CLAUSE_ALLIANCE, 0);
      adip->asked_about_alliance = !aplayer->ai.control ? 13 : 0;
      if (target) {
        diplo_notify(aplayer, TALK(%s) "Greetings friend, may we suggest "
                     "a joint campaign against %s?", pplayer->name, 
target->name);
        freelog(LOG_DIPL2, "(%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_DIPL2, "(%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 > 0 && !aplayer->ai.control)) {
        break; /* never */
      }
      ai_diplomacy_suggest(pplayer, aplayer, CLAUSE_PEACE, 0);
      adip->asked_about_peace = !aplayer->ai.control ? 12 : 0;
      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 > 0 && !aplayer->ai.control)) {
        break; /* never */
      }
      ai_diplomacy_suggest(pplayer, aplayer, CLAUSE_CEASEFIRE, 0);
      adip->asked_about_ceasefire = !aplayer->ai.control ? 9 : 0;
      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]
  • [freeciv-ai] AI Diplomacy v9 (PR#2413), Per I. Mathisen <=