{$INCLUDE Switches.inc}
unit Barbarina;

interface

uses
{$IFDEF DEBUG}SysUtils,{$ENDIF} // necessary for debug exceptions
{$IFDEF DEBUG}Names,{$ENDIF}
  Protocol, ToolAI, CustomAI;

const
  nModelCategory = 4;
  ctGroundSlow = 0;
  ctGroundFast = 1;
  ctSeaTrans = 2;
  ctSeaArt = 3;

  maxCOD = 256;

  maxModern = 16;
// maximum number of modern resources of one type being managed
// (for designed maps only, number is 2 in standard game)


type
  TColonyShipPlan = array[0..nShipPart - 1] of record
    cixProducing: Integer;
    LocResource: array[0..maxModern - 1] of Integer;
    nLocResource: Integer;
    LocFoundCity: array[0..maxModern - 1] of Integer;
    nLocFoundCity: Integer;
  end;

  TBarbarina = class(TToolAI)
    constructor Create(Nation: Integer); override;
  protected
    ColonyShipPlan: TColonyShipPlan;
    function Barbarina_GoHidden: Boolean; // whether we should prepare for barbarina mode
    function Barbarina_Go: Boolean; // whether we should switch to barbarina mode now
    procedure Barbarina_DoTurn;
    procedure Barbarina_SetCityProduction;
    function Barbarina_ChooseResearchAdvance: Integer;
    function Barbarina_WantCheckNegotiation(Nation: Integer): Boolean;
    procedure Barbarina_DoCheckNegotiation;
    function Barbarina_WantNegotiation(Nation: Integer; NegoTime: TNegoTime): Boolean;
    procedure Barbarina_DoNegotiation;
    procedure MakeColonyShipPlan;
  private
    TurnOfMapAnalysis, Neighbours: Integer;
    ContinentPresence: array[0..maxCOD - 1] of Integer;
    OceanPresence: array[0..maxCOD - 1] of Integer;
    ContinentSize: array[0..maxCOD - 1] of Integer;
    OceanSize: array[0..maxCOD - 1] of Integer;
    mixBest: array[0..nModelCategory - 1] of Integer;
    NegoCause: (CancelTreaty);
    function IsModelAvailable(rmix: Integer): Boolean;
    procedure FindBestModels;
    procedure AnalyzeMap;
    procedure RateAttack(uix: Integer);
    function DoAttack(uix, AttackLoc: Integer): Boolean;
    function ProcessMove(uix: Integer): Boolean;
    procedure AttackAndPatrol;
  end;


implementation

uses
  Pile;

type
  TResearchModel = record
    Category, Domain, Weight, adStop, FutMStrength: Integer;
    Upgrades: Cardinal;
    Cap: array [0..nFeature - 1] of Integer;
  end;

const
  //UnitKind
  ukSlow = $01;
  ukFast = $02;

  neumax = 4096;
  mixTownGuard = 2;

  PresenceUnknown = $10000;

  WonderProductionThreshold = 15;
  WonderInclination = 24.0; // higher value means lower probability of building wonder
  ReduceDefense = 16; // if this is x, 1/x of all units is used to defend cities

  nResearchOrder = 40;
  ResearchOrder: array[0..nResearchOrder - 1] of Integer =
    (adBronzeWorking, -adMapMaking, adChivalry, adMonotheism, adIronWorking,
    adGunPowder, adTheology, adConstruction, adCodeOfLaws, -adEngineering,
    -adSeafaring, -adNavigation, adMetallurgy, adBallistics, adScience, adExplosives,
    adTactics, adSteel, -adSteamEngine, -adAmphibiousWarfare, -adMagnetism, adRadio,
    adAutomobile, adMobileWarfare, adRailroad, adCommunism, adDemocracy,
    adTheCorporation, adMassProduction, adIndustrialization, adRobotics, adComposites,
    adTheLaser, adFlight, adAdvancedFlight, adSpaceFlight,
    adSyntheticFood, adTransstellarColonization, adElectronics, adSmartWeapons);

  nResearchModel = 16;
  ResearchModel: array[0..nResearchModel - 1] of TResearchModel =
    //       Wea Arm Mob Sea Car Tur Bom Fue Air Nav Rad Sub Art Alp Sup Ove Air Spy SE  NP  Jet Ste Fan Fir Wil Aca Lin
    ((Category: ctGroundSlow; Domain: dGround; Weight: 7; adStop: adIronWorking;
    Upgrades: $0003;
    Cap: (3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)),
    (Category: ctGroundFast; Domain: dGround; Weight: 7; adStop: adIronWorking;
    Upgrades: $0003;
    Cap: (3, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)),
    (Category: ctGroundSlow; Domain: dGround; Weight: 7; adStop: adExplosives;
    Upgrades: $003F;
    Cap: (3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)),
    (Category: ctGroundFast; Domain: dGround; Weight: 7; adStop: adExplosives;
    Upgrades: $003F;
    Cap: (3, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)),
    (Category: ctSeaTrans; Domain: dSea; Weight: 7; adStop: adExplosives;
    Upgrades: $000F;
    Cap: (0, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)),
    (Category: ctSeaArt; Domain: dSea; Weight: 7; adStop: adExplosives; Upgrades: $000F;
    Cap: (4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)),
    (Category: ctGroundSlow; Domain: dGround; Weight: 7; adStop: adAutomobile;
    Upgrades: $00FF;
    Cap: (1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)),
    (Category: ctGroundFast; Domain: dGround; Weight: 7; adStop: adAutomobile;
    Upgrades: $00FF;
    Cap: (3, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)),
    (Category: ctSeaTrans; Domain: dSea; Weight: 9; adStop: -1; Upgrades: $00FF;
    Cap: (0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)),
    (Category: ctSeaArt; Domain: dSea; Weight: 9; adStop: -1; Upgrades: $00FF;
    Cap: (5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)),
    (Category: ctGroundSlow; Domain: dGround; Weight: 10; adStop: adCommunism;
    Upgrades: $05FF;
    Cap: (3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)),
    (Category: ctGroundFast; Domain: dGround; Weight: 10; adStop: adCommunism;
    Upgrades: $05FF;
    Cap: (5, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)),
    (Category: ctGroundSlow; Domain: dGround; Weight: 10; adStop: adComposites;
    Upgrades: $07FF;
    Cap: (3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1)),
    (Category: ctGroundFast; Domain: dGround; Weight: 10; adStop: adComposites;
    Upgrades: $07FF;
    Cap: (5, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1)),
    (Category: ctGroundSlow; Domain: dGround; Weight: 10; adStop: -1; Upgrades: $3FFF;
    Cap: (3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1)),
    (Category: ctGroundFast; Domain: dGround; Weight: 10; adStop: -1; Upgrades: $3FFF;
    Cap: (5, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1)));
  EntryModel_Base = 1;
  EntryModel_GunPowder = 3;
  EntryModel_MassProduction = 13;


var
  Moved: array[0..numax - 1] of Boolean;
  UnitPresence: array[0..lxmax * lymax - 1] of Byte;
  euixMap: array[0..lxmax * lymax - 1] of SmallInt;
  uixAttack: array[0..neumax - 1] of SmallInt;
  AttackScore: array[0..neumax - 1] of Integer;

constructor TBarbarina.Create(Nation: Integer);
begin
  inherited;
  TurnOfMapAnalysis := -1;
end;

// whether one of the existing models matches a specific research model
function TBarbarina.IsModelAvailable(rmix: Integer): Boolean;
var
  I, mix, MStrength: Integer;
begin
  Result := False;
  with ResearchModel[rmix] do
  begin
    MStrength := CurrentMStrength(Domain);
    for mix := 3 to RO.nModel - 1 do
      if ((MyModel[mix].kind = mkSelfDeveloped) or
        (MyModel[mix].kind = mkEnemyDeveloped)) and (MyModel[mix].Domain = Domain) and
        (Upgrades and not MyModel[mix].Upgrades = 0) then
      begin
        Result := MStrength < (MyModel[mix].MStrength * 3) div 2;
        // for future techs: don't count model available if 50% stronger possible
        for I := 0 to nFeature - 1 do
          if MyModel[mix].Cap[I] < Cap[I] then
          begin
            Result := False;
            Break;
          end;
        if Result then
          Break;
      end;
  end;
end;

function TBarbarina.Barbarina_GoHidden: Boolean;
var
  V21, Loc1, cix: Integer;
  Radius: TVicinity21Loc;
begin
  if IsResearched(adMassProduction) then
  begin
    Result := True;
    for cix := 0 to RO.nCity - 1 do
      with MyCity[cix] do begin
        if Loc >= 0 then
        begin // search for modern resource
          V21_to_Loc(Loc, Radius);
          for V21 := 1 to 26 do
          begin
            Loc1 := Radius[V21];
            if (Loc1 >= 0) and (RO.Map[Loc1] and fModern <> 0) then begin
              Result := False;
              Break;
            end;
          end;
        end;
        if not Result then Break;
      end;
  end
  else if IsResearched(adGunPowder) then
    Result := (RO.Tech[adTheRepublic] < tsSeen) and IsResearched(adTheology)
  else
    Result := False;
end;

function TBarbarina.Barbarina_Go: Boolean;
begin
  if IsResearched(adMassProduction) then
    Result := IsResearched(adTheology) and IsModelAvailable(EntryModel_MassProduction)
  else if IsResearched(adGunPowder) then
    Result := IsResearched(adTheology) and IsResearched(adMapMaking) and
      IsModelAvailable(EntryModel_GunPowder)
  else
  begin
    Result := (RO.nCity >= 3) and IsResearched(adMapMaking) and
      IsModelAvailable(EntryModel_Base);
    Exit;
  end;
  Result := Result and ((RO.nUn >= RO.nCity * 3) or
    (RO.Wonder[woZeus].EffectiveOwner = Me));
