//Random storehouse placement. Mirror placement of storehouses.
//The original script was written by Thunder.
//Rewriten by Ritter des Todes.
//27.02.2024.
//Developed for r6720.

//Description.
//Use InitPlayer function to set randomising limits, serfs and workers count.
//Use BindPlayers to bind 2 players to make them have mirrored storehouses.
//Change default values for wares in constants if you need it.
//That's it. Enjoy.

//TODO.
//Make user functions to give different amount of wares to players to storehouse. For power locs for example.
//Make user functions to add different types of units at start.

type Resource =
record
  WareType: integer;
  Count: integer;

end;

type Player =
record
  XMIN: integer;
  XMAX: integer;
  YMIN: integer;
  YMAX: integer;

  WorkersCount: integer;
  SerfsCount: integer;

  X: integer;
  Y: integer;

  SlowBuildOrder: boolean;

  Resourses: array of Resource;
  UnlockedHouseTypes: array of integer;

  SightRadius: integer;
end;

type Binding =
record
  First: integer;
  Second: integer;
end;

const
  UT_SERF          =  0;
  UT_LABORER       =  9;


  WT_TREE_TRUNK    =  0;
  WT_STONES        =  1;
  WT_TIMBER        =  2;
  WT_GOLD          =  7;
  WT_WINE          =  8;
  WT_LOAVES        = 10;
  WT_SAUSAGES      = 13;
  WT_FISH          = 27;

  HT_STOREHOUSE    = 11;

  CIN= 30;  //Cinematics length.
var
  CoalPlacementAllowed: boolean;
  Players: array [0..7] of Player;
  Bindings: array of Binding;

function FindBoundPlayer(playerId: integer): integer;
var
  i: integer;

begin
  result := -1;

  for i := 0 to Length(Bindings) - 1 do
  begin
    if (Bindings[i].First = playerId)then
    begin
      result:= Bindings[i].Second;
      exit;
    end;
    if (Bindings[i].Second = playerId)then
    begin
      result:= Bindings[i].First;
      exit;
    end;
  end;
end;

procedure BindPlayers(first, second: integer);
var
  count: integer;
  newBinding: Binding;
begin
  //Player can be bound only once.
  if ((FindBoundPlayer(first) <> -1) or (FindBoundPlayer(second) <> -1)) then
  begin
    exit;
  end;

  count := Length(Bindings);
  newBinding.First := first;
  newBinding.Second := second;
  setLength(Bindings, count + 1);
  Bindings[count] := newBinding;
end;

procedure InitPlayer(playerId, xMin, xMax, yMin, yMax, workersCount, serfsCount: integer);
begin
  Players[playerId].XMIN:= xMin;
  Players[playerId].XMAX:= xMax;
  Players[playerId].YMIN:= yMin;
  Players[playerId].YMAX:= yMax;

  Players[playerId].WorkersCount:= workersCount;
  Players[playerId].SerfsCount:= serfsCount;
end;

procedure AddPlayerResource(playerId, wareType, count: integer);
begin
  setLength(Players[playerId].Resourses, Length(Players[playerId].Resourses) + 1);
  Players[playerId].Resourses[High(Players[playerId].Resourses)].WareType := wareType;
  Players[playerId].Resourses[High(Players[playerId].Resourses)].Count := count;
end;

procedure AddPlayerUnlockedHouseType(playerId, houseType: integer) ;
begin
  setLength(Players[playerId].UnlockedHouseTypes, Length(Players[playerId].UnlockedHouseTypes) + 1);
  Players[playerId].UnlockedHouseTypes[High(Players[playerId].UnlockedHouseTypes)] := houseType;
end;

procedure SetSlowBuildOrder(playerId: integer; value : boolean);
begin

  Players[playerId].SlowBuildOrder := value;
end;

procedure AddPlayerResources(playerId, stone, tree, timber, gold, wine, bread, sausage, fish: integer);
begin

  AddPlayerResource(playerId, WT_STONES,stone);
  AddPlayerResource(playerId, WT_TREE_TRUNK,tree);
  AddPlayerResource(playerId, WT_GOLD,gold);
  AddPlayerResource(playerId, WT_TIMBER,timber);
  AddPlayerResource(playerId, WT_WINE,wine);
  AddPlayerResource(playerId, WT_LOAVES,bread);
  AddPlayerResource(playerId, WT_SAUSAGES,sausage);
  AddPlayerResource(playerId, WT_FISH,fish);

