| 1 | {$INCLUDE switches.inc}
 | 
|---|
| 2 | unit AI;
 | 
|---|
| 3 | 
 | 
|---|
| 4 | interface
 | 
|---|
| 5 | 
 | 
|---|
| 6 | uses
 | 
|---|
| 7 | {$IFDEF DEBUG}SysUtils, {$ENDIF} // necessary for debug exceptions
 | 
|---|
| 8 |   Protocol, CustomAI, ToolAI;
 | 
|---|
| 9 | 
 | 
|---|
| 10 | type
 | 
|---|
| 11 |   UnitRole = (Roam, Defend);
 | 
|---|
| 12 | 
 | 
|---|
| 13 |   TAI = class(TToolAI)
 | 
|---|
| 14 |     constructor Create(Nation: integer); override;
 | 
|---|
| 15 | 
 | 
|---|
| 16 |   protected
 | 
|---|
| 17 |     procedure DoTurn; override;
 | 
|---|
| 18 |     procedure DoNegotiation; override;
 | 
|---|
| 19 |     function ChooseResearchAdvance: integer; override;
 | 
|---|
| 20 |     function ChooseGovernment: integer; override;
 | 
|---|
| 21 |     function WantNegotiation(Nation: integer; NegoTime: TNegoTime)
 | 
|---|
| 22 |       : boolean; override;
 | 
|---|
| 23 | 
 | 
|---|
| 24 |     procedure ProcessSettlers;
 | 
|---|
| 25 |     procedure ProcessUnit(uix: integer; Role: UnitRole);
 | 
|---|
| 26 |     procedure SetCityProduction;
 | 
|---|
| 27 |   end;
 | 
|---|
| 28 | 
 | 
|---|
| 29 | implementation
 | 
|---|
| 30 | 
 | 
|---|
| 31 | uses
 | 
|---|
| 32 |   Pile;
 | 
|---|
| 33 | 
 | 
|---|
| 34 | const
 | 
|---|
| 35 |   // fine adjustment
 | 
|---|
| 36 |   Aggressive = 40; // 0 = never attacks, 100 = attacks even with heavy losses
 | 
|---|
| 37 |   DestroyBonus = 30; // percent of building cost
 | 
|---|
| 38 | 
 | 
|---|
| 39 | constructor TAI.Create(Nation: integer);
 | 
|---|
| 40 | begin
 | 
|---|
| 41 |   inherited;
 | 
|---|
| 42 | end;
 | 
|---|
| 43 | 
 | 
|---|
| 44 | 
 | 
|---|
| 45 | // -------------------------------
 | 
|---|
| 46 | // MY TURN
 | 
|---|
| 47 | // -------------------------------
 | 
|---|
| 48 | 
 | 
|---|
| 49 | procedure TAI.DoTurn;
 | 
|---|
| 50 | var
 | 
|---|
| 51 |   uix: integer;
 | 
|---|
| 52 | begin
 | 
|---|
| 53 |   // correct tax rate if necessary
 | 
|---|
| 54 |   if RO.Money > RO.nCity * 16 then
 | 
|---|
| 55 |     ChangeRates(RO.TaxRate - 10, 0)
 | 
|---|
| 56 |   else if RO.Money < RO.nCity * 8 then
 | 
|---|
| 57 |     ChangeRates(RO.TaxRate + 10, 0);
 | 
|---|
| 58 | 
 | 
|---|
| 59 |   // better government form available?
 | 
|---|
| 60 |   if RO.Government <> gAnarchy then
 | 
|---|
| 61 |     if IsResearched(adTheRepublic) then
 | 
|---|
| 62 |     begin
 | 
|---|
| 63 |       if RO.Government <> gRepublic then
 | 
|---|
| 64 |         Revolution
 | 
|---|
| 65 |     end
 | 
|---|
| 66 |     else if IsResearched(adMonarchy) then
 | 
|---|
| 67 |     begin
 | 
|---|
| 68 |       if RO.Government <> gMonarchy then
 | 
|---|
| 69 |         Revolution
 | 
|---|
| 70 |     end;
 | 
|---|
| 71 | 
 | 
|---|
| 72 |   // do combat
 | 
|---|
| 73 |   for uix := 0 to RO.nUn - 1 do
 | 
|---|
| 74 |     if (MyUnit[uix].Loc >= 0) and not(MyModel[MyUnit[uix].mix].Kind
 | 
|---|
| 75 |       in [mkSettler, mkSlaves]) then
 | 
|---|
| 76 |       ProcessUnit(uix, Roam);
 | 
|---|
| 77 | 
 | 
|---|
| 78 |   ProcessSettlers;
 | 
|---|
| 79 | 
 | 
|---|
| 80 |   // do discover/patrol
 | 
|---|
| 81 | 
 | 
|---|
| 82 |   OptimizeCityTiles;
 | 
|---|
| 83 |   SetCityProduction;
 | 
|---|
| 84 | end;
 | 
|---|
| 85 | 
 | 
|---|
| 86 | // ProcessSettlers: move settlers, do terrain improvement, found cities
 | 
|---|
| 87 | procedure TAI.ProcessSettlers;
 | 
|---|
| 88 | var
 | 
|---|
| 89 |   uix, cix, ecix, Loc, RadiusLoc, TestScore, BestNearCityScore, TerrType,
 | 