end;

procedure TBarbarina.AnalyzeMap;
var
  Loc, Loc1, V8, f1, p1, cix: Integer;
  Adjacent: TVicinity8Loc;
begin
  if TurnOfMapAnalysis = RO.Turn then
    Exit;

  // inherited;

  // collect nation presence information for continents and oceans
  FillChar(ContinentPresence, SizeOf(ContinentPresence), 0);
  FillChar(OceanPresence, SizeOf(OceanPresence), 0);
  FillChar(ContinentSize, SizeOf(ContinentSize), 0);
  FillChar(OceanSize, SizeOf(OceanSize), 0);
  for Loc := 0 to MapSize - 1 do
  begin
    f1 := Formation[Loc];
    case f1 of
      0..maxCOD - 1:
      begin
        p1 := RO.Territory[Loc];
        if p1 >= 0 then
          if Map[Loc] and fTerrain >= fGrass then
          begin
            Inc(ContinentSize[f1]);
            ContinentPresence[f1] := ContinentPresence[f1] or (1 shl p1);
          end
          else
          begin
            Inc(OceanSize[f1]);
            OceanPresence[f1] := OceanPresence[f1] or (1 shl p1);
          end;
      end;
      nfUndiscovered:
      begin // adjacent formations are not completely discovered
        V8_to_Loc(Loc, Adjacent);
        for V8 := 0 to 7 do
        begin
          Loc1 := Adjacent[V8];
          if Loc1 >= 0 then
          begin
            f1 := Formation[Loc1];
            if (f1 >= 0) and (f1 < maxCOD) then
              if Map[Loc1] and fTerrain >= fGrass then
                ContinentPresence[f1] := ContinentPresence[f1] or PresenceUnknown
              else
                OceanPresence[f1] := OceanPresence[f1] or PresenceUnknown;
          end;
        end;
      end;
      nfPeace:
      begin // nation present in adjacent formations
        V8_to_Loc(Loc, Adjacent);
        for V8 := 0 to 7 do
        begin
          Loc1 := Adjacent[V8];
          if Loc1 >= 0 then
          begin
            f1 := Formation[Loc1];
            if (f1 >= 0) and (f1 < maxCOD) then
              if Map[Loc1] and fTerrain >= fGrass then
                ContinentPresence[f1] :=
                  ContinentPresence[f1] or (1 shl RO.Territory[Loc])
              else
                OceanPresence[f1] := OceanPresence[f1] or (1 shl RO.Territory[Loc]);
          end;
        end;
      end;
    end;
  end;

  Neighbours := 0;
  for cix := 0 to RO.nCity - 1 do
    with MyCity[cix] do
      if (Loc >= 0) and (Formation[Loc] >= 0) and (Formation[Loc] < maxCOD) then
        Neighbours := Neighbours or ContinentPresence[Formation[Loc]];
  Neighbours := Neighbours and not PresenceUnknown;

  TurnOfMapAnalysis := RO.Turn;
end;

procedure TBarbarina.FindBestModels;
var
  I, mix, rmix, cat: Integer;
begin
  for I := 0 to nModelCategory - 1 do
    mixBest[I] := -1;
  for rmix := nResearchModel - 1 downto 0 do
    with ResearchModel[rmix] do
      if mixBest[Category] < 0 then
        for mix := 3 to RO.nModel - 1 do
          if (MyModel[mix].Domain = Domain) and
            (Upgrades and not MyModel[mix].Upgrades = 0) then
          begin
            mixBest[Category] := mix;
            for I := 0 to nFeature - 1 do
              if MyModel[mix].Cap[I] < Cap[I] then
              begin
                mixBest[Category] := -1;
                Break;
              end;
            if mixBest[Category] >= 0 then
              Break;
          end;
  for mix := 3 to RO.nModel - 1 do
    with MyModel[mix] do
      if Kind <= mkEnemyDeveloped then
      begin
        cat := -1;
        case Domain of
          dGround:
            if Speed >= 250 then
              cat := ctGroundFast
            else
              cat := ctGroundSlow;
          dSea:
            if Cap[mcSeaTrans] > 0 then
              cat := ctSeaTrans
            else if Cap[mcArtillery] > 0 then
              cat := ctSeaArt;
        end;
        if (cat >= 0) and (mix <> mixBest[cat]) and
          ((mixBest[cat] < 0) or (Weight * MStrength > MyModel[mixBest[cat]].Weight +
          MyModel[mixBest[cat]].MStrength)) then
          mixBest[cat] := mix;
      end;
  if (mixBest[ctSeaTrans] < 0) and not IsResearched(adExplosives) then // longboat?
    for mix := 3 to RO.nModel - 1 do
      if MyModel[mix].Cap[mcSeaTrans] > 0 then
      begin
        mixBest[ctSeaTrans] := mix;
        Break;
      end;
end;

procedure TBarbarina.Barbarina_DoTurn;
begin
  if (RO.Government in [gRepublic, gDemocracy, gFuture]) or
    (RO.Government <> gFundamentalism) and (RO.Government <> gAnarchy) and
    IsResearched(adTheology) then
    Revolution;

  AnalyzeMap;

  FindBestModels;

  AttackAndPatrol;
end;

// find one unit to destroy each known enemy unit, result in uixAttack
procedure TBarbarina.RateAttack(uix: Integer);
var
  MoveStyle, TestLoc, TestTime, NextLoc, NextTime, V8, RemHealth,
  RecoverTurns, Score, BestScore, euixBest, uixOld: Integer;
  NextTile: Cardinal;
  Adjacent: TVicinity8Loc;
  Defense: ^TUnitInfo;
  Reached: array[0..lxmax * lymax - 1] of Boolean;
begin
  with MyUnit[uix] do
    if Movement > 0 then
    begin
      BestScore := 0;
      FillChar(Reached, MapSize, False);
      MoveStyle := GetMyMoveStyle(mix, Health);
      Pile.Create(MapSize);
      Pile.Put(Loc, $800 - Movement);
      while Pile.Get(TestLoc, TestTime) do
      begin
        Reached[TestLoc] := True;
        V8_to_Loc(TestLoc, Adjacent);
        for V8 := 0 to 7 do
        begin
          NextLoc := Adjacent[V8];
          if (NextLoc >= 0) and not Reached[NextLoc] then
          begin
            NextTile := Map[NextLoc];
            if euixMap[NextLoc] >= 0 then
            begin // check attack
              Defense := @RO.EnemyUn[euixMap[NextLoc]];
              if Unit_AttackForecast(uix, NextLoc, $800 - TestTime, RemHealth) then
              begin
                if RemHealth <= 0 then // send unit into death?
                begin
                  Score := 0;
                  if ($800 - TestTime >= 100) and
                    ((MyModel[mix].Domain = dGround) and
                    (NextTile and fTerrain >= fGrass) or
                    (MyModel[mix].Domain = dSea) and (NextTile and
                    fTerrain < fGrass)) and
                    (MyModel[mix].Attack > MyModel[mix].Defense) then
                  begin
                    Score := (Defense.Health + RemHealth) *
                      RO.EnemyModel[Defense.emix].Cost * 2 div MyModel[mix].Cost;
                    if NextTile and fCity <> 0 then
                      Score := Score * 4;
                  end;
                end
                else
                  Score := RO.EnemyModel[Defense.emix].Cost * 25 -
                    (Health - RemHealth) * MyModel[mix].Cost shr 4;
                if (Score > BestScore) and (Score > AttackScore[euixMap[NextLoc]]) then
                begin
                  BestScore := Score;
                  euixBest := euixMap[NextLoc];
                end;
              end;
            end
            else if (NextTile and (fUnit or fCity) = 0) or
              (NextTile and fOwned <> 0) then
              case CheckStep(MoveStyle, TestTime, V8 and 1, NextTime,
                  RecoverTurns, Map[TestLoc], NextTile, True) of
                csOk:
                  if NextTime < $800 then
                    Pile.Put(NextLoc, NextTime);
                csForbiddenTile:
                  Reached[NextLoc] := True; // don't check moving there again
                csCheckTerritory:
                  if (NextTime < $800) and (RO.Territory[NextLoc] =
                    RO.Territory[TestLoc]) then
                    Pile.Put(NextLoc, NextTime);
              end;
          end;
        end;
      end;
      Pile.Free;

      if BestScore > 0 then
      begin
        uixOld := uixAttack[euixBest];
        AttackScore[euixBest] := BestScore;
        uixAttack[euixBest] := uix;
        if uixOld >= 0 then
          RateAttack(uixOld);
      end;
    end;
end;

function TBarbarina.DoAttack(uix, AttackLoc: Integer): Boolean;
  // AttackLoc=maNextCity means bombard only
var
  MoveResult, Kind, Temp, MoveStyle, TestLoc, TestTime, NextLoc,
  NextTime, V8, RecoverTurns, ecix: Integer;
  NextTile: Cardinal;
  AttackPositionReached, IsBombardment: Boolean;
  Adjacent: TVicinity8Loc;
  PreLoc: array[0..lxmax * lymax - 1] of Word;
  Reached: array[0..lxmax * lymax - 1] of Boolean;