end;

procedure InitBoundPlayers(fId, fxMin, fxMax, fyMin, fyMax, sId, sxMin, sxMax, syMin, syMax : integer);
var
  serfs: integer;
  workers: integer;

  stone: integer;
  tree: integer;
  timber: integer;
  gold: integer;
  wine: integer;
  bread: integer;
  sausage: integer;
  fish: integer;

  slow: boolean;
begin

  case States.KamRandomI(4) of
    0:
      begin
        serfs := 7;
        workers := 6;
      end;
    1:
      begin
        serfs := 6;
        workers := 5;
      end;
    2:
      begin
        serfs := 5;
        workers := 4;
      end;
    3:
      begin
        serfs := 4;
        workers := 3;
      end;
  end;

  slow := States.KamRandomI(5) = 0;

  if (slow) then
  begin
    SetSlowBuildOrder(fId, true);
    SetSlowBuildOrder(sId, true);
  end;

  case States.KamRandomI(2) of
    0:
      begin
        if (not slow) then
        begin
          AddPlayerUnlockedHouseType(fId, 14);
          AddPlayerUnlockedHouseType(sId, 14);
        end;

      end;
    1:
      begin
        AddPlayerUnlockedHouseType(fId, 9);
        AddPlayerUnlockedHouseType(sId, 9);
      end;
  end;

  if  (States.KamRandomI(2) = 0) then
  begin
    AddPlayerUnlockedHouseType(fId, 8);
    AddPlayerUnlockedHouseType(sId, 8);
  end;

  stone:= 66 + States.KamRandomI(14);
  tree := 4 + States.KamRandomI(4);
  timber:= 34 + States.KamRandomI(10);
  gold:= 56 + States.KamRandomI(21);
  wine:= 13 + States.KamRandomI(53);
  bread:= 9 + States.KamRandomI(37);
  sausage:= 4 + States.KamRandomI(27);
  fish:= 0 + States.KamRandomI(26);

  AddPlayerResources(fId, stone, tree, timber, gold, wine, bread, sausage, fish);
  AddPlayerResources(sId, stone, tree, timber, gold, wine, bread, sausage, fish);

  InitPlayer(fId, fxMin,fxMax,fyMin,fyMax,workers,serfs);
  InitPlayer(sId, sxMin,sxMax,syMin,syMax,workers,serfs);
  BindPlayers(fId,sId);

end;

procedure InitPlayers();
begin

  InitBoundPlayers(0,15,49,13,47,
                   4,15,49,208,242);
  InitBoundPlayers(1, 79,113,25,59,
                   5, 79,113,196,230);
  InitBoundPlayers(2, 143,177,25,59,
                   6, 144,178,195,229);
  InitBoundPlayers(3, 207,241,13,47,
                   7, 207,241,208,242);

  Players[0].SightRadius := 50;
  Players[1].SightRadius := 50;
  Players[2].SightRadius := 50;
  Players[3].SightRadius := 50;
  Players[4].SightRadius := 50;
  Players[5].SightRadius := 50;
  Players[6].SightRadius := 50;
  Players[7].SightRadius := 50;

end;

function IsPossibleToMakeStoreHouse(playerId, x,y: integer): boolean;
var
  houseId: integer;
begin
  houseId := Actions.GiveHouse(playerId,HT_STOREHOUSE,x,y);
  if (houseId <> -1) then
  begin
    result := true;
    Actions.HouseDestroy(houseId,True);
  end else
  begin
    result := false;
  end;
end;

function IsPossibleToMakeRoad(playerId, x,y: integer):boolean;
var
  isRoadPlaced: boolean;
begin
  isRoadPlaced := Actions.GiveRoad(playerId,x,y);
  if (isRoadPlaced) then
  begin
    result := true;
    Actions.RemoveRoad(x,y);
  end else
  begin
    result := false;
  end;
end;