|---|
| 90 |     Special, V21: integer;
 | 
|---|
| 91 |   Radius: TVicinity21Loc;
 | 
|---|
| 92 |   ResourceScore, CityScore: array [0 .. lxmax * lymax - 1] of integer;
 | 
|---|
| 93 | 
 | 
|---|
| 94 |   procedure ReserveCityRadius(Loc: integer);
 | 
|---|
| 95 |   var
 | 
|---|
| 96 |     V21, RadiusLoc: integer;
 | 
|---|
| 97 |     Radius: TVicinity21Loc;
 | 
|---|
| 98 |   begin
 | 
|---|
| 99 |     V21_to_Loc(Loc, Radius);
 | 
|---|
| 100 |     for V21 := 1 to 26 do
 | 
|---|
| 101 |     begin
 | 
|---|
| 102 |       RadiusLoc := Radius[V21];
 | 
|---|
| 103 |       if (RadiusLoc >= 0) and (RadiusLoc < MapSize) then
 | 
|---|
| 104 |         ResourceScore[RadiusLoc] := 0
 | 
|---|
| 105 |     end
 | 
|---|
| 106 |   end;
 | 
|---|
| 107 | 
 | 
|---|
| 108 | begin
 | 
|---|
| 109 |   JobAssignment_Initialize;
 | 
|---|
| 110 | 
 | 
|---|
| 111 |   // rate resources of all tiles
 | 
|---|
| 112 |   fillchar(ResourceScore, MapSize * sizeof(integer), 0);
 | 
|---|
| 113 |   for Loc := 0 to MapSize - 1 do
 | 
|---|
| 114 |     if (Map[Loc] and fRare) = 0 then
 | 
|---|
| 115 |       if (Map[Loc] and fTerrain) = fGrass then
 | 
|---|
| 116 |         if (Map[Loc] and fSpecial) <> 0 then
 | 
|---|
| 117 |           ResourceScore[Loc] := 3 // plains, 3 points
 | 
|---|
| 118 |         else
 | 
|---|
| 119 |           ResourceScore[Loc] := 2 // grassland, 2 points
 | 
|---|
| 120 |       else if (Map[Loc] and fSpecial) <> 0 then
 | 
|---|
| 121 |         ResourceScore[Loc] := 4; // special resource, 4 points
 | 
|---|
| 122 |   for cix := 0 to RO.nCity - 1 do
 | 
|---|
| 123 |     if MyCity[cix].Loc >= 0 then
 | 
|---|
| 124 |       ReserveCityRadius(MyCity[cix].Loc); // these resources already have a city
 | 
|---|
| 125 |   for uix := 0 to RO.nUn - 1 do
 | 
|---|
| 126 |     if (MyUnit[uix].Loc >= 0) and (MyUnit[uix].Job = jCity) then
 | 
|---|
| 127 |       ReserveCityRadius(MyUnit[uix].Loc);
 | 
|---|
| 128 |   // these resources almost already have a city
 | 
|---|
| 129 |   for ecix := 0 to RO.nEnemyCity - 1 do
 | 
|---|
| 130 |     if RO.EnemyCity[ecix].Loc >= 0 then
 | 
|---|
| 131 |       ReserveCityRadius(RO.EnemyCity[ecix].Loc);
 | 
|---|
| 132 |   // these resources already have an enemy city
 | 
|---|
| 133 | 
 | 
|---|
| 134 |   // rate possible new cities
 | 
|---|
| 135 |   fillchar(CityScore, MapSize * sizeof(integer), 0);
 | 
|---|
| 136 |   for Loc := 0 to MapSize - 1 do
 | 
|---|
| 137 |     if ((Map[Loc] and fTerrain) = fGrass) and ((Map[Loc] and fRare) = 0) and
 | 
|---|
| 138 |       ((RO.Territory[Loc] < 0) or (RO.Territory[Loc] = me)) then
 | 
|---|
| 139 |     // don't consider founding cities in foreign nation territory
 | 
|---|
| 140 |     begin
 | 
|---|
| 141 |       TestScore := 0;
 | 
|---|
| 142 |       BestNearCityScore := 0;
 | 
|---|
| 143 |       V21_to_Loc(Loc, Radius);
 | 
|---|
| 144 |       for V21 := 1 to 26 do
 | 
|---|
| 145 |       begin // sum resource scores in potential city radius
 | 
|---|
| 146 |         RadiusLoc := Radius[V21];
 | 
|---|
| 147 |         if (RadiusLoc >= 0) and (RadiusLoc < MapSize) then
 | 
|---|
| 148 |         begin
 | 
|---|
| 149 |           TestScore := TestScore + ResourceScore[RadiusLoc];
 | 
|---|
| 150 |           if CityScore[RadiusLoc] > BestNearCityScore then
 | 
|---|
| 151 |             BestNearCityScore := CityScore[RadiusLoc]
 | 
|---|
| 152 |         end
 | 
|---|
| 153 |       end;
 | 
|---|
| 154 |       if TestScore >= 10 then // city is worth founding
 | 
|---|
| 155 |       begin
 | 
|---|
| 156 |         TestScore := TestScore shl 8 + ((Loc xor me) * 4567) mod 251;
 | 
|---|
| 157 |         // some unexactness, random but always the same for this tile
 | 
|---|
| 158 |         if TestScore > BestNearCityScore then
 | 