begin
  Result := False;
  IsBombardment := AttackLoc = maNextCity;
  with MyUnit[uix] do
  begin
    if (MyModel[mix].Domain = dGround) and (MyModel[mix].Attack > 0) then
      if MyModel[mix].Speed >= 250 then
        Kind := ukFast
      else
        Kind := ukSlow
    else
      Kind := 0;
    FillChar(Reached, MapSize, False);
    AttackPositionReached := False;
    MoveStyle := GetMyMoveStyle(mix, Health);
    Pile.Create(MapSize);
    Pile.Put(Loc, $800 - Movement);
    while Pile.Get(TestLoc, TestTime) do
    begin
      if (TestTime >= $800) or (AttackLoc = maNextCity) and (TestTime > $800 - 100) then
        Break;
      Reached[TestLoc] := True;
      V8_to_Loc(TestLoc, Adjacent);
      for V8 := 0 to 7 do
      begin
        NextLoc := Adjacent[V8];
        if NextLoc >= 0 then
        begin
          if IsBombardment and (Map[NextLoc] and (fCity or
            fUnit or fOwned or fObserved) = fCity or fObserved) and
            (RO.Treaty[RO.Territory[NextLoc]] < trPeace) then
          begin
            City_FindEnemyCity(NextLoc, ecix);
            Assert(ecix >= 0);
            with RO.EnemyCity[ecix] do
              if (Size > 2) and (Flags and ciCoastalFort = 0) then
                AttackLoc := NextLoc;
          end;
          if (NextLoc = AttackLoc) and ((MyModel[mix].Domain <> dSea) or
            (Map[TestLoc] and fTerrain < fGrass)) then
            // ships can only attack from water
          begin
            AttackPositionReached := True;
            Break;
          end
          else if not Reached[NextLoc] then
          begin
            NextTile := Map[NextLoc];
            if (NextTile and (fUnit or fCity) = 0) or
              (NextTile and fOwned <> 0) then
              case CheckStep(MoveStyle, TestTime, V8 and 1, NextTime,
                  RecoverTurns, Map[TestLoc], NextTile, True) of
                csOk:
                  if Pile.Put(NextLoc, NextTime) then
                    PreLoc[NextLoc] := TestLoc;
                csForbiddenTile:
                  Reached[NextLoc] := True; // don't check moving there again
                csCheckTerritory:
                  if RO.Territory[NextLoc] = RO.Territory[TestLoc] then
                    if Pile.Put(NextLoc, NextTime) then
                      PreLoc[NextLoc] := TestLoc;
              end;
          end;
        end;
      end;
      if AttackPositionReached then
      begin
        PreLoc[NextLoc] := TestLoc;
        Break;
      end;
    end;
    Pile.Free;
    if not AttackPositionReached then
      Exit;

    TestLoc := AttackLoc;
    NextLoc := PreLoc[TestLoc];
    while TestLoc <> Loc do
    begin
      Temp := TestLoc;
      TestLoc := NextLoc;
      NextLoc := PreLoc[TestLoc];
      PreLoc[TestLoc] := Temp;
    end;

    UnitPresence[Loc] := UnitPresence[Loc] and not Kind;
    // assume unit was only one of kind here
    repeat
      NextLoc := PreLoc[Loc];
      MoveResult := Unit_Step(uix, NextLoc);
    until (NextLoc = AttackLoc) or (MoveResult and rExecuted = 0) or
      (MoveResult and rUnitRemoved <> 0);
    Result := (NextLoc = AttackLoc) and (MoveResult and rExecuted <> 0);

    if IsBombardment and Result then
    begin
      City_FindEnemyCity(AttackLoc, ecix);
      Assert(ecix >= 0);
      while (Movement >= 100) and (RO.EnemyCity[ecix].Size > 2) do
        Unit_Step(uix, AttackLoc);
    end;

    if Loc >= 0 then
      UnitPresence[Loc] := UnitPresence[Loc] or Kind;
  end;
end;

function TBarbarina.ProcessMove(uix: Integer): Boolean;
  // return true if no new enemy spotted
const
  DistanceScore = 4;
var
  PatrolScore, BestCount, PatrolLoc, TestLoc, NextLoc, TestTime, V8,
  TestScore, MoveResult, MoveStyle, NextTime, TerrOwner, Kind, Temp,
  RecoverTurns, MaxScore: Integer;
  Tile, NextTile: Cardinal;
  CaptureOnly, PeaceBorder, done, NextToEnemyCity: Boolean;
  Adjacent: TVicinity8Loc;
  AdjacentUnknown: array[0..lxmax * lymax - 1] of ShortInt;
  PreLoc: array[0..lxmax * lymax - 1] of Word;
  MoreTurn: array[0..lxmax * lymax - 1] of Byte;

begin
  Result := True;
  done := False;
  while not done do
    with MyUnit[uix] do
    begin
      if (MyModel[mix].Domain = dSea) and (Health < 100) and
        ((Health < 34) or (MyModel[mix].Cap[mcSeaTrans] > 0)) then
      begin
        if Map[Loc] and fCity = 0 then
          Unit_MoveEx(uix, maNextCity);
        Exit;
      end;

      if (MyModel[mix].Domain = dGround) and (MyModel[mix].Attack > 0) then
        if MyModel[mix].Speed >= 250 then
          Kind := ukFast
        else
          Kind := ukSlow
      else
        Kind := 0;
      CaptureOnly := (Health < 100) and ((Map[Loc] and fCity <> 0) or
        ((100 - Health) * Terrain[Map[Loc] and fTerrain].Defense > 60) and
        not (Map[Loc] and fTerrain in [fOcean, fShore, fArctic, fDesert]));
      MoveStyle := GetMyMoveStyle(mix, Health);

      if MyModel[mix].Attack > 0 then
        MaxScore := $400
      else
        MaxScore := $400 - 32 + 5;
      PatrolScore := -999999;
      PatrolLoc := -1;
      FillChar(AdjacentUnknown, MapSize, $FF); // -1, indicates tiles not checked yet
      Pile.Create(MapSize);
      Pile.Put(Loc, $800 - Movement);
      while Pile.Get(TestLoc, TestTime) do
      begin
        if (MaxScore * $1000 - DistanceScore * TestTime <= PatrolScore)
          // assume a score of $400 is the best achievable
          or CaptureOnly and (TestTime >= $1000) then
          Break;

        TestScore := 0;
        Tile := Map[TestLoc];
        Assert(Tile and (fUnit or fOwned) <> fUnit);
        TerrOwner := RO.Territory[TestLoc];
        AdjacentUnknown[TestLoc] := 0;
        PeaceBorder := False;
        NextToEnemyCity := False;

        if ((Tile and fCity) <> 0) and ((Tile and fOwned) = 0) then
        begin
          if (MyModel[mix].Domain = dGround) and (MyModel[mix].Attack > 0) and
            ((TerrOwner < 0)
            // happens only for unobserved cities of extinct tribes, new owner unknown
            or (RO.Treaty[TerrOwner] < trPeace)) then
            if (Tile and fObserved <> 0) and (Tile and fUnit = 0) then
              TestScore := $400 // unfriendly undefended city -- capture!
            else
              TestScore := $400 - 14; // unfriendly city, not observed or defended
        end

        else
        begin // no enemy city or unit here
          V8_to_Loc(TestLoc, Adjacent);
          for V8 := 0 to 7 do
          begin
            NextLoc := Adjacent[V8];
            if (NextLoc >= 0) and (AdjacentUnknown[NextLoc] < 0) then
            begin
              NextTile := Map[NextLoc];
              if NextTile and fTerrain = fUNKNOWN then
                Inc(AdjacentUnknown[TestLoc])
              else if NextTile and fTerrain = fArctic then
              else if NextTile and (fCity or fUnit or fOwned or fObserved) =
                fCity or fUnit or fObserved then
                NextToEnemyCity := True
              else
                case CheckStep(MoveStyle, TestTime, V8 and 1, NextTime,
                    RecoverTurns, Tile, NextTile, True) of
                  csOk:
{              if (NextTime and $7FFFF000=TestTime and $7FFFF000)
                or (UnitPresence[TestLoc] and Kind=0)
                or (Tile and fCity<>0)
                or (Tile and fTerImp=tiFort) or (Tile and fTerImp=tiBase) then}
                  begin
                    if Pile.Put(NextLoc, NextTime + RecoverTurns * $1000) then
                    begin
                      PreLoc[NextLoc] := TestLoc;
                      MoreTurn[NextLoc] := NextTime shr 12 and $FFF;
                    end;
                  end;
                  csForbiddenTile:
                  begin
                    AdjacentUnknown[NextLoc] := 0; // don't check moving there again
                    if NextTile and fPeace <> 0 then
                      PeaceBorder := True;
                  end;
                  csCheckTerritory:
                    if RO.Territory[NextLoc] = TerrOwner then
                    begin
                      if Pile.Put(NextLoc, NextTime + RecoverTurns * $1000) then
                      begin
                        PreLoc[NextLoc] := TestLoc;
                        MoreTurn[NextLoc] := NextTime shr 12 and $FFF;
                      end;
                    end
                    else
                      PeaceBorder := True;
                end;
            end;
          end;
          if not CaptureOnly then
            if NextToEnemyCity and (MyModel[mix].Attack > 0) and
              (MyModel[mix].Domain = dGround) then
              TestScore := $400 - 14
            else if AdjacentUnknown[TestLoc] > 0 then
              if PeaceBorder or (TerrOwner >= 0) and (TerrOwner <> Me) and
                (RO.Treaty[TerrOwner] < trPeace) then
                TestScore := $400 - 32 + AdjacentUnknown[TestLoc]
              else
                TestScore := $400 - 64 + AdjacentUnknown[TestLoc]
            else if PeaceBorder then
              TestScore := $400 - 32
            else
              TestScore := (RO.Turn - RO.MapObservedLast[TestLoc]) div 16;
        end; // no enemy city or unit here

        if TestScore > 0 then
        begin
          TestScore := TestScore * $1000 - DistanceScore * TestTime;
          if TestScore > PatrolScore then
            BestCount := 0;
          if TestScore >= PatrolScore then
          begin
            Inc(BestCount);
            if Random(BestCount) = 0 then
            begin
              PatrolScore := TestScore;
              PatrolLoc := TestLoc;
            end;
          end;
        end;
      end; // while Pile.Get
      Pile.Free;

      if (PatrolLoc >= 0) and (PatrolLoc <> Loc) then
      begin // capture/discover/patrol task found, execute it
        while (PatrolLoc <> Loc) and (MoreTurn[PatrolLoc] > 0) and
          ((MoreTurn[PatrolLoc] > 1) or not (Map[PatrolLoc] and fTerrain in
            [fMountains, fDesert, fArctic])) do
        begin
          PatrolLoc := PreLoc[PatrolLoc];
          done := True; // no effect if enemy spotted
        end;
        while (PatrolLoc <> Loc) and (UnitPresence[PatrolLoc] and Kind <> 0) and
          (Map[PatrolLoc] and fCity = 0) and (Map[PatrolLoc] and fTerImp <> tiFort) and
          (Map[PatrolLoc] and fTerImp <> tiBase) and not
          (Map[PreLoc[PatrolLoc]] and fTerrain in [fDesert, fArctic]) do
        begin
          PatrolLoc := PreLoc[PatrolLoc];
          done := True; // no effect if enemy spotted
        end;
        if PatrolLoc = Loc then
          Exit;
        TestLoc := PatrolLoc;
        NextLoc := PreLoc[TestLoc];
        while TestLoc <> Loc do
        begin
          Temp := TestLoc;
          TestLoc := NextLoc;
          NextLoc := PreLoc[TestLoc];
          PreLoc[TestLoc] := Temp;
        end;

        UnitPresence[Loc] := UnitPresence[Loc] and not Kind;
        // assume unit was only one of kind here
        while Loc <> PatrolLoc do
        begin
          NextLoc := PreLoc[Loc];
          MoveResult := Unit_Step(uix, NextLoc);
          if (MoveResult and (rUnitRemoved or rEnemySpotted) <> 0) or
            (MoveResult and rExecuted = 0) then
          begin
            if MoveResult and rExecuted = 0 then
              Moved[uix] := True;
            Result := MoveResult and rEnemySpotted = 0;
            done := True;
            Break;
          end;
          Assert(Loc = NextLoc);
        end;
        if Loc >= 0 then
        begin
          UnitPresence[Loc] := UnitPresence[Loc] or Kind;
          if Map[Loc] and fCity <> 0 then
          begin
            Moved[uix] := True;
            done := True; // stay in captured city as defender
          end;
        end;
      end
      else
        done := True;
    end; // while not done
  if Result then
    Moved[uix] := True;