function CheckSidesTileType(x,y: integer): boolean;
begin
  if ((States.MapTileType(x,y) = 139) or
      (States.MapTileType(x,y) = 167) or
      (States.MapTileType(x,y) = 168) or
      (States.MapTileType(x,y) = 169) or
      (States.MapTileType(x,y) = 170) or
      (States.MapTileType(x,y) = 172) or
      (States.MapTileType(x,y) = 173) or
      (States.MapTileType(x,y) = 174) or
      (States.MapTileType(x,y) = 175)) then
  begin
    result := true;
  end else
  begin
    result := false;
  end;
end;

function CheckEntranceTileType(x,y: integer): boolean;
begin
  if ((States.MapTileType(x,y) = 155) or
      (States.MapTileType(x,y) = 154) or
      (States.MapTileType(x,y) = 153) or
      (States.MapTileType(x,y) = 152)) then
  begin
    result := true;
  end else
  begin
    result := false;
  end;
end;

//Go spiral way and add citizens. (Actually it's not spiral but I like the result).
procedure AddCitizens(playerId,x,y: integer);
var
  axis : string;
  xStep : integer;
  yStep : integer;
  radius : integer;
  currentX : integer;
  currentY: integer;

  dirrectionCounter: integer;
begin

  currentX := x;
  currentY := y;

  xStep := 1;
  yStep := 1;

  axis:= 'Y';
  dirrectionCounter:= 0;

  //27 is just wonderful number. After all citizens will be added loop will do nothing and end fast anyway.
  for radius := 1 to 27 do
  begin
    while (States.StatCitizenCount(playerId) < Players[playerId].SerfsCount + Players[playerId].WorkersCount) do
    begin

      if (axis = 'Y') then
      begin

        currentY := currentY + yStep;
        if ((currentY >= (y + radius)) or (currentY <= (y - radius))) then
        begin

          xStep := -xStep;
          axis := 'X';
          Inc(dirrectionCounter)
        end;

      end else
      begin

        currentX := currentX + xStep;
        if ((currentX >= (x + radius)) or (currentX <= (x - radius))) then
        begin

          yStep := -yStep;
          axis := 'Y';
          Inc(dirrectionCounter)
        end;
      end;

      if (States.StatCitizenCount(playerId) < Players[playerId].SerfsCount) then
      begin
        Actions.GiveUnit(playerId, UT_SERF, currentX, (currentY), 5);
      end else
      begin
        Actions.GiveUnit(playerId, UT_LABORER, currentX, (currentY), 5);
      end;

      //We go to 5 dirrections before we increse radius.
      if (dirrectionCounter > 5) then
      begin
        dirrectionCounter:= 0;
        break;
      end;
    end;
  end;
end;

function GetTreesCount(x,y: integer): integer;
var
  i: integer;
  j: integer;

  objectType : integer;
begin

  // This would be the TREECOUNTER before the Stroehouse is placed, but it is not working atm...how hard can it be?
	for i:= (x-1) to (x+1) do
		begin

    for j:= (y-2) to y do
			begin
				objectType:= States.MapTileObject(i,j);
				case objectType of
					88,89,90,91,92,93,94,95,96,97,98,99,100,
          101,102,103,104,105,106,107,108,109,110,
          111,112,113,114,115,116,117,118,119,120,
          121,123,124,125,49 ,150,151,152,153,154,
          155,156,157,158,159,160,161,162,163,164,
          165,166,167,168,169,170,171,172:
          inc(result);
				else end;
		end;
	end;
end;

function TryToSetStoreHouse(playerId,x,y : integer): boolean;
var
  i: integer;
  treesCount: integer;