|---|
| 159 |         begin // better than all other sites in radius
 | 
|---|
| 160 |           if BestNearCityScore > 0 then // found no other cities in radius
 | 
|---|
| 161 |           begin
 | 
|---|
| 162 |             for V21 := 1 to 26 do
 | 
|---|
| 163 |             begin
 | 
|---|
| 164 |               RadiusLoc := Radius[V21];
 | 
|---|
| 165 |               if (RadiusLoc >= 0) and (RadiusLoc < MapSize) then
 | 
|---|
| 166 |                 CityScore[RadiusLoc] := 0;
 | 
|---|
| 167 |             end;
 | 
|---|
| 168 |           end;
 | 
|---|
| 169 |           CityScore[Loc] := TestScore
 | 
|---|
| 170 |         end;
 | 
|---|
| 171 |       end
 | 
|---|
| 172 |     end;
 | 
|---|
| 173 |   for Loc := 0 to MapSize - 1 do
 | 
|---|
| 174 |     if CityScore[Loc] > 0 then
 | 
|---|
| 175 |       JobAssignment_AddJob(Loc, jCity, 10);
 | 
|---|
| 176 | 
 | 
|---|
| 177 |   // improve terrain
 | 
|---|
| 178 |   for cix := 0 to RO.nCity - 1 do
 | 
|---|
| 179 |     with MyCity[cix] do
 | 
|---|
| 180 |       if Loc >= 0 then
 | 
|---|
| 181 |       begin
 | 
|---|
| 182 |         V21_to_Loc(Loc, Radius);
 | 
|---|
| 183 |         for V21 := 1 to 26 do
 | 
|---|
| 184 |           if (Tiles and (1 shl V21) and not(1 shl CityOwnTile)) <> 0 then
 | 
|---|
| 185 |           begin // tile is exploited, but not the city own tile -- check if improvable
 | 
|---|
| 186 |             RadiusLoc := Radius[V21];
 | 
|---|
| 187 |             assert((RadiusLoc >= 0) and (RadiusLoc < MapSize));
 | 
|---|
| 188 |             if (RadiusLoc >= 0) and (RadiusLoc < MapSize) then
 | 
|---|
| 189 |             begin
 | 
|---|
| 190 |               TerrType := Map[RadiusLoc] and fTerrain;
 | 
|---|
| 191 |               Special := Map[RadiusLoc] shr 5 and 3;
 | 
|---|
| 192 |               if TerrType >= fGrass then // can't improve water tiles
 | 
|---|
| 193 |                 if (Terrain[TerrType].IrrEff > 0) // terrain is irrigatable
 | 
|---|
| 194 |                   and not((RO.Government = gDespotism) and
 | 
|---|
| 195 |                   (Terrain[TerrType].FoodRes[Special] >= 3))
 | 
|---|
| 196 |                 // improvement makes no sense when limit is depotism
 | 
|---|
| 197 |                   and ((Map[RadiusLoc] and fTerImp) = 0) then
 | 
|---|
| 198 |                 // no terrain improvement yet
 | 
|---|
| 199 |                   JobAssignment_AddJob(RadiusLoc, jIrr, 50) // irrigate!
 | 
|---|
| 200 |                 else if (Terrain[TerrType].MoveCost = 1) // plain terrain
 | 
|---|
| 201 |                   and ((Map[RadiusLoc] and (fRoad or fRR or fRiver)) = 0) then
 | 
|---|
| 202 |                 // no road or railroad yet, no river
 | 
|---|
| 203 |                   JobAssignment_AddJob(RadiusLoc, jRoad, 40);
 | 
|---|
| 204 |               // build road (The Wheel trade benefit)
 | 
|---|
| 205 |             end
 | 
|---|
| 206 |           end;
 | 
|---|
| 207 |       end;
 | 
|---|
| 208 | 
 | 
|---|
| 209 |   // choose all settlers to work
 | 
|---|
| 210 |   for uix := 0 to RO.nUn - 1 do
 | 
|---|
| 211 |     if (MyUnit[uix].Loc >= 0) and
 | 
|---|
| 212 |       (MyModel[MyUnit[uix].mix].Kind in [mkSettler, mkSlaves]) then
 | 
|---|
| 213 |       JobAssignment_AddUnit(uix);
 | 
|---|
| 214 | 
 | 
|---|
| 215 |   JobAssignment_Go;
 | 
|---|
| 216 | end; // ProcessSettlers
 | 
|---|
| 217 | 
 | 
|---|
| 218 | // ProcessUnit: execute attack, capture, discover or patrol task according to unit role
 | 
|---|
| 219 | procedure TAI.ProcessUnit(uix: integer; Role: UnitRole);
 | 
|---|
| 220 | const
 | 
|---|
| 221 |   DistanceScore = 4;
 | 
|---|
| 222 | var
 | 
|---|
| 223 |   BestScore, BestCount, BestLoc, TerrType, TestLoc, NextLoc, TestDistance, Tile,
 | 
|---|
| 224 |     V8, TestScore, euix, MyDamage, EnemyDamage, TerrOwner, StepSize, OldLoc,
 | 
|---|
| 225 |     AttackForecast, MoveResult, AttackResult: integer;
 | 
|---|
| 226 |   Exhausted: boolean;
 | 
|---|
| 227 |   TestTask, BestTask: (utNone, utAttack, utCapture, utDiscover, utPatrol,
 | 
|---|
| 228 |     utGoHome);
 | 