end;

procedure TBarbarina.AttackAndPatrol;

  procedure SetCityDefenders;
  var
    uix, cix, V8, Loc1, Best, uixBest, det: Integer;
    Adjacent: TVicinity8Loc;
    IsPort: Boolean;
  begin
    for cix := 0 to RO.nCity - 1 do
      with MyCity[cix] do
        if Loc >= 0 then
        begin
          IsPort := False;
          V8_to_Loc(Loc, Adjacent);
          for V8 := 0 to 7 do
          begin
            Loc1 := Adjacent[V8];
            if (Loc1 >= 0) and (Map[Loc1] and fTerrain < fGrass) and
              (Formation[Loc1] >= 0) and (Formation[Loc1] < maxCOD) and
              (OceanPresence[Formation[Loc1]] and not Neighbours <> 0) then
              IsPort := True;
          end;
          Best := -1;
          for uix := 0 to RO.nUn - 1 do
            if MyUnit[uix].Loc = Loc then
              with MyUnit[uix] do
                if (MyModel[mix].Domain = dGround) and (MyModel[mix].Attack > 0) then
                begin
                  if (mix = 2) and (RO.Government = gDespotism) then
                  begin
                    det := 1 shl 16;
                    Moved[uix] := True;
                  end // town guard
                  else if IsPort then
                    det := MyModel[mix].Defense shl 8 + Flags and
                      unFortified shl 7 - health
                  else
                    det := MyModel[mix].Speed shl 8 + Flags and
                      unFortified shl 7 - health;
                  if det > Best then
                  begin
                    Best := det;
                    uixBest := uix;
                  end;
                end;
          if Best >= 0 then
            Moved[uixBest] := True;
        end;
  end;

  procedure ProcessSeaTransport;
  var
    I, F, uix, Loc1, A, B: Integer;
    Ready, Go: Boolean;
    TransportPlan: TGroupTransportPlan;
  begin
    Go := False;
    for F := 0 to maxCOD - 1 do
      if (F < nContinent) and (ContinentPresence[F] and not
        (1 shl Me or PresenceUnknown) <> 0) then begin
          Go := True; // any enemy island known?
          Break;
        end;
    if not Go then
      Exit;

    SeaTransport_BeginInitialize;
    Go := False;
    for uix := 0 to RO.nUn - 1 do
      if not Moved[uix] then
        with MyUnit[uix] do
          if (Loc >= 0) and (MyModel[mix].Domain = dGround) and
            (MyModel[mix].Attack > 0) and (Map[Loc] and fTerrain >= fGrass) then
          begin
            F := Formation[Loc];
            if (F >= 0) and (F < maxCOD) and (ContinentPresence[F] and
              not (1 shl Me) = 0) then
            begin
              go := True;
              SeaTransport_AddLoad(uix);
            end;
          end;
    if Go then
    begin
      Go := False;
      for uix := 0 to RO.nUn - 1 do
        if not Moved[uix] then
          with MyUnit[uix] do
            if (Loc >= 0) and (mix = mixBest[ctSeaTrans]) and
              (TroopLoad = 0) and (Health = 100) then
            begin
              Go := True;
              SeaTransport_AddTransport(uix);
            end;
    end;
    if Go then
      for Loc1 := 0 to MapSize - 1 do
        if Map[Loc1] and fTerrain >= fGrass then
        begin
          F := Formation[Loc1];
          if (F >= 0) and (F < maxCOD) and (ContinentPresence[F] and
            not (1 shl Me or PresenceUnknown) <> 0) then
            SeaTransport_AddDestination(Loc1);
        end;
    SeaTransport_EndInitialize;
    while SeaTransport_MakeGroupPlan(TransportPlan) do
    begin
      Moved[TransportPlan.uixTransport] := True;
      Ready := MyUnit[TransportPlan.uixTransport].Loc = TransportPlan.LoadLoc;
      if not Ready then
      begin
        Unit_MoveEx(TransportPlan.uixTransport, TransportPlan.LoadLoc);
        Ready := MyUnit[TransportPlan.uixTransport].Loc = TransportPlan.LoadLoc;
      end;
      if Ready then
        for I := 0 to TransportPlan.nLoad - 1 do
        begin
          Loc_to_ab(TransportPlan.LoadLoc,
            MyUnit[TransportPlan.uixLoad[I]].Loc, A, B);
          Ready := Ready and (Abs(A) <= 1) and (Abs(B) <= 1);
        end;
      if Ready then
      begin
        for I := 0 to TransportPlan.nLoad - 1 do
        begin
          Unit_Step(TransportPlan.uixLoad[I], TransportPlan.LoadLoc);
          Moved[TransportPlan.uixLoad[I]] := True;
        end;
      end
      else
      begin
        for I := 0 to TransportPlan.nLoad - 1 do
        begin
          Unit_MoveEx(TransportPlan.uixLoad[I], TransportPlan.LoadLoc, mxAdjacent);
          Moved[TransportPlan.uixLoad[I]] := True;
        end;
      end;
    end;
  end;

  procedure ProcessUnload(uix: Integer);

    procedure Unload(Kind, ToLoc: Integer);
    var
      uix1: Integer;
    begin
      for uix1 := 0 to RO.nUn - 1 do
        with MyUnit[uix1] do
          if (Loc >= 0) and (Master = uix) and (MyModel[mix].Domain = dGround) and
            (MyModel[mix].Attack > 0) and (Movement = MyModel[mix].Speed) and
            ((MyModel[mix].Speed >= 250) = (Kind = ukFast)) then
          begin
            Unit_Step(uix1, ToLoc);
            UnitPresence[ToLoc] := UnitPresence[ToLoc] or Kind;
            Break;
          end;
    end;

  var
    uix1, MoveStyle, TestLoc, TestTime, NextLoc, NextTime, V8,
    RecoverTurns, nSlow, nFast, SlowUnloadLoc, FastUnloadLoc, EndLoc, F: Integer;
    NextTile: Cardinal;
    Adjacent: TVicinity8Loc;
    Reached: array[0..lxmax * lymax - 1] of Boolean;
  begin
    // inventory
    nSlow := 0;
    nFast := 0;
    for uix1 := 0 to RO.nUn - 1 do
      with MyUnit[uix1] do
        if (Loc >= 0) and (Master = uix) and (MyModel[mix].Domain = dGround) and
          (MyModel[mix].Attack > 0) then
          if MyModel[mix].Speed >= 250 then
            Inc(nFast)
          else
            Inc(nSlow);

    with MyUnit[uix] do
    begin
      MoveStyle := GetMyMoveStyle(mix, Health);
      repeat
        SlowUnloadLoc := -1;
        FastUnloadLoc := -1;
        EndLoc := -1;
        FillChar(Reached, MapSize, False);
        Pile.Create(MapSize);
        Pile.Put(Loc, $800 - Movement);
        while (SlowUnloadLoc < 0) and (FastUnloadLoc < 0) and
          Pile.Get(TestLoc, TestTime) do
        begin
          Reached[TestLoc] := True;
          V8_to_Loc(TestLoc, Adjacent);
          for V8 := 0 to 7 do
          begin
            NextLoc := Adjacent[V8];
            if (NextLoc >= 0) and not Reached[NextLoc] then
            begin
              NextTile := Map[NextLoc];
              if NextTile and fTerrain = fUnknown then
              else if NextTile and fTerrain >= fGrass then
              begin
                F := Formation[NextLoc];
                if (F >= 0) and (F < maxCOD) and
                  (ContinentPresence[F] and not (1 shl Me or PresenceUnknown) <> 0) and
                  (NextTile and (fUnit or fOwned) <> fUnit) then
                begin
                  if (nSlow > 0) and (UnitPresence[NextLoc] and
                    ukSlow = 0) and ((SlowUnloadLoc < 0) or
                    (Terrain[Map[NextLoc] and fTerrain].Defense >
                    Terrain[Map[SlowUnloadLoc] and fTerrain].Defense)) then
                  begin
                    EndLoc := TestLoc;
                    SlowUnloadLoc := NextLoc;
                  end;
                  if (nFast > 0) and (UnitPresence[NextLoc] and
                    ukFast = 0) and ((FastUnloadLoc < 0) or
                    (Terrain[Map[NextLoc] and fTerrain].Defense >
                    Terrain[Map[FastUnloadLoc] and fTerrain].Defense)) then
                  begin
                    EndLoc := TestLoc;
                    FastUnloadLoc := NextLoc;
                  end;
                end;
              end
              else if EndLoc < 0 then
                case CheckStep(MoveStyle, TestTime, V8 and 1, NextTime,
                    RecoverTurns, Map[TestLoc], NextTile, True) of
                  csOk:
                    Pile.Put(NextLoc, NextTime);
                  csForbiddenTile:
                    Reached[NextLoc] := True; // don't check moving there again
                  csCheckTerritory:
                    if RO.Territory[NextLoc] = RO.Territory[TestLoc] then
                      Pile.Put(NextLoc, NextTime);
                end;
            end;
          end;
        end;
        Pile.Free;

        if EndLoc < 0 then
          Exit;
        if Loc <> EndLoc then
          Unit_MoveEx(uix, EndLoc);
        if Loc <> EndLoc then
          Exit;
        if SlowUnloadLoc >= 0 then
        begin
          Unload(ukSlow, SlowUnloadLoc);
          Dec(nSlow);
        end;
        if FastUnloadLoc >= 0 then
        begin
          Unload(ukFast, FastUnloadLoc);
          Dec(nFast);
        end;
        if TroopLoad = 0 then
        begin
          Moved[uix] := False;
          Exit;
        end
      until False;
    end;
  end;