begin

  //Count trees before placing storehouse.
  treesCount := GetTreesCount(x,y);

  result := true;
  if (not IsPossibleToMakeStoreHouse(playerId, x,y)) then
  begin
    result := false;
    exit;
  end;

  //Check tiles at sides and in front of storehouse that we want to place.
  if ((not IsPossibleToMakeRoad(playerId, x  ,y+1)) or (not IsPossibleToMakeRoad(playerId, x+1,y+1)) or
      (not IsPossibleToMakeRoad(playerId, x-1,y+1)) or (not IsPossibleToMakeRoad(playerId, x-2,y  )) or
      (not IsPossibleToMakeRoad(playerId, x-2,y+1)) or (not IsPossibleToMakeRoad(playerId, x-2,y-1)) or
      (not IsPossibleToMakeRoad(playerId, x-2,y-2)) or (not IsPossibleToMakeRoad(playerId, x+2,y  )) or
      (not IsPossibleToMakeRoad(playerId, x+2,y+1)) or (not IsPossibleToMakeRoad(playerId, x+2,y-1)) or
      (not IsPossibleToMakeRoad(playerId, x+2,y-1))) then
  begin
    result := false;
    exit;
  end;

  //Check walk possibility.
  if  ((not States.ConnectedByWalking(x,y+1,x-1,y+1)) or (not States.ConnectedByWalking(x,y+1,x+1,y+1)) or
       (not States.ConnectedByWalking(x,y+1,x+2,y+1)) or (not States.ConnectedByWalking(x,y+1,x+2,y  )) or
       (not States.ConnectedByWalking(x,y+1,x+2,y-1)) or (not States.ConnectedByWalking(x,y+1,x-1,y+2)) or
       (not States.ConnectedByWalking(x,y+1,x+1,y+2)) or (not States.ConnectedByWalking(x,y+1,x  ,y+2))) then
  begin
    result := false;
    exit;
  end;

  if (not CoalPlacementAllowed) then
  begin
    if (CheckEntranceTileType(x-1,y) or CheckEntranceTileType(x-1,y-2) or CheckEntranceTileType(x+1,y) or
        CheckEntranceTileType(x+1,y-2)) then
    begin
      result := false;
      exit;
    end;
  end;

  if (CheckSidesTileType(x-1,y-3) or CheckSidesTileType(x+1,y-3) or CheckSidesTileType(x-2,y-3) or
      CheckSidesTileType(x+2,y-3)) then
  begin
    result := false;
    exit;
  end;

  //This place is ok. We start placing storehouse here.
  Actions.GiveHouse(playerId,HT_STOREHOUSE,x,y);

  //Roads in front of storehouse.
  Actions.GiveRoad(playerId,x  ,y+1);
	Actions.GiveRoad(playerId,x+1,y+1);
	Actions.GiveRoad(playerId,x-1,y+1);

  for i:= 0 to High(Players[playerId].Resourses) do
  begin
    Actions.GiveWares(playerId,Players[playerId].Resourses[i].WareType,Players[playerId].Resourses[i].Count);
  end;

  //The bonus tree trunks.
	Actions.GiveWares(playerId,WT_TREE_TRUNK,treesCount);

  //Remove the object in front of the storehouse.
	Actions.MapTileObjectSet(x,(y+1),255);
	Actions.MapTileObjectSet(x+1,(y+1),255);
	Actions.MapTileObjectSet(x-1,(y+1),255);

  if (Players[playerId].SlowBuildOrder) then
  begin
    Actions.HouseAllow(playerId, 27, False);  //Inn.
    Actions.HouseAllow(playerId, 14, False);  //Quarry.
    Actions.HouseAllow(playerId, 9, False);  //Woodcutter.
  end;

  for i:= 0 to High(Players[playerId].UnlockedHouseTypes) do
  begin
    Actions.HouseAllow(playerId,Players[playerId].UnlockedHouseTypes[i], true);
    Actions.HouseUnlock(playerId,Players[playerId].UnlockedHouseTypes[i]);
  end;

  //Reveal huge area for vision
	//Actions.FogRevealCircle(playerId,x,y,RADD); 		//set vision after the store is placed

  AddCitizens(playerId, x, y);

  Players[playerId].X := x;
  Players[playerId].Y := y;
end;

procedure PlaceRandomStorehouse(id : integer);
var
  wasSet: boolean;
  x: integer;
  y: integer;
  boundPlayer: integer;