|---|
| 229 |   Adjacent: TVicinity8Loc;
 | 
|---|
| 230 |   AdjacentUnknown: array [0 .. lxmax * lymax - 1] of integer;
 | 
|---|
| 231 | 
 | 
|---|
| 232 | begin
 | 
|---|
| 233 |   Pile.Create(MapSize);
 | 
|---|
| 234 |   with MyUnit[uix] do
 | 
|---|
| 235 |     repeat
 | 
|---|
| 236 |       BestScore := -999999;
 | 
|---|
| 237 |       BestTask := utNone;
 | 
|---|
| 238 |       fillchar(AdjacentUnknown, sizeof(AdjacentUnknown), $FF);
 | 
|---|
| 239 |       // -1, indicates tiles not checked yet
 | 
|---|
| 240 |       Pile.Empty;
 | 
|---|
| 241 |       Pile.Put(Loc, 0); // start search for something to do at current location
 | 
|---|
| 242 |       while Pile.Get(TestLoc, TestDistance) do
 | 
|---|
| 243 |       begin
 | 
|---|
| 244 |         TestScore := 0;
 | 
|---|
| 245 |         Tile := Map[TestLoc];
 | 
|---|
| 246 |         AdjacentUnknown[TestLoc] := 0;
 | 
|---|
| 247 | 
 | 
|---|
| 248 |         if ((Tile and fUnit) <> 0) and ((Tile and fOwned) = 0) then
 | 
|---|
| 249 |         begin // enemy unit
 | 
|---|
| 250 |           Unit_FindEnemyDefender(TestLoc, euix);
 | 
|---|
| 251 |           if RO.Treaty[RO.EnemyUn[euix].Owner] < trPeace then
 | 
|---|
| 252 |           begin // unfriendly unit -- check attack
 | 
|---|
| 253 |             if Unit_AttackForecast(uix, TestLoc, 100, AttackForecast) then
 | 
|---|
| 254 |             begin // attack possible, but advantageous?
 | 
|---|
| 255 |               if AttackForecast > 0 then
 | 
|---|
| 256 |               begin // enemy unit would be destroyed
 | 
|---|
| 257 |                 MyDamage := Health - AttackForecast;
 | 
|---|
| 258 |                 EnemyDamage := RO.EnemyUn[euix].Health + DestroyBonus;
 | 
|---|
| 259 |               end
 | 
|---|
| 260 |               else // own unit would be destroyed
 | 
|---|
| 261 |               begin
 | 
|---|
| 262 |                 MyDamage := Health + DestroyBonus;
 | 
|---|
| 263 |                 EnemyDamage := RO.EnemyUn[euix].Health + AttackForecast;
 | 
|---|
| 264 |               end;
 | 
|---|
| 265 |               TestScore := Aggressive * 2 *
 | 
|---|
| 266 |                 (EnemyDamage * RO.EnemyModel[RO.EnemyUn[euix].emix].Cost)
 | 
|---|
| 267 |                 div (MyDamage * MyModel[mix].Cost);
 | 
|---|
| 268 |               if TestScore <= 100 then
 | 
|---|
| 269 |                 TestScore := 0 // own losses exceed enemy losses, no good
 | 
|---|
| 270 |               else
 | 
|---|
| 271 |               begin
 | 
|---|
| 272 |                 TestScore := (TestScore - 100) div 10 + 30;
 | 
|---|
| 273 |                 TestTask := utAttack
 | 
|---|
| 274 |               end
 | 
|---|
| 275 |             end
 | 
|---|
| 276 |           end
 | 
|---|
| 277 |         end // enemy unit
 | 
|---|
| 278 | 
 | 
|---|
| 279 |         else if ((Tile and fCity) <> 0) and ((Tile and fOwned) = 0) then
 | 
|---|
| 280 |         begin // enemy city, empty or unobserved
 | 
|---|
| 281 |           if (MyModel[mix].Domain = dGround)
 | 
|---|
| 282 |           // ships of this AI have no long-range guns, so don't try to attack cities with them
 | 
|---|
| 283 |             and ((RO.Territory[TestLoc] < 0)
 | 
|---|
| 284 |             // happens only for unobserved cities of extinct tribes, new owner unknown
 | 
|---|
| 285 |             or (RO.Treaty[RO.Territory[TestLoc]] < trPeace)) then
 | 
|---|
| 286 |           begin // unfriendly city -- check attack/capture
 | 
|---|
| 287 |             if (Tile and fObserved) <> 0 then
 | 
|---|
| 288 |             begin // observed and no unit present -- city is undefended, capture!
 | 
|---|
| 289 |               TestScore := 40;
 | 
|---|
| 290 |               TestTask := utCapture
 | 
|---|
| 291 |             end
 | 
|---|
| 292 |             else if Role = Roam then
 | 
|---|
| 293 |             begin // unobserved city, possibly defended -- go for attack
 | 
|---|
| 294 |               TestScore := 30;
 | 
|---|
| 295 |               TestTask := utPatrol
 | 
|---|
| 296 |             end
 | 
|---|
| 297 |           end
 | 
|---|
| 298 |         end // enemy city, empty or unobserved
 | 
|---|
| 299 | 
 | 
|---|
| 300 |         else
 | 