var
  uix, euix, Kind, euixBest, AttackLoc: Integer;
  OldTile: Cardinal;
  BackToStart, FirstLoop: Boolean;
begin
  FillChar(UnitPresence, MapSize, 0);
  for uix := 0 to RO.nUn - 1 do
    with MyUnit[uix] do
      if (Loc >= 0) and (MyModel[mix].Domain = dGround) and
        (MyModel[mix].Attack > 0) then
      begin
        if MyModel[mix].Speed >= 250 then
          Kind := ukFast
        else
          Kind := ukSlow;
        UnitPresence[Loc] := UnitPresence[Loc] or Kind;
      end;

  FillChar(Moved, RO.nUn, False);
  for uix := 0 to RO.nUn - 1 do
    if (MyUnit[uix].Master >= 0) or (MyUnit[uix].TroopLoad > 0) then
      Moved[uix] := True;

  FirstLoop := True;
  repeat
    // ATTACK
    repeat
      BackToStart := False;
      if RO.nEnemyUn > 0 then
      begin
        FillChar(euixMap, MapSize * 2, $FF);
        FillChar(AttackScore, RO.nEnemyUn * 4, 0);
        for euix := 0 to RO.nEnemyUn - 1 do
          with RO.EnemyUn[euix] do
            if (Loc >= 0) and (RO.Treaty[Owner] < trPeace) then
            begin
              BackToStart := True;
              euixMap[Loc] := euix;
              uixAttack[euix] := -1;
            end;
      end;
      if not BackToStart then
        Break;

      for uix := 0 to RO.nUn - 1 do
        with MyUnit[uix] do
          if (Loc >= 0) and (Master < 0) and (MyModel[mix].Attack > 0) then
            RateAttack(uix);

      BackToStart := False;
      repeat
        euixBest := -1;
        for euix := 0 to RO.nEnemyUn - 1 do
          if (AttackScore[euix] > 0) and ((euixBest < 0) or
            (AttackScore[euix] > AttackScore[euixBest])) then
            euixBest := euix;
        if euixBest < 0 then
          Break;
        uix := uixAttack[euixBest];
        AttackLoc := RO.EnemyUn[euixBest].Loc;
        OldTile := Map[AttackLoc];
        if (AttackLoc < 0)
          // only happens when city was destroyd with attack and enemy units have disappeared
          or (DoAttack(uix, AttackLoc) and
          ((Map[AttackLoc] and fUnit <> 0) or (OldTile and fCity <> 0) and
          (Map[AttackLoc] and fCity = 0))) then
          BackToStart := True // new situation, rethink
        else
        begin
          euixMap[AttackLoc] := -1;
          AttackScore[euixBest] := 0;
          uixAttack[euixBest] := -1;
          if MyUnit[uix].Loc >= 0 then
            RateAttack(uix);
        end;
      until BackToStart;
    until not BackToStart;

    if FirstLoop then
    begin
      SetCityDefenders;
      ProcessSeaTransport;
      for uix := 0 to RO.nUn - 1 do
        with MyUnit[uix] do
          if (Loc >= 0) and (TroopLoad > 0) then
            ProcessUnload(uix);
    end;
    FirstLoop := False;

    for uix := 0 to RO.nUn - 1 do
      with MyUnit[uix], MyModel[mix] do
        if not Moved[uix] and (Loc >= 0) and (Domain = dSea) and
          (Attack > 0) and (Cap[mcArtillery] > 0) then
          DoAttack(uix, maNextCity); // check bombardments

    // MOVE
    for uix := 0 to RO.nUn - 1 do
      if not Moved[uix] then
        with MyUnit[uix] do
          if (Loc >= 0) and ((MyModel[mix].Attack > 0) or
            (MyModel[mix].Domain = dSea)) then
            if not ProcessMove(uix) then
            begin
              BackToStart := True;
              Break;
            end
  until not BackToStart;
end;

procedure TBarbarina.Barbarina_SetCityProduction;

const
  CoastalWonder = 1 shl woLighthouse + 1 shl woMagellan;
  PrimeWonder = 1 shl woColossus + 1 shl woGrLibrary + 1 shl woSun +
    1 shl woMagellan + 1 shl woEiffel + 1 shl woLiberty + 1 shl woShinkansen;

  function LowPriority(cix: Integer): Boolean;
  var
    Part, cixHighPriority, TestDistance: Integer;
  begin
    Result := False;
    for Part := 0 to nShipPart - 1 do
    begin
      cixHighPriority := ColonyShipPlan[Part].cixProducing;
      if (cixHighPriority >= 0) and (cixHighPriority <> cix) then
      begin
        TestDistance := Distance(MyCity[cix].Loc, MyCity[cixHighPriority].Loc);
        if TestDistance < 11 then
        begin
          Result := True;
          Exit;
        end;
      end;
    end;
  end;

  function ChooseWonderToBuild(WonderAvailable: Integer; AllowCoastal: Boolean): Integer;
  var
    Count, iix: Integer;
  begin
    if (WonderAvailable and PrimeWonder > 0) and (AllowCoastal or
      (WonderAvailable and PrimeWonder and not CoastalWonder > 0)) then
      WonderAvailable := WonderAvailable and PrimeWonder; // alway prefer prime wonders
    Count := 0;
    for iix := 0 to nWonder - 1 do
    begin
      if (1 shl iix) and WonderAvailable <> 0 then
        if (1 shl iix) and CoastalWonder <> 0 then
        begin
          if AllowCoastal then
            Inc(Count, 2);
        end
        else
          Inc(Count);
    end;
    Count := Random(Count);
    for iix := 0 to nWonder - 1 do
    begin
      if (1 shl iix) and WonderAvailable <> 0 then
        if (1 shl iix) and CoastalWonder <> 0 then
        begin
          if AllowCoastal then
            Dec(Count, 2);
        end
        else
          Dec(Count);
      if Count < 0 then
      begin
        Result := iix;
        Exit;
      end;
    end;
  end;

var
  I, iix, cix, mix, uix, mixProduce, mixShip, V8, V21, Loc1, TotalPop,
  AlonePop, F, f1, nTownGuard, ShipPart, ProduceShipPart, TestDistance,
  part, WonderAvailable, WonderInWork, cixNewCapital, Center, Score, BestScore: Integer;
  mixCount: array[0..nmmax - 1] of Integer;
  //RareLoc: array[0..5] of integer;
  Adjacent: TVicinity8Loc;
  IsCoastal, IsPort, IsUnitProjectObsolete, HasSettler, SpezializeShipProduction,
  AlgaeAvailable, ProjectComplete, DoLowPriority, WillProduceColonyShip,
  ImportantCity: Boolean;
  Radius: TVicinity21Loc;
  Report: TCityReportNew;