begin

  wasSet:= false;

  while (not wasSet) do
  begin

    x := 0;
    y := 0;
    boundPlayer := FindBoundPlayer(id);

    //If there is a bound player and storehouse for this player was placed we place storehouse using this coordinates.
    if (boundPlayer <> -1) then
    begin

      if ((Players[boundPlayer].X > 0) and (Players[boundPlayer].Y > 0))then
      begin

        x := Players[id].XMIN + (Players[boundPlayer].X - Players[boundPlayer].XMIN);
        y := Players[id].YMIN + (Players[boundPlayer].Y - Players[boundPlayer].YMIN);
      end;
    end;

    //If there is no bound player or bound player still has no storehouse placed.
    if ((x = 0) and (y = 0))then
    begin
      x := Players[id].XMIN+ (States.KamRandomI((Players[id].XMAX-Players[id].XMIN)))
      y := Players[id].YMIN+ (States.KamRandomI((Players[id].YMAX-Players[id].YMIN)));
    end;

    if (TryToSetStoreHouse(id,x,y)) then
    begin
      wasSet:=true;
    end;
  end;
end;

procedure ShareFog(playerId: integer);
var
  i: integer;
begin
  for i:= 0 to High(Players) do
  begin
    if ((i <> playerId) and States.PlayerEnabled(i) and (States.PlayerAllianceCheck(i, playerId))) then
    begin
      Actions.FogRevealCircle(i,Players[playerId].X,Players[playerId].Y,Players[playerId].SightRadius);
    end;
  end;
end;

//Events------------------------------------------------------------------------

procedure OnHouseBuilt(aHouse: Integer);
begin

  if (not Players[States.HouseOwner(aHouse)].SlowBuildOrder) then
  begin
    exit;
  end;

  if States.HouseType(aHouse) = 13 then //unlock inn after school
    Actions.HouseAllow(States.HouseOwner(aHouse), 27, True);
  if States.HouseType(aHouse) = 27 then //unlock quarry
    Actions.HouseAllow(States.HouseOwner(aHouse), 14, True);
  if States.HouseType(aHouse) = 14 then //unlock cutter
    Actions.HouseAllow(States.HouseOwner(aHouse), 9, True);
end;

procedure OnMissionStart;
var
  i: integer;
begin
  InitPlayers();

  CoalPlacementAllowed := true;

  for i:= 0 to High(Players) do
  begin
    if (States.PlayerEnabled(i)) then
    begin
      PlaceRandomStorehouse(i);
    end;
  end;

  for i:= 0 to High(Players) do
  begin
    if (States.PlayerEnabled(i)) then
    begin
      Actions.FogCoverAll(i);
    end;
  end;

  for i:= 0 to High(Players) do
  begin
    if (States.PlayerEnabled(i)) then
    begin
      Actions.FogRevealCircle(i,Players[i].X,Players[i].Y,Players[i].SightRadius);
      ShareFog(i);
    end;
  end;

  Actions.PlayWAVLooped(-1, 'Desert', 0.15);
  Actions.PlayWAVLooped(-1, 'Desert2', 1);

end;

procedure OnTick;
var i: Integer;
begin

	if States.GameTime = 2 then
	begin

		for i := 0 to High(Players) do
    begin

			if States.PlayerEnabled(i) then
      begin
				Actions.CinematicStart(i);
      end;
    end;
	end;


	if States.GameTime = 10 then
	begin

		for i := 0 to High(Players) do
    begin

			if (States.PlayerEnabled(i) and (Players[i].X > 0) and (Players[i].Y > 0) )then
      begin
				Actions.CinematicPanTo(i,Players[i].X,Players[i].Y,4);
      end;
    end;
	end;

	if States.GameTime = CIN then
	begin

		for i := 0 to High(Players) do
    begin

			if States.PlayerEnabled(i) then
			begin;
      	Actions.CinematicEnd(i);
        Actions.GroupKillAll(States.GroupAt(63,10), true);
        Actions.GroupKillAll(States.GroupAt(97,12), true);
        Actions.GroupKillAll(States.GroupAt(147,10), true);
        Actions.GroupKillAll(States.GroupAt(191,8), true);
        Actions.GroupKillAll(States.GroupAt(66,251), true);
        Actions.GroupKillAll(States.GroupAt(100,246), true);
        Actions.GroupKillAll(States.GroupAt(162,243), true);
        Actions.GroupKillAll(States.GroupAt(198,252), true);
      end;
    end;
	end;

end;