|---|
| 301 |         begin // no enemy city or unit here
 | 
|---|
| 302 |           // add surrounding tiles to queue, but only if there's a chance to beat BestScore
 | 
|---|
| 303 |           if 50 - DistanceScore * (TestDistance + 1) >= BestScore then
 | 
|---|
| 304 |           // assume a score of 50 is the best achievable
 | 
|---|
| 305 |           begin
 | 
|---|
| 306 |             V8_to_Loc(TestLoc, Adjacent);
 | 
|---|
| 307 |             for V8 := 0 to 7 do
 | 
|---|
| 308 |             begin
 | 
|---|
| 309 |               NextLoc := Adjacent[V8];
 | 
|---|
| 310 |               if (NextLoc >= 0) and (NextLoc < MapSize) and
 | 
|---|
| 311 |                 (AdjacentUnknown[NextLoc] < 0) then // tile not checked yet
 | 
|---|
| 312 |               begin
 | 
|---|
| 313 |                 TerrType := Map[NextLoc] and fTerrain;
 | 
|---|
| 314 |                 if TerrType = fUNKNOWN then
 | 
|---|
| 315 |                   inc(AdjacentUnknown[TestLoc])
 | 
|---|
| 316 |                 else
 | 
|---|
| 317 |                 begin
 | 
|---|
| 318 |                   case MyModel[mix].Domain of
 | 
|---|
| 319 |                     dGround:
 | 
|---|
| 320 |                       begin
 | 
|---|
| 321 |                         TerrOwner := RO.Territory[NextLoc];
 | 
|---|
| 322 |                         if (TerrType >= fGrass) and (TerrType <> fArctic)
 | 
|---|
| 323 |                         // terrain can be walked
 | 
|---|
| 324 |                           and ((TerrOwner < 0) or (TerrOwner = me) or
 | 
|---|
| 325 |                           (RO.Treaty[TerrOwner] < trPeace))
 | 
|---|
| 326 |                         // no peace treaty violated
 | 
|---|
| 327 |                           and (((Map[NextLoc] and (fUnit or fCity)) <> 0) or
 | 
|---|
| 328 |                           (Map[TestLoc] and Map[NextLoc] and fInEnemyZoC = 0))
 | 
|---|
| 329 |                         then // no ZoC violated
 | 
|---|
| 330 |                         begin // yes, consider walking this tile
 | 
|---|
| 331 |                           if TerrType = fMountains then
 | 
|---|
| 332 |                             StepSize := 2 // mountains cause delay
 | 
|---|
| 333 |                           else
 | 
|---|
| 334 |                             StepSize := 1
 | 
|---|
| 335 |                         end
 | 
|---|
| 336 |                         else
 | 
|---|
| 337 |                           StepSize := 0 // no, don't walk here
 | 
|---|
| 338 |                       end;
 | 
|---|
| 339 |                     dSea:
 | 
|---|
| 340 |                       if TerrType = fShore then
 | 
|---|
| 341 |                       // ships of this AI can only move along shore
 | 
|---|
| 342 |                         StepSize := 1
 | 
|---|
| 343 |                       else
 | 
|---|
| 344 |                         StepSize := 0;
 | 
|---|
| 345 |                     dAir:
 | 
|---|
| 346 |                       StepSize := 1;
 | 
|---|
| 347 |                   end;
 | 
|---|
| 348 |                   if StepSize > 0 then
 | 
|---|
| 349 |                     Pile.Put(NextLoc, TestDistance + StepSize)
 | 
|---|
| 350 |                 end
 | 
|---|
| 351 |               end;
 | 
|---|
| 352 |             end;
 | 
|---|
| 353 |           end;
 | 
|---|
| 354 |           if Role = Defend then
 | 
|---|
| 355 |             TestScore := 0 // don't discover/patrol
 | 
|---|
| 356 |           else if AdjacentUnknown[TestLoc] > 0 then
 | 
|---|
| 357 |           begin
 | 
|---|
| 358 |             TestScore := 20 + AdjacentUnknown[TestLoc];
 | 
|---|
| 359 |             TestTask := utDiscover
 | 
|---|
| 360 |           end
 | 
|---|
| 361 |           else
 | 
|---|
| 362 |           begin
 | 
|---|
| 363 |             TestScore := (RO.Turn - RO.MapObservedLast[TestLoc]) div 10;
 | 
|---|
| 364 |             TestTask := utPatrol
 | 
|---|
| 365 |           end
 | 
|---|
| 366 |         end; // no enemy city or unit here
 | 
|---|
| 367 | 
 | 
|---|
| 368 |         if TestScore > 0 then
 | 
|---|
| 369 |         begin
 | 
|---|
| 370 |           TestScore := TestScore - DistanceScore * TestDistance;
 | 
|---|
| 371 |           if TestScore > BestScore then
 | 
|---|
| 372 |             BestCount := 0;
 | 
|---|
| 373 |           if TestScore >= BestScore then
 | 
|---|
| 374 |           begin
 | 
|---|
| 375 |             inc(BestCount);
 | 
|---|
| 376 |             if random(BestCount) = 0 then
 | 
|---|
| 377 |             begin
 | 
|---|
| 378 |               BestScore := TestScore;
 | 
|---|
| 379 |               BestLoc := TestLoc;
 | 
|---|
| 380 |               BestTask := TestTask;
 | 
|---|
| 381 |             end
 | 