begin
  AnalyzeMap;

  FindBestModels;

  FillChar(mixCount, RO.nModel * 4, 0);
  for uix := 0 to RO.nUn - 1 do
    with MyUnit[uix] do
      if Loc >= 0 then
        Inc(mixCount[mix]);
  if (mixBest[ctGroundSlow] >= 0) and ((mixBest[ctGroundFast] < 0) or
    (mixCount[mixBest[ctGroundSlow]] < mixCount[mixBest[ctGroundFast]])) then
    mixProduce := mixBest[ctGroundSlow]
  else
    mixProduce := mixBest[ctGroundFast];
  if (mixBest[ctSeaTrans] >= 0) and ((mixBest[ctSeaArt] < 0) or
    (mixCount[mixBest[ctSeaTrans]] < mixCount[mixBest[ctSeaArt]])) then
    mixShip := mixBest[ctSeaTrans]
  else
    mixShip := mixBest[ctSeaArt];
  if (mixProduce >= 0) and (mixBest[ctSeaTrans] >= 0) and
    (mixCount[mixShip] * RO.Model[mixBest[ctSeaTrans]].Cap[mcSeaTrans] *
    RO.Model[mixBest[ctSeaTrans]].MTrans div 2 >= mixCount[mixProduce]) then
    mixShip := -1;

  // produce ships only on certain continents?
  TotalPop := 0;
  AlonePop := 0;
  for cix := 0 to RO.nCity - 1 do
    with MyCity[cix] do
      if (Loc >= 0) and (Flags and chCaptured = 0) then
      begin
        Inc(TotalPop, Size);
        F := Formation[Loc];
        if (F < 0) or (F >= maxCOD) or (ContinentPresence[F] = 1 shl Me) then
          Inc(AlonePop, Size);
      end;
  SpezializeShipProduction := AlonePop * 2 >= TotalPop;

  cixNewCapital := -1;
  WonderAvailable := 0;
  WonderInWork := 0;
  for iix := 0 to nWonder - 1 do
    if (Imp[iix].Preq <> preNA) and ((Imp[iix].Preq = preNone) or
      IsResearched(Imp[iix].Preq)) and (RO.Wonder[iix].CityID = WonderNotBuiltYet) then
      Inc(WonderAvailable, 1 shl iix);
  for cix := 0 to RO.nCity - 1 do
    if MyCity[cix].Loc >= 0 then
    begin
      iix := City_CurrentImprovementProject(cix);
      if (iix >= 0) and (iix < nWonder) then
        Inc(WonderInWork, 1 shl iix)
      else if iix = imPalace then
        cixNewCapital := cix;
    end;

  if (RO.NatBuilt[imPalace] = 0) and (cixNewCapital < 0) then
  begin // palace was destroyed, build new one
    Center := CenterOfEmpire;
    BestScore := 0;
    for cix := 0 to RO.nCity - 1 do
      with MyCity[cix] do
        if (Loc >= 0) and (Flags and chCaptured = 0) then
        begin // evaluate city as new capital
          Score := Size * 12 + 512 - Distance(Loc, Center);
          V8_to_Loc(Loc, Adjacent);
          for V8 := 0 to 7 do
          begin
            Loc1 := Adjacent[V8];
            if (Loc1 >= 0) and (Map[Loc1] and fTerrain < fGrass) then
            begin
              f1 := Formation[Loc1];
              if (f1 >= 0) and (f1 < maxCOD) and
                ((OceanSize[f1] >= 8) or (OceanPresence[f1] and not
                (1 shl Me) <> 0)) then
              begin // prefer non-coastal cities
                Dec(Score, 18);
                Break;
              end;
            end;
          end;
          if Score > BestScore then
          begin
            BestScore := Score;
            cixNewCapital := cix;
          end;
        end;
  end;

  AlgaeAvailable := (RO.NatBuilt[imAlgae] = 0) and
    (RO.Tech[Imp[imAlgae].Preq] >= tsApplicable);
  for cix := 0 to RO.nCity - 1 do
    with MyCity[cix] do
      if (Loc >= 0) and (Project and (cpImp + cpIndex) = cpImp + imAlgae) then begin
        AlgaeAvailable := False;
        Break;
      end;

  for cix := 0 to RO.nCity - 1 do
    with MyCity[cix] do
      if (Loc >= 0) and (Flags and chCaptured = 0) and LowPriority(cix) then
        City_SetTiles(cix, 1 shl CityOwnTile); // free all tiles of low-prio cities
  for DoLowPriority := False to True do
    for cix := 0 to RO.nCity - 1 do
      with MyCity[cix] do
        if (Loc >= 0) and (Flags and chCaptured = 0) and
          (LowPriority(cix) = DoLowPriority) then
        begin
          F := Formation[Loc];
          IsCoastal := False;
          IsPort := False;
          V8_to_Loc(Loc, Adjacent);
          for V8 := 0 to 7 do
          begin
            Loc1 := Adjacent[V8];
            if (Loc1 >= 0) and (Map[Loc1] and fTerrain < fGrass) then
            begin
              IsCoastal := True;
              f1 := Formation[Loc1];
              if (f1 >= 0) and (f1 < maxCOD) and (OceanSize[f1] >= 8) and
                (OceanPresence[f1] and not (1 shl Me) <> 0) then
              begin
                IsPort := True;
                Break;
              end;
            end;
          end;
          if (City_CurrentUnitProject(cix) >= 0) and
            (RO.Model[City_CurrentUnitProject(cix)].Kind <> mkSettler) then
          begin
            I := nModelCategory - 1;
            while (I >= 0) and (City_CurrentUnitProject(cix) <> mixBest[I]) do
              Dec(I);
            IsUnitProjectObsolete := I < 0;
          end
          else
            IsUnitProjectObsolete := False;
          if RO.Government = gDespotism then
          begin
            nTownGuard := 0;
            for uix := 0 to RO.nUn - 1 do
              if (MyUnit[uix].mix = mixTownGuard) and (MyUnit[uix].Loc = Loc) then
                Inc(nTownGuard);
          end;

          iix := City_CurrentImprovementProject(cix);
          if (iix >= 0) and (iix < nWonder) or (iix = imPalace) or
            (iix = imShipComp) or (iix = imShipPow) or (iix = imShipHab) then
            City_OptimizeTiles(cix, rwMaxProd)
          else if Size < 8 then
            City_OptimizeTiles(cix, rwMaxGrowth)
          else
            City_OptimizeTiles(cix, rwForceProd);

          WillProduceColonyShip := False;
          ProduceShipPart := -1;
          for part := 0 to nShipPart - 1 do
            if ColonyShipPlan[part].cixProducing = cix then
            begin
              WillProduceColonyShip := True;
              ProduceShipPart := ShipImpIndex[part];
            end;

          if cix = cixNewCapital then
            City_StartImprovement(cix, imPalace)
          else if (iix >= 0) and (iix < nWonder) and ((1 shl iix) and
            WonderAvailable <> 0) then
          // complete wonder production first
          else if (mixProduce >= 0) and (City_CurrentUnitProject(cix) >= 0) and
            not IsUnitProjectObsolete and ((Flags and chProduction = 0) or
            (RO.Model[City_CurrentUnitProject(cix)].Cap[mcLine] > 0) and
            (mixCount[City_CurrentUnitProject(cix)] < RO.nCity * (2 + cix and 3))) then
          // complete unit production first
          else
          begin
            if ProduceShipPart >= 0 then
            begin
              if (Built[imGranary] = 0) and (Size < 10) and
                City_Improvable(cix, imGranary) then
                City_StartImprovement(cix, imGranary)
              else if (Built[imAqueduct] = 0) and City_Improvable(cix, imAqueduct) then
                City_StartImprovement(cix, imAqueduct)
              else if (Built[imAqueduct] > 0) and (Size < 12) and
                (AlgaeAvailable or (Project and (cpImp + cpIndex) =
                cpImp + imAlgae)) then
                City_StartImprovement(cix, imAlgae)
              else if (Built[imFactory] = 0) and City_Improvable(cix, imFactory) then
                City_StartImprovement(cix, imFactory)
              else if (Built[imPower] + Built[imHydro] + Built[imNuclear] = 0) and
                (City_Improvable(cix, imPower) or
                City_Improvable(cix, imHydro) or City_Improvable(cix, imNuclear)) then
              begin
                if City_Improvable(cix, imHydro) then
                  City_StartImprovement(cix, imHydro)
                else if City_Improvable(cix, imPower) then
                  City_StartImprovement(cix, imPower)
                else
                  City_StartImprovement(cix, imNuclear);
              end
              else if (Built[imMfgPlant] = 0) and City_Improvable(cix, imMfgPlant) then
                City_StartImprovement(cix, imMfgPlant)
              else if City_Improvable(cix, ProduceShipPart) then
                City_StartImprovement(cix, ProduceShipPart)
              else
                ProduceShipPart := -1;
            end;
            if ProduceShipPart < 0 then
            begin
              ProjectComplete :=
                not City_HasProject(cix) or (Flags and chProduction <> 0);
              HasSettler := False;
              for uix := 0 to RO.nUn - 1 do
                with MyUnit[uix] do
                  if (Loc >= 0) and (Home = cix) and
                    (MyModel[mix].Kind = mkSettler) then begin
                      HasSettler := True;
                      Break;
                    end;
              if ((RO.Government <> gDespotism) or (RO.nUn >= RO.nCity * 4)) and
                not IsResearched(adMassProduction) and (Built[imPalace] > 0) and
                (RO.Wonder[woZeus].CityID = WonderNotBuiltYet) and City_Improvable(cix, woZeus) then
                City_StartImprovement(cix, woZeus)
              else if (City_CurrentImprovementProject(cix) >= 0) and
                (City_CurrentImprovementProject(cix) < nWonder) then
              begin// wonder already built, try to switch to different one
                if (WonderAvailable and not WonderInWork > 0) and
                  (IsCoastal or (WonderAvailable and not WonderInWork and
                  not CoastalWonder > 0)) then
                begin
                  iix := ChooseWonderToBuild(WonderAvailable and not
                    WonderInWork, IsCoastal);
                  City_StartImprovement(cix, iix);
                  WonderInWork := WonderInWork or (1 shl iix);
                end
                else
                  City_StopProduction(cix);
              end
              else if (Built[imPalace] > 0) and (RO.NatBuilt[imSpacePort] = 0) and
                City_Improvable(cix, imSpacePort) then
                City_StartImprovement(cix, imSpacePort)
              else if Built[imPalace] + Built[imCourt] + Built[imTownHall] = 0 then
              begin
                if City_Improvable(cix, imCourt) then
                  City_StartImprovement(cix, imCourt)
                else
                  City_StartImprovement(cix, imTownHall);
              end
              else if not HasSettler and (RO.nUn >= RO.nCity * 4) then
              begin
                if ProjectComplete and (City_CurrentUnitProject(cix) <> 0) then
                begin
                  mix := RO.nModel - 1;
                  while RO.Model[mix].Kind <> mkSettler do
                    Dec(mix);
                  City_StartUnitProduction(cix, mix);
                end;
              end
              else if (RO.Government = gDespotism) and (nTownGuard < 2) and
                (nTownGuard * 2 + 3 < Size) then
              begin
                if ProjectComplete then
                  City_StartUnitProduction(cix, 2);
              end
              else if (RO.Government = gFundamentalism) and
                (Size >= 8) and (Built[imAqueduct] = 0) and
                City_Improvable(cix, imAqueduct) and (RO.nUn >= RO.nCity * 4) then
              begin
                if ProjectComplete then
                  City_StartImprovement(cix, imAqueduct);
              end
              else if ProjectComplete then
              begin // low prio projects
                ImportantCity := WillProduceColonyShip or (Built[imPalace] > 0);
                for iix := 0 to nWonder - 1 do
                  if Built[iix] > 0 then begin
                    ImportantCity := True;
                    Break;
                  end;
                City_GetReportNew(cix, Report);
                if (Report.Corruption >= 6) and (RO.nUn >= RO.nCity * 4) and
                  City_Improvable(cix, imCourt) then
                  City_StartImprovement(cix, imCourt)
                else if (Report.Production >= WonderProductionThreshold) and
                  (WonderAvailable and not WonderInWork > 0) and
                  (IsCoastal or (WonderAvailable and not WonderInWork and
                  not CoastalWonder > 0)) and (Random >=
                  (1 + WonderInclination) / (RO.nCity + WonderInclination)) then
                begin
                  iix := ChooseWonderToBuild(WonderAvailable and not
                    WonderInWork, IsCoastal);
                  City_StartImprovement(cix, iix);
                  WonderInWork := WonderInWork or (1 shl iix);
                end
                else if (ImportantCity or (Loc mod 9 = 0)) and
                  (Built[imWalls] = 0) and City_Improvable(cix, imWalls) then
                  City_StartImprovement(cix, imWalls)
                else if IsPort and (ImportantCity or (Loc mod 7 = 0)) and
                  (Built[imCoastalFort] = 0) and City_Improvable(cix, imCoastalFort) then
                  City_StartImprovement(cix, imCoastalFort)
            {else if (ImportantCity or (Loc mod 11=0)) and (Built[imMissileBat]=0)
              and City_Improvable(cix,imMissileBat) then
              City_StartImprovement(cix,imMissileBat)}
                else if IsPort and (not SpezializeShipProduction or
                  (F < 0) or (F >= maxCOD) or (ContinentPresence[F] = 1 shl Me)) and
                  (Built[imDockyard] = 0) and City_Improvable(cix, imDockyard) then
                  City_StartImprovement(cix, imDockyard)
                else if IsPort and (mixShip >= 0) and
                  (not SpezializeShipProduction or (F < 0) or
                  (F >= maxCOD) or (ContinentPresence[F] = 1 shl Me)) then
                  City_StartUnitProduction(cix, mixShip)
                else if (Built[imBarracks] + Built[imMilAcademy] = 0) and
                  City_Improvable(cix, imBarracks) then
                  City_StartImprovement(cix, imBarracks)
                else if mixProduce >= 0 then
                  City_StartUnitProduction(cix, mixProduce)
                else if City_HasProject(cix) then
                  City_StopProduction(cix);
              end;
            end;
          end;
          if (City_CurrentImprovementProject(cix) = imCourt) and
            (Built[imTownHall] > 0) and (Prod >= imp[imCourt].Cost *
            BuildCostMod[G.Difficulty[Me]] div 12 -
            (Imp[imTownHall].Cost * BuildCostMod[G.Difficulty[Me]] div 12) *
            2 div 3) then
            City_RebuildImprovement(cix, imTownHall)
          else if (RO.Government = gFundamentalism) and not WillProduceColonyShip then
            for iix := nWonder to nImp - 1 do
              if (Built[iix] > 0) and
                ((iix in [imTemple, imTheater, imCathedral, imColosseum,
                imLibrary, imUniversity, imResLab, imHarbor, imSuperMarket]) or
                (iix in [imFactory, imMfgPlant, imPower, imHydro, imNuclear]) and
                (Built[imRecycling] = 0)) then
              begin
                if City_RebuildImprovement(cix, iix) < rExecuted then
                  City_SellImprovement(cix, iix);
                Break;
              end;
        end;
end;

function TBarbarina.Barbarina_ChooseResearchAdvance: Integer;
var
  nPreq, rmix, rmixChosen, I, MaxWeight, MaxDefense, ChosenPreq: Integer;
  NeedSeaUnits, Ready: Boolean;
  ModelExists: set of 0..nModelCategory - 1;
  Known: array[0..nAdv - 1] of Integer;

  procedure ChoosePreq(ad: Integer);
  var
    I: Integer;
    PreqOk: Boolean;
  begin
    Assert(RO.Tech[ad] < tsApplicable);
    if Known[ad] = 0 then
    begin
      Known[ad] := 1;
      PreqOk := True;
      if not (ad in [adScience, adMassProduction]) and (RO.Tech[ad] < tsSeen) then
        for I := 0 to 1 do
          if (AdvPreq[ad, I] >= 0) and (RO.Tech[AdvPreq[ad, I]] < tsApplicable) then
          begin
            PreqOk := False;
            ChoosePreq(AdvPreq[ad, I]);
          end;
      if PreqOk then
      begin
        Inc(nPreq);
        if Random(nPreq) = 0 then
          ChosenPreq := ad;
      end;
    end;
  end;

begin
  // check military research
  rmixChosen := -1;
  ModelExists := [];
  for rmix := nResearchModel - 1 downto 0 do
    with ResearchModel[rmix] do
      if not (Category in ModelExists) and ((adStop < 0) or not
        IsResearched(adStop)) then
      begin
        MaxWeight := 0;
        case Domain of
          dGround:
          begin
            if IsResearched(adWarriorCode) then
              MaxWeight := 5;
            if IsResearched(adHorsebackRiding) then
              MaxWeight := 7;
            if IsResearched(adAutomobile) then
              MaxWeight := 10;
          end;
          dSea:
          begin
            if IsResearched(adMapMaking) then
              MaxWeight := 5;
            if IsResearched(adSeaFaring) then
              MaxWeight := 7;
            if IsResearched(adSteel) then
              MaxWeight := 9;
          end;
          dAir:
          begin
            if IsResearched(adFlight) then
              MaxWeight := 5;
            if IsResearched(adAdvancedFlight) then
              MaxWeight := 7;
          end;
        end;
        if Domain = dGround then
          MaxDefense := 2
        else
          MaxDefense := 3;
        if IsResearched(adSteel) then
          Inc(MaxDefense);
        Ready := (MaxWeight >= Weight) and (MaxDefense >= Cap[mcDefense]);
        if Ready then
          for I := 0 to nFeature - 1 do
            if (Cap[I] > 0) and (Feature[I].Preq <> preNone) and
              ((Feature[I].Preq < 0) or not IsResearched(Feature[I].Preq)) then begin
                Ready := False;
                Break;
              end;
        if Ready then
        begin
          for I := 0 to nUpgrade - 1 do
            if (Upgrades and (1 shl I) <> 0) and not
              IsResearched(Upgrade[Domain, I].Preq) then begin
                Ready := False;
                Break;
              end;
        end;
        if Ready then
        begin
          Include(ModelExists, Category);
          if not IsModelAvailable(rmix) then
            rmixChosen := rmix;
        end;
      end;
  if rmixChosen >= 0 then
    with ResearchModel[rmixChosen] do
    begin
      PrepareNewModel(Domain);
      for I := 0 to nFeature - 1 do
        if (I < 2) or (Cap[I] > 0) then
          SetNewModelFeature(I, Cap[I]);
      if RO.Wonder[woSun].EffectiveOwner = Me then
      begin
        //if Cap[mcWeapons]>=2*Cap[mcArmor] then
        //  SetNewModelFeature(mcFirst,1);
        if Cap[mcWeapons] >= Cap[mcArmor] then
          SetNewModelFeature(mcWill, 1);
      end;
      Result := adMilitary;
      Exit;
    end;

  NeedSeaUnits := True;
  I := 0;
  while (I < nResearchOrder) and (not NeedSeaUnits and (ResearchOrder[I] < 0) or
    IsResearched(Abs(ResearchOrder[I]))) do
      Inc(I);
  if I >= nResearchOrder then // list done, continue with future tech
  begin
    if Random(2) = 1 then
      Result := futArtificialIntelligence
    else
      Result := futMaterialTechnology;
  end
  else
  begin
    FillChar(Known, SizeOf(Known), 0);
    nPreq := 0;
    ChosenPreq := -1;
    ChoosePreq(Abs(ResearchOrder[I]));
    Assert(nPreq > 0);
    Result := ChosenPreq;
  end;
end;

function TBarbarina.Barbarina_WantCheckNegotiation(Nation: Integer): Boolean;
begin
  if (RO.Tech[adTheRepublic] < tsSeen) and (RO.Tech[adTheology] >= tsApplicable) and
    (RO.Tech[adGunPowder] >= tsApplicable) and
    (RO.EnemyReport[Nation].Tech[adTheRepublic] >= tsApplicable) then
    Result := True
  else
    Result := False;
end;

procedure TBarbarina.Barbarina_DoCheckNegotiation;
begin
  if RO.Tech[adTheRepublic] >= tsSeen then
    Exit; // default reaction
  if MyLastAction = scContact then
  begin
    MyAction := scDipOffer;
    MyOffer.nDeliver := 1;
    MyOffer.nCost := 1;
    if (RO.Tech[adTheology] >= tsApplicable) and
      (RO.EnemyReport[Opponent].Tech[adTheology] < tsSeen) then
      MyOffer.Price[0] := opTech + adTheology
    else
      MyOffer.Price[0] := opChoose;
    MyOffer.Price[1] := opTech + adTheRepublic;
  end
  else if OppoAction = scDipAccept then
  else if OppoAction = scDipOffer then
  begin
    if (OppoOffer.nDeliver = 1) and (OppoOffer.Price[0] = opTech + adTheRepublic) and
      ((OppoOffer.nCost = 0) or (OppoOffer.nCost = 1) and
      (OppoOffer.Price[1] and opMask = opTech) and
      (RO.Tech[OppoOffer.Price[1] - opTech] >= tsApplicable)) then
      MyAction := scDipAccept
    else
      MyAction := scDipBreak;
  end
  else if OppoAction <> scDipBreak then
    MyAction := scDipBreak;
end;

function TBarbarina.Barbarina_WantNegotiation(Nation: Integer;
  NegoTime: TNegoTime): Boolean;
var
  uix, TestLoc, V8: Integer;
  Adjacent: TVicinity8Loc;
begin
  Result := False;
  case NegoTime of
    EnemyCalled:
      Result := False;
    EndOfTurn:
      Result := False;
    BeginOfTurn:
      if RO.Turn >= RO.LastCancelTreaty[Nation] + CancelTreatyTurns then
      begin
        if (RO.Turn and 3 = (Nation + $F - Me) and 3) and
          (RO.Treaty[Nation] > trPeace) then
        begin
          DebugMessage(1, 'End alliance/friendly contact with P' + Char(48 + Nation));
          NegoCause := CancelTreaty;
          Result := True;
        end
        else if RO.Treaty[Nation] = trPeace then
        begin // declare war now?
          for uix := 0 to RO.nUn - 1 do
            with MyUnit[uix] do
              if (Loc >= 0) and (MyModel[mix].Attack > 0) then
              begin
                V8_to_Loc(Loc, Adjacent);
                for V8 := 0 to 7 do
                begin
                  TestLoc := Adjacent[V8];
                  if (TestLoc >= 0) and (RO.Territory[TestLoc] = Nation) and
                    ((Map[TestLoc] and fTerrain >= fGrass) or
                    (Master >= 0) or (MyModel[mix].Domain <> dGround)) and
                    ((Map[TestLoc] and fTerrain < fGrass) or
                    (MyModel[mix].Domain <> dSea)) then
                  begin
                    DebugMessage(1, 'Declare war on P' + Char(48 + Nation));
                    NegoCause := CancelTreaty;
                    Result := True;
                    Exit;
                  end;
                end;
              end;
        end;
      end;
  end;
end;

procedure TBarbarina.Barbarina_DoNegotiation;
begin
  if OppoAction = scDipStart then
  begin
    if NegoCause = CancelTreaty then
      MyAction := scDipCancelTreaty;
  end;
end;

procedure TBarbarina.MakeColonyShipPlan;
var
  I, V21, V21C, CityLoc, Loc1, Part, cix, BestValue, TestValue, FoodCount,
  ProdCount, ProdExtra, Score, BestScore: Integer;
  Tile: Cardinal;
  Ok, Check: Boolean;
  Radius, RadiusC: TVicinity21Loc;
begin
  for Part := 0 to nShipPart - 1 do
  begin
    ColonyShipPlan[Part].cixProducing := -1;
    ColonyShipPlan[Part].nLocResource := 0;
    ColonyShipPlan[Part].nLocFoundCity := 0;
  end;
  if RO.Tech[adMassProduction] >= tsApplicable then // able to recognize ressources yet
  begin
    // Check already existing cities
    for cix := 0 to RO.nCity - 1 do
      with MyCity[cix] do
        if Loc >= 0 then
        begin
          V21_to_Loc(Loc, Radius);
          for V21 := 1 to 26 do
          begin
            Loc1 := Radius[V21];
            if Loc1 >= 0 then
            begin
              Tile := RO.Map[Loc1];
              if Tile and fModern <> 0 then
              begin
                Part := (Tile and fModern) shr 25 - 1;
                if RO.Ship[Me].Parts[Part] < ShipNeed[Part] then
                  // not enough of this kind already
                begin
                  Ok := True;
                  if ColonyShipPlan[Part].cixProducing >= 0 then
                  begin // another city is already assigned to this ship part, choose one of the two
                    TestValue := (ID and $FFF) shl 4 + ((ID shr 12) + 15 - Me) and $F;
                    BestValue :=
                      (MyCity[ColonyShipPlan[Part].cixProducing].ID and $FFF) shl
                      4 + ((MyCity[ColonyShipPlan[Part].cixProducing].ID shr 12) +
                      15 - Me) and $F;
                    if TestValue <= BestValue then
                      Ok := False;
                  end;
                  if Ok then
                    ColonyShipPlan[part].cixProducing := cix;
                end;
              end;
            end;
          end;
        end;

    // for parts without existing city, look for location of city to found
    Check := False;
    for Part := 0 to nShipPart - 1 do
      if (RO.Ship[Me].Parts[Part] < ShipNeed[Part]) // not enough of this kind already
        and (ColonyShipPlan[Part].cixProducing < 0) then begin // no city to produce
          Check := True;
          Break;
        end;
    if Check then
    begin
      for Loc1 := 0 to MapSize - 1 do
      begin
        Tile := RO.Map[Loc1];
        if Tile and fModern <> 0 then
        begin
          Part := (Tile and fModern) shr 25 - 1;
          if ColonyShipPlan[Part].nLocResource < maxModern then
          begin
            ColonyShipPlan[Part].LocResource[ColonyShipPlan[Part].nLocResource] := Loc1;
            Inc(ColonyShipPlan[Part].nLocResource);
          end;
        end;
      end;
      for Part := 0 to nShipPart - 1 do
        if (RO.Ship[Me].Parts[Part] < ShipNeed[Part]) // not enough of this kind already
          and (ColonyShipPlan[Part].cixProducing < 0) // no city to produce
          and (ColonyShipPlan[Part].nLocResource > 0) then // resource is known
        begin
          for I := 0 to ColonyShipPlan[Part].nLocResource - 1 do
          begin
            BestScore := 0;
            V21_to_Loc(ColonyShipPlan[Part].LocResource[I], Radius);
            for V21 := 1 to 26 do
            begin // Check all potential cities in range
              CityLoc := Radius[V21];
              if CityLoc >= 0 then
              begin
                Tile := RO.Map[CityLoc];
                if (Tile and fTerrain <> fUNKNOWN) and
                  ((Tile and fTerrain = fForest) or
                  (Tile and fTerrain = fSwamp) or
                  (Terrain[Tile and fTerrain].IrrEff > 0)) then
                begin
                  FoodCount := 0;
                  ProdCount := 0;
                  ProdExtra := 0;
                  V21_to_Loc(CityLoc, RadiusC);
                  for V21C := 1 to 26 do
                  begin
                    Loc1 := RadiusC[V21C];
                    if Loc1 >= 0 then
                    begin
                      case RO.Map[Loc1] and (fTerrain or fSpecial) of
                        fGrass, fGrass + fSpecial1, fSwamp: Inc(FoodCount);
                        fHills, fHills + fSpecial1: Inc(ProdCount);
                        fShore + fSpecial1, fDesert + fSpecial1, fPrairie + fSpecial1,
                        fForest + fSpecial1:
                          Inc(FoodCount, 2);
                        fSwamp + fSpecial1, fShore + fSpecial2, fDesert + fSpecial2,
                        fPrairie + fSpecial2, fTundra + fSpecial2, fArctic + fSpecial1,
                        fHills + fSpecial2, fMountains + fSpecial1:
                        begin
                          Inc(ProdCount);
                          Inc(ProdExtra);
                        end;
                      end;
                    end;
                  end;
                  if FoodCount = 0 then
                    Score := 0
                  else
                  begin
                    if ProdCount > 7 then
                      ProdCount := 7;
                    if FoodCount < 5 then
                      Dec(ProdCount, 5 - FoodCount);
                    Score := ProdCount * 4 + ProdExtra * 8 + FoodCount;
                    Score := Score shl 8 + ((CityLoc xor Me) * 4567) mod 251;
                    // some unexactness, random but always the same for this tile
                  end;
                  if Score > BestScore then
                  begin
                    BestScore := Score;
                    ColonyShipPlan[Part].LocFoundCity[
                      ColonyShipPlan[Part].nLocFoundCity] :=
                      CityLoc;
                  end;
                end;
              end;
            end;
            if BestScore > 0 then
              Inc(ColonyShipPlan[Part].nLocFoundCity);
          end;
        end;
    end;
  end;
end;

end.