|---|
| 382 |           end;
 | 
|---|
| 383 |         end
 | 
|---|
| 384 |       end;
 | 
|---|
| 385 | 
 | 
|---|
| 386 |       if (BestTask = utNone) and ((Map[Loc] and fCity) = 0) then
 | 
|---|
| 387 |       begin // nothing to do, move home
 | 
|---|
| 388 |         if Home >= 0 then
 | 
|---|
| 389 |           BestLoc := MyCity[Home].Loc
 | 
|---|
| 390 |         else
 | 
|---|
| 391 |           BestLoc := maNextCity;
 | 
|---|
| 392 |         BestTask := utGoHome;
 | 
|---|
| 393 |       end;
 | 
|---|
| 394 |       if BestTask <> utNone then
 | 
|---|
| 395 |       begin // attack/capture/discover/patrol task found, execute it
 | 
|---|
| 396 |         OldLoc := Loc;
 | 
|---|
| 397 |         MoveResult := Unit_Move(uix, BestLoc);
 | 
|---|
| 398 |         Exhausted := (Loc = OldLoc) or
 | 
|---|
| 399 |           ((MoveResult and (rMoreTurns or rUnitRemoved)) <> 0);
 | 
|---|
| 400 |         if (BestTask = utAttack) and ((MoveResult and rLocationReached) <> 0)
 | 
|---|
| 401 |         then
 | 
|---|
| 402 |           if Movement < 100 then
 | 
|---|
| 403 |             Exhausted := true
 | 
|---|
| 404 |           else
 | 
|---|
| 405 |           begin
 | 
|---|
| 406 |             AttackResult := Unit_Attack(uix, BestLoc);
 | 
|---|
| 407 |             Exhausted := ((AttackResult and rExecuted) = 0) or
 | 
|---|
| 408 |               ((AttackResult and rUnitRemoved) <> 0);
 | 
|---|
| 409 |           end;
 | 
|---|
| 410 |         if not Exhausted then
 | 
|---|
| 411 |           Exhausted := (Movement < 100) and
 | 
|---|
| 412 |             ((Map[Loc] and (fRoad or fRR or fRiver or fCity)) = 0);
 | 
|---|
| 413 |         // no road, too few movement points for further movement
 | 
|---|
| 414 |       end
 | 
|---|
| 415 |       else
 | 
|---|
| 416 |         Exhausted := true;
 | 
|---|
| 417 |     until Exhausted;
 | 
|---|
| 418 |   Pile.Free;
 | 
|---|
| 419 | end; // ProcessUnit
 | 
|---|
| 420 | 
 | 
|---|
| 421 | // SetCityProduction: choose production of each city
 | 
|---|
| 422 | procedure TAI.SetCityProduction;
 | 
|---|
| 423 | var
 | 
|---|
| 424 |   cix, mix, mixSettler, mixShip, mixArmy, V8, NewImprovement, count, wix,
 | 
|---|
| 425 |     AdjacentLoc: integer;
 | 
|---|
| 426 |   IsPort: boolean;
 | 
|---|
| 427 |   Adjacent: TVicinity8Loc;
 | 
|---|
| 428 |   Report: TCityReport;
 | 
|---|
| 429 | 
 | 
|---|
| 430 |   procedure TryBuild(Improvement: integer);
 | 
|---|
| 431 |   begin
 | 
|---|
| 432 |     if (NewImprovement < 0) // already improvement of higher priority found
 | 
|---|
| 433 |       and (MyCity[cix].Built[Improvement] = 0) // not built yet
 | 
|---|
| 434 |       and City_Improvable(cix, Improvement) then
 | 
|---|
| 435 |       NewImprovement := Improvement;
 | 
|---|
| 436 |   end;
 | 
|---|
| 437 | 
 | 
|---|
| 438 | begin
 | 
|---|
| 439 |   // only produce newest models
 | 
|---|
| 440 |   mixSettler := -1;
 | 
|---|
| 441 |   mixArmy := -1;
 | 
|---|
| 442 |   mixShip := -1;
 | 
|---|
| 443 |   for mix := 0 to RO.nModel - 1 do
 | 
|---|
| 444 |     with MyModel[mix] do
 | 
|---|
| 445 |       if Kind = mkSettler then
 | 
|---|
| 446 |         mixSettler := mix
 | 
|---|
| 447 |       else if (Domain = dGround) and (Kind < mkSpecial_TownGuard) then
 | 
|---|
| 448 |         mixArmy := mix
 | 
|---|
| 449 |       else if Domain = dSea then
 | 
|---|
| 450 |         mixShip := mix;
 | 
|---|
| 451 | 
 | 
|---|
| 452 |   for cix := 0 to RO.nCity - 1 do
 | 
|---|
| 453 |     with MyCity[cix] do
 | 
|---|
| 454 |       if (RO.Turn = 0) or ((Flags and chProduction) <> 0)
 | 
|---|
| 455 |       // city production complete
 | 
|---|
| 456 |         or not City_HasProject(cix) then
 | 
|---|
| 457 |       begin // check production
 | 
|---|
| 458 |         IsPort := false;
 | 
|---|
| 459 |         V8_to_Loc(Loc, Adjacent);
 | 
|---|
| 460 |         for V8 := 0 to 7 do
 | 
|---|
| 461 |         begin
 | 
|---|
| 462 |           AdjacentLoc := Adjacent[V8];
 | 
|---|
| 463 |           if (AdjacentLoc >= 0) and (AdjacentLoc < MapSize) and
 | 
|---|
| 464 |             ((Map[AdjacentLoc] and fTerrain) = fShore) then
 | 
|---|
| 465 |             IsPort := true; // shore tile at adjacent location -- city is port!
 | 
|---|
| 466 |         end;
 | 
|---|
| 467 |         City_GetReport(cix, Report);
 | 
|---|
| 468 | 
 | 
|---|
| 469 |         if (Report.Support = 0) or (SupportFree[RO.Government] < 2) and
 | 
|---|
| 470 |           (Report.Support < Report.ProdRep div 2) then
 | 
|---|
| 471 |         begin // enough material to support more units
 | 
|---|
| 472 |           if (RO.Turn > 4) and
 | 
|---|
| 473 |             ((Report.Eaten - Size * 2) div SettlerFood[RO.Government] <
 | 
|---|
| 474 |             Size div 4) then
 | 
|---|
| 475 |             // less than 1 settler per 4 citizens -- produce more!
 | 
|---|
| 476 |             City_StartUnitProduction(cix, mixSettler)
 | 
|---|
| 477 |           else if IsPort and (mixShip >= 0) and (random(2) = 0) then
 | 
|---|
| 478 |             City_StartUnitProduction(cix, mixShip)
 | 
|---|
| 479 |           else
 | 
|---|
| 480 |             City_StartUnitProduction(cix, mixArmy)
 | 
|---|
| 481 |         end
 | 
|---|
| 482 |         else
 | 
|---|
| 483 |         begin // check for building a city improvement
 | 
|---|
| 484 |           NewImprovement := -1;
 | 
|---|
| 485 |           if Built[imPalace] + Built[imCourt] + Built[imTownHall] = 0 then
 | 
|---|
| 486 |           begin
 | 
|---|
| 487 |             TryBuild(imCourt);
 | 
|---|
| 488 |             TryBuild(imTownHall);
 | 
|---|
| 489 |           end;
 | 
|---|
| 490 |           if Report.Trade - Report.Corruption >= 11 then
 | 
|---|
| 491 |             TryBuild(imLibrary);
 | 
|---|
| 492 |           if Report.Trade - Report.Corruption >= 11 then
 | 
|---|
| 493 |             TryBuild(imMarket);
 | 
|---|
| 494 |           if Size >= 9 then
 | 
|---|
| 495 |             TryBuild(imHighways);
 | 
|---|
| 496 |           if (RO.Government <> gDespotism) and (Size >= 4) then
 | 
|---|
| 497 |             TryBuild(imTemple);
 | 
|---|
| 498 |           if (RO.Government <> gDespotism) and (Size >= 6) then
 | 
|---|
| 499 |             TryBuild(imTheater);
 | 
|---|
| 500 |           if (RO.Government <> gDespotism) and (Size >= 8) then
 | 
|---|
| 501 |             TryBuild(imAqueduct);
 | 
|---|
| 502 |           if (Report.ProdRep >= 4) or (RO.nCity = 1) then
 | 
|---|
| 503 |             TryBuild(imBarracks);
 | 
|---|
| 504 |           TryBuild(imWalls);
 | 
|---|
| 505 |           if IsPort then
 | 
|---|
| 506 |             TryBuild(imCoastalFort);
 | 
|---|
| 507 |           if NewImprovement < 0 then
 | 
|---|
| 508 |           begin // nothing to produce -- check for building a wonder
 | 
|---|
| 509 |             count := 0;
 | 
|---|
| 510 |             for wix := 0 to nImp - 1 do
 | 
|---|
| 511 |               if (Imp[wix].Kind = ikWonder) and (RO.Wonder[wix].CityID = -1)
 | 
|---|
| 512 |               // not built yet
 | 
|---|
| 513 |                 and ((Report.ProdRep - Report.Support) * 40 >= Imp[wix].Cost)
 | 
|---|
| 514 |               // takes less than 40 turns to produce
 | 
|---|
| 515 |                 and City_Improvable(cix, wix) then
 | 
|---|
| 516 |               begin
 | 
|---|
| 517 |                 inc(count);
 | 
|---|
| 518 |                 if random(count) = 0 then
 | 
|---|
| 519 |                   NewImprovement := wix // yes, build this wonder!
 | 
|---|
| 520 |               end;
 | 
|---|
| 521 |           end;
 | 
|---|
| 522 |           if NewImprovement >= 0 then
 | 
|---|
| 523 |             City_StartImprovement(cix, NewImprovement)
 | 
|---|
| 524 |           else if City_HasProject(cix) then
 | 
|---|
| 525 |             City_StopProduction(cix); // nothing to produce
 | 
|---|
| 526 |         end
 | 
|---|
| 527 |       end // check production
 | 
|---|
| 528 | end; // SetCityProduction
 | 
|---|
| 529 | 
 | 
|---|
| 530 | function TAI.ChooseResearchAdvance: integer;
 | 
|---|
| 531 | var
 | 
|---|
| 532 |   mix: integer;
 | 
|---|
| 533 | begin
 | 
|---|
| 534 |   if not IsResearched(adWheel) then
 | 
|---|
| 535 |   begin
 | 
|---|
| 536 |     result := adWheel;
 | 
|---|
| 537 |     exit
 | 
|---|
| 538 |   end // research the wheel first
 | 
|---|
| 539 |   else if not IsResearched(adWarriorCode) then
 | 
|---|
| 540 |   begin
 | 
|---|
| 541 |     result := adWarriorCode;
 | 
|---|
| 542 |     exit
 | 
|---|
| 543 |   end // research warrior code first
 | 
|---|
| 544 |   else if not IsResearched(adHorsebackRiding) then
 | 
|---|
| 545 |   begin
 | 
|---|
| 546 |     result := adHorsebackRiding;
 | 
|---|
| 547 |     exit
 | 
|---|
| 548 |   end; // research horseback riding first
 | 
|---|
| 549 | 
 | 
|---|
| 550 |   result := -1; // random advance
 | 
|---|
| 551 |   if random(10) = 0 then
 | 
|---|
| 552 |   begin // check military research
 | 
|---|
| 553 |     result := adMilitary;
 | 
|---|
| 554 |     if IsResearched(adMapMaking) and (random(2) = 0) then
 | 
|---|
| 555 |     begin // try to develop new ship
 | 
|---|
| 556 |       PrepareNewModel(dSea);
 | 
|---|
| 557 |       SetNewModelFeature(mcDefense, 3);
 | 
|---|
| 558 |       SetNewModelFeature(mcOffense, RO.DevModel.MaxWeight - 3);
 | 
|---|
| 559 |     end
 | 
|---|
| 560 |     else
 | 
|---|
| 561 |     begin // try to develop new ground unit
 | 
|---|
| 562 |       PrepareNewModel(dGround);
 | 
|---|
| 563 |       SetNewModelFeature(mcDefense, 1);
 | 
|---|
| 564 |       SetNewModelFeature(mcOffense, RO.DevModel.MaxWeight - 4);
 | 
|---|
| 565 |       SetNewModelFeature(mcMob, 2);
 | 
|---|
| 566 |     end;
 | 
|---|
| 567 | 
 | 
|---|
| 568 |     // don't develop model twice
 | 
|---|
| 569 |     for mix := 0 to RO.nModel - 1 do
 | 
|---|
| 570 |       if (RO.DevModel.Domain = MyModel[mix].Domain) and
 | 
|---|
| 571 |         (RO.DevModel.Attack = MyModel[mix].Attack) and
 | 
|---|
| 572 |         (RO.DevModel.Defense = MyModel[mix].Defense) then
 | 
|---|
| 573 |         result := -1; // already have this model
 | 
|---|
| 574 |   end;
 | 
|---|
| 575 | end; // ChooseResearchAdvance
 | 
|---|
| 576 | 
 | 
|---|
| 577 | function TAI.ChooseGovernment: integer;
 | 
|---|
| 578 | begin
 | 
|---|
| 579 |   if IsResearched(adTheRepublic) then
 | 
|---|
| 580 |     result := gRepublic
 | 
|---|
| 581 |   else if IsResearched(adMonarchy) then
 | 
|---|
| 582 |     result := gMonarchy
 | 
|---|
| 583 |   else
 | 
|---|
| 584 |     result := gDespotism
 | 
|---|
| 585 | end;
 | 
|---|
| 586 | 
 | 
|---|
| 587 | 
 | 
|---|
| 588 | // -------------------------------
 | 
|---|
| 589 | // DIPLOMACY
 | 
|---|
| 590 | // -------------------------------
 | 
|---|
| 591 | 
 | 
|---|
| 592 | function TAI.WantNegotiation(Nation: integer; NegoTime: TNegoTime): boolean;
 | 
|---|
| 593 | begin
 | 
|---|
| 594 |   result := (NegoTime = EnemyCalled) // always accept contact
 | 
|---|
| 595 |     or (NegoTime = EndOfTurn) and (RO.Turn mod 20 = Nation + me)
 | 
|---|
| 596 |   // ask for contact only once in 20 turns
 | 
|---|
| 597 | end;
 | 
|---|
| 598 | 
 | 
|---|
| 599 | procedure TAI.DoNegotiation;
 | 
|---|
| 600 | begin
 | 
|---|
| 601 |   if (RO.Treaty[Opponent] < trPeace) and Odd(me + Opponent) then
 | 
|---|
| 602 |   // make peace with some random nations
 | 
|---|
| 603 |     if (OppoAction = scDipOffer) and (OppoOffer.nCost = 0) and
 | 
|---|
| 604 |       (OppoOffer.nDeliver = 1) and (OppoOffer.Price[0] = opTreaty + trPeace)
 | 
|---|
| 605 |     then
 | 
|---|
| 606 |       MyAction := scDipAccept // accept peace
 | 
|---|
| 607 |     else if OppoAction = scDipStart then
 | 
|---|
| 608 |     begin
 | 
|---|
| 609 |       MyOffer.nCost := 0;
 | 
|---|
| 610 |       MyOffer.nDeliver := 1;
 | 
|---|
| 611 |       MyOffer.Price[0] := opTreaty + trPeace;
 | 
|---|
| 612 |       // offer peace in exchange of nothing
 | 
|---|
| 613 |       MyAction := scDipOffer;
 | 
|---|
| 614 |     end
 | 
|---|
| 615 | end;
 | 
|---|
| 616 | 
 | 
|---|
| 617 | end.
 | 
|---|