{$I PTLUtils.script}

{$EVENT evtMissionStart:OnMissionStart_Attacks}

var militaryUnitTypes: TKMUnitTypeSet;

function GetClosestHousePlanIdFromGroupInRangeBasedOnGroupType(groupId: integer): integer;
var groupType: TKMGroupType;
var range: Rectangle;
begin
	groupType := States.GroupTypeEx(groupId);
	if(groupType = gtRanged) then 
		begin
			range := GetSquareAreaAroundGroupLeader(groupId, 6);	
		end
	else
		begin
			range := GetSquareAreaAroundGroupLeader(groupId, 2);
		end;
	
	Result := GetClosestHousePlanIdFromGroupInRange(groupId, range);
end;	


procedure AttackClosestHousePlanIdForAllPlayerGroups(playerId: integer);
var i: integer;
var groupIds: array of integer;
var closestHousePlanId: integer;
begin
	groupIds := States.PlayerGetAllGroups(playerId);
	
	for i := 0 to Length(groupIds) - 1 do
		begin
			closestHousePlanId := GetClosestHousePlanIdFromGroupInRangeBasedOnGroupType(groupIds[i]);
			if(closestHousePlanId <> -1) then
				begin
					Actions.GroupOrderAttackHouse(groupIds[i], closestHousePlanId);
				end;
		end;
end;
		

function RemoveDefencePositions(playerId: integer; defencePositionsUIDSToRemoveArray: array of integer): array of integer;
var i: integer;
begin
	for i := 0 to Length(defencePositionsUIDSToRemoveArray) - 1 do
		begin
			Actions.AIDefencePositionRemoveByUID(playerId, defencePositionsUIDSToRemoveArray[i]);
		end;
	SetLength(defencePositionsUIDSToRemoveArray, 0);
	Result := defencePositionsUIDSToRemoveArray;
end;	

function ShouldFinishFirstAttackPhase(playerId: integer; attackStartedTime: integer; maximumFirstAttackPhaseTimeDuration: integer; defencePositionsFirstAttackPhaseArray: array of integer): boolean;
var i: integer;
var shouldFinishFirstPhase: boolean;
var groupPosition: TKMPoint;
var defencePosition: TKMDefencePositionInfo;
var unitId: integer;
begin
	shouldFinishFirstPhase := true;
	for i := 0 to Length(defencePositionsFirstAttackPhaseArray) - 1 do
		begin
			defencePosition := States.AIDefencePositionGetByIndex(playerId,GetIndexInDefencePositionsArray(playerId, defencePositionsFirstAttackPhaseArray[i]));
			if(defencePosition.GroupId = -1) then
				begin
					shouldFinishFirstPhase := false;
				end
			else 
				begin
					unitId := States.GroupMember(defencePosition.GroupID,0);
					if(unitId = -1) then
						begin
							Continue;
						end;
					groupPosition := States.UnitPosition(unitId);
					if(not ComparePoints(groupPosition,Utils.KMPoint(defencePosition.X, defencePosition.Y))) then
						begin
							shouldFinishFirstPhase := false;
						end;
				end;
		end;
	if(attackStartedTime + maximumFirstAttackPhaseTimeDuration <= States.GameTime) then
		begin
			shouldFinishFirstPhase := true;
		end;		
		
	Result := shouldFinishFirstPhase;
end;

function PlaceDefencePositionsForFirstAttackPhase(playerId: integer; defencePositionsToReAddArray: array of TKMDefencePositionInfo; firstAttackPhaseDataArray: array of FirstAttackPhaseData; defencePositionsUIDSFirstAttackPhaseArray: array of integer): array of integer;
var i: integer;
var defencePosition: TKMDefencePositionInfo;
var newDefencePositionUID: integer;
var newDefencePosition: TKMDefencePositionInfo;
begin
	for i := 0 to Length(defencePositionsToReAddArray) - 1 do
		begin
			defencePosition := defencePositionsToReAddArray[i];
			defencePosition.X := firstAttackPhaseDataArray[i].firstAttackPhasePosition.X;
			defencePosition.Y := firstAttackPhaseDataArray[i].firstAttackPhasePosition.Y;
			defencePosition.Dir := firstAttackPhaseDataArray[i].firstAttackPhaseDirection;
			newDefencePositionUID := Actions.AIDefencePositionAddEx(playerId, States.StatAIDefencePositionsCount(playerId), defencePosition);
			newDefencePosition := States.AIDefencePositionGetByIndex(playerId, GetIndexInDefencePositionsArray(playerId, newDefencePositionUID));
			defencePositionsUIDSFirstAttackPhaseArray := AddElementToIntegerArray(defencePositionsUIDSFirstAttackPhaseArray, newDefencePositionUID);
		end;
	Result := defencePositionsUIDSFirstAttackPhaseArray;
end;	

function RemoveDefencePositionsUntilAttackIsOver(playerId: integer; attackGroupsArray: array of GroupData; defencePositionsToReAddArray: array of TKMDefencePositionInfo): array of TKMDefencePositionInfo;
var i: integer;
var defencePosition: TKMDefencePositionInfo;
begin
	for i := 0 to States.StatAIDefencePositionsCount(playerId) - 1 do
		begin
			defencePosition := States.AIDefencePositionGetByIndex(playerId, i);		
			if(GetIndexInGroupDataArray(attackGroupsArray, defencePosition.GroupID) <> -1) then
				begin
					defencePositionsToReAddArray := AddElementToTKMDefencePositionInfoArray(defencePositionsToReAddArray, defencePosition);
				end;
		end;
	for i := 0 to Length(defencePositionsToReAddArray) - 1 do
		begin
			Actions.AIDefencePositionRemoveByUID(playerId, defencePositionsToReAddArray[i].UID);
		end;
	Result := defencePositionsToReAddArray;
end;	


function ReAddRemovedDefencePositions(playerId: integer; defencePositionsToReAddArray: array of TKMDefencePositionInfo): array of TKMDefencePositionInfo;
var i: integer;
begin
	for i := 0 to Length(defencePositionsToReAddArray) - 1 do
		begin
			Actions.AIDefencePositionAddEx(playerId, States.StatAIDefencePositionsCount(playerId), defencePositionsToReAddArray[i]);
		end;
	SetLength(defencePositionsToReAddArray, 0);
	Result := defencePositionsToReAddArray;
end;	

function AddGroupIdsToAttackArray(attackGroupsArray: array of GroupData; playerId: integer;  meleeGroupCount: integer; antiHorseGroupCount: integer; rangedGroupCount: integer; mountedGroupCount: integer): array of GroupData;
var attackGroupData: GroupData; 
var rangedGroupAddedCount: integer;
var mountedGroupAddedCount: integer;
var antiHorseGroupAddedCount: integer;
var meleeGroupAddedCount: integer;
var defencePosition: TKMDefencePositionInfo;
var i: integer;
begin
	meleeGroupAddedCount := 0;
	antiHorseGroupAddedCount := 0;
	rangedGroupAddedCount := 0;
	mountedGroupAddedCount := 0;
	for i := 0 to States.StatAIDefencePositionsCount(playerId) - 1 do
		begin
			defencePosition := States.AIDefencePositionGetByIndex(playerId, i);	
			attackGroupData.groupId := defencePosition.GroupID;
			attackGroupData.groupHaltTime := -1;
			if(defencePosition.PositionType = dtBackLine) then
				begin
					if ((States.GroupTypeEx(defencePosition.GroupID) = gtMelee) and (meleeGroupAddedCount < meleeGroupCount)) then
						begin
							attackGroupsArray := AddElementToGroupDataArray(attackGroupsArray, attackGroupData);
							meleeGroupAddedCount := meleeGroupAddedCount + 1;
						end;
					if ((States.GroupTypeEx(defencePosition.GroupID) = gtAntiHorse) and (antiHorseGroupAddedCount < antiHorseGroupCount)) then
						begin
							attackGroupsArray := AddElementToGroupDataArray(attackGroupsArray, attackGroupData);
							antiHorseGroupAddedCount := antiHorseGroupAddedCount + 1;
						end;
					if ((States.GroupTypeEx(defencePosition.GroupID) = gtRanged) and (rangedGroupAddedCount < rangedGroupCount)) then
						begin
							attackGroupsArray := AddElementToGroupDataArray(attackGroupsArray, attackGroupData);
							rangedGroupAddedCount := rangedGroupAddedCount + 1;
						end;
					if ((States.GroupTypeEx(defencePosition.GroupID) = gtMounted) and (mountedGroupAddedCount < mountedGroupCount)) then
						begin
							attackGroupsArray := AddElementToGroupDataArray(attackGroupsArray, attackGroupData);
							mountedGroupAddedCount := mountedGroupAddedCount + 1;
						end;
				end;
		end;
	Result := attackGroupsArray;
end;	

function HasEnoughSoldiersForAttack(playerId: integer; meleeFullGroupNeededCount: integer; antiHorseFullGroupNeededCount: integer; rangedFullGroupNeededCount: integer; mountedFullGroupNeededCount: integer): boolean;
var rangedGroupOwnedCount: integer;
var mountedGroupOwnedCount: integer;
var antiHorseGroupOwnedCount: integer;
var meleeGroupOwnedCount: integer;
var hasAllDefencePositionsOccupied: boolean;
var defencePosition: TKMDefencePositionInfo;
var i: integer;
begin
	rangedGroupOwnedCount := 0;
	mountedGroupOwnedCount := 0;
	antiHorseGroupOwnedCount := 0;
	meleeGroupOwnedCount := 0;
	
	hasAllDefencePositionsOccupied := true;
	for i := 0 to States.StatAIDefencePositionsCount(playerId) - 1 do
		begin
			defencePosition := States.AIDefencePositionGetByIndex(playerId, i);	
			if(defencePosition.GroupID = -1) then
				begin
					hasAllDefencePositionsOccupied := false;
					Break;
				end
			else
				begin
					if(defencePosition.PositionType = dtBackLine) then
						begin
							if ((States.GroupTypeEx(defencePosition.GroupID) = gtMelee) and (States.GroupMemberCount(defencePosition.GroupID) = 12)) then
								begin
									meleeGroupOwnedCount := meleeGroupOwnedCount + 1;
								end;
							if ((States.GroupTypeEx(defencePosition.GroupID) = gtAntiHorse) and (States.GroupMemberCount(defencePosition.GroupID) = 12)) then
								begin
									antiHorseGroupOwnedCount := antiHorseGroupOwnedCount + 1;
								end;
							if ((States.GroupTypeEx(defencePosition.GroupID) = gtRanged) and (States.GroupMemberCount(defencePosition.GroupID) = 15)) then
								begin
									rangedGroupOwnedCount := rangedGroupOwnedCount + 1;
								end;
							if ((States.GroupTypeEx(defencePosition.GroupID) = gtMounted) and (States.GroupMemberCount(defencePosition.GroupID) = 12)) then
								begin
									mountedGroupOwnedCount := mountedGroupOwnedCount + 1;
								end;
						end;
				end;
		end;
		
		if((hasAllDefencePositionsOccupied) and (meleeGroupOwnedCount >= meleeFullGroupNeededCount) and (rangedGroupOwnedCount >= rangedFullGroupNeededCount) and (antiHorseGroupOwnedCount >= antiHorseFullGroupNeededCount) and (mountedGroupOwnedCount >= mountedFullGroupNeededCount)) then
			begin
				Result := true;
			end
		else 
			begin
				Result := false;
			end;
end;

function SetGroupsHaltIfSpread(playerId: integer; attackGroupsArray: array of GroupData): array of GroupData;
var memberPosition: TKMPoint;
var leaderPosition: TKMPoint;
var shouldHalt: boolean;
var allPlayerGroups: array of integer;
var indexInAttackGroupsArray: integer;
var i,j: integer;
var unitId: integer;
begin
	allPlayerGroups := States.PlayerGetAllGroups(playerId);
	for i := 0 to Length(allPlayerGroups) - 1 do
		begin
			indexInAttackGroupsArray := GetIndexInGroupDataArray(attackGroupsArray, allPlayerGroups[i]);
			shouldHalt := false;
			unitId := States.GroupMember(allPlayerGroups[i],0);
				if(unitId = -1) then
					begin
						Continue;
					end;
			leaderPosition := States.UnitPosition(unitId);
			for j := 1 to States.GroupMemberCount(allPlayerGroups[i]) - 1 do
				begin
					unitId := States.GroupMember(allPlayerGroups[i],j);
					if(unitId = -1) then
						begin
							Continue;
						end;
					memberPosition := States.UnitPosition(unitId);
					if((Utils.AbsI(leaderPosition.X - memberPosition.X) >= 5) or (Utils.AbsI(leaderPosition.Y - memberPosition.Y) >= 5)) then 
						begin
							if(not States.GroupTypeEx(allPlayerGroups[i]) = gtRanged) then 
								begin
									shouldHalt := true;
								end
							else if(States.GameTime mod 200 = 0) then
								begin
									shouldHalt := true;
								end;
						end;
				end;
			if(shouldHalt) then 
				begin
					if(indexInAttackGroupsArray <> -1) then
						begin
							attackGroupsArray[indexInAttackGroupsArray].groupHaltTime := States.GameTime + Utils.TimeToTick(0,0,2);
						end;
					Actions.GroupOrderHalt(allPlayerGroups[i]);
				end
			else
				begin
					if(indexInAttackGroupsArray <> -1) then
						begin
							attackGroupsArray[indexInAttackGroupsArray].groupHaltTime := -1;
						end;
				end;
		end;
	Result := attackGroupsArray;
end;	

procedure OrderGroupHouseAttacks(groupHouseAttackDataArray: array of GroupHouseAttackData);
var i: integer;
begin
	for i := 0 to Length(groupHouseAttackDataArray) - 1 do
		begin
			if((not States.HouseDestroyed(groupHouseAttackDataArray[i].houseId)) and (not States.GroupDead(groupHouseAttackDataArray[i].groupId))) then 
				begin
					Actions.GroupOrderAttackHouse(groupHouseAttackDataArray[i].groupId, groupHouseAttackDataArray[i].houseId);
				end;
		end;
end;	

procedure OrderAttackForAllAttackGroups(playerId: integer; attackGroupsArray: array of GroupData);
var i,j: integer;
var groupPosition: TKMPoint;
var currentMilitaryUnitPosition: TKMPoint;
var currentHousePosition: TKMPoint;
var currentMilitaryUnitId: integer;
var currentHouseId: integer;
var closestMilitaryUnitId: integer;
var closestHouseId: integer;
var closestHousePlanId: integer;
var unitId: integer;
var distanceClosestUnit: double;
var distanceClosestHouse: double;
begin
	for i := 0 to Length(attackGroupsArray) - 1 do
		begin
			closestMilitaryUnitId := -1;
			closestHouseId := -1;
			if((attackGroupsArray[i].groupHaltTime < States.GameTime) and (States.GroupMember(attackGroupsArray[i].groupId,0) <> -1)) then
				begin
					closestHousePlanId := GetClosestHousePlanIdFromGroupInRangeBasedOnGroupType(attackGroupsArray[i].groupId);
					if(closestHousePlanId <> -1) then
						begin
							Actions.GroupOrderAttackHouse(attackGroupsArray[i].groupId, closestHousePlanId);
							Continue;
						end;
					unitId := States.GroupMember(attackGroupsArray[i].groupId,0);
					if(unitId = -1) then
						begin
							Continue;
						end;
					groupPosition := States.UnitPosition(unitId);
					distanceClosestUnit := 9999;
					distanceClosestHouse := 9999;
					for j := 0 to States.StatPlayerCount - 1 do
						begin
							if (alliancesPerPlayerIdMap[playerId][j] = false) then
								begin
									currentMilitaryUnitId := States.ClosestUnitMultipleTypesEx(j,groupPosition.X,groupPosition.Y,militaryUnitTypes);
									if(currentMilitaryUnitId <> -1) then
										begin
											currentMilitaryUnitPosition := States.UnitPosition(currentMilitaryUnitId);
											if(distance(currentMilitaryUnitPosition.X, currentMilitaryUnitPosition.Y, groupPosition.X, groupPosition.Y) < distanceClosestUnit) then
												begin
													distanceClosestUnit := distance(currentMilitaryUnitPosition.X, currentMilitaryUnitPosition.Y, groupPosition.X, groupPosition.Y);
													closestMilitaryUnitId := currentMilitaryUnitId;
												end;
										end;
								
									currentHouseId := States.ClosestHouseEx(j,groupPosition.X,groupPosition.Y,htAny);
									if(currentHouseId <> -1) then
										begin
											currentHousePosition := States.HousePosition(currentHouseId);
											if(distance(currentHousePosition.X, currentHousePosition.Y, groupPosition.X, groupPosition.Y) < distanceClosestHouse) then
												begin
													distanceClosestHouse := distance(currentHousePosition.X, currentHousePosition.Y, groupPosition.X, groupPosition.Y);
													closestHouseId := currentHouseId;
												end;
										end;
								end;
						end;
					if (closestMilitaryUnitId <> -1) then
						begin
							Actions.GroupOrderAttackUnit(attackGroupsArray[i].groupId, closestMilitaryUnitId);
						end
					else if (closestHouseId <> -1) then
						begin
							Actions.GroupOrderAttackHouse(attackGroupsArray[i].groupId, closestHouseId);
						end;
				end;
		end;
end;	

procedure OrderAttackForAllAttackGroupsHousePriority(playerId: integer; attackGroupsArray: array of GroupData);
var i,j: integer;
var groupPosition: TKMPoint;
var currentMilitaryUnitPosition: TKMPoint;
var currentHousePosition: TKMPoint;
var currentMilitaryUnitId: integer;
var currentHouseId: integer;
var closestMilitaryUnitId: integer;
var closestHouseId: integer;
var closestHousePlanId: integer;
var unitId: integer;
var distanceClosestUnit: double;
var distanceClosestHouse: double;
begin
	for i := 0 to Length(attackGroupsArray) - 1 do
		begin
			closestMilitaryUnitId := -1;
			closestHouseId := -1;
			if((attackGroupsArray[i].groupHaltTime < States.GameTime) and (States.GroupMember(attackGroupsArray[i].groupId,0) <> -1)) then
				begin
					closestHousePlanId := GetClosestHousePlanIdFromGroupInRangeBasedOnGroupType(attackGroupsArray[i].groupId);
					if(closestHousePlanId <> -1) then
						begin
							Actions.GroupOrderAttackHouse(attackGroupsArray[i].groupId, closestHousePlanId);
							Continue;
						end;
					unitId := States.GroupMember(attackGroupsArray[i].groupId,0);
					if(unitId = -1) then
						begin
							Continue;
						end;
					groupPosition := States.UnitPosition(unitId);
					distanceClosestUnit := 9999;
					distanceClosestHouse := 9999;
					for j := 0 to States.StatPlayerCount - 1 do
						begin
							if (alliancesPerPlayerIdMap[playerId][j] = false) then
								begin
									currentMilitaryUnitId := States.ClosestUnitMultipleTypesEx(j,groupPosition.X,groupPosition.Y,militaryUnitTypes);
									if(currentMilitaryUnitId <> -1) then
										begin
											currentMilitaryUnitPosition := States.UnitPosition(currentMilitaryUnitId);
											if(distance(currentMilitaryUnitPosition.X, currentMilitaryUnitPosition.Y, groupPosition.X, groupPosition.Y) < distanceClosestUnit) then
												begin
													distanceClosestUnit := distance(currentMilitaryUnitPosition.X, currentMilitaryUnitPosition.Y, groupPosition.X, groupPosition.Y);
													closestMilitaryUnitId := currentMilitaryUnitId;
												end;
										end;
								
									currentHouseId := GetClosestHouseFromGroup(j,attackGroupsArray[i].groupId);
									if(currentHouseId <> -1) then
										begin
											currentHousePosition := States.HousePosition(currentHouseId);
											if(distance(currentHousePosition.X, currentHousePosition.Y, groupPosition.X, groupPosition.Y) < distanceClosestHouse) then
												begin
													distanceClosestHouse := distance(currentHousePosition.X, currentHousePosition.Y, groupPosition.X, groupPosition.Y);
													closestHouseId := currentHouseId;
												end;
										end;
								end;
						end;
					if ((closestHouseId  <> -1) and (distanceClosestUnit > 10)) then
						begin
							Actions.GroupOrderAttackHouse(attackGroupsArray[i].groupId, closestHouseId);
						end
					else if (closestMilitaryUnitId <> -1) then
						begin
							Actions.GroupOrderAttackUnit(attackGroupsArray[i].groupId, closestMilitaryUnitId);
						end;
				end;
		end;
end;	

function DeleteDeadAttackGroupsFromArray(attackGroupsArray: array of GroupData): array of GroupData;
var attackGroupIndexesToDelete: array of integer;
var i: integer;
begin
	for i := 0 to Length(attackGroupsArray) - 1 do
		begin
			if(States.GroupDead(attackGroupsArray[i].groupId)) then
				begin
					attackGroupIndexesToDelete := AddElementToIntegerArray(attackGroupIndexesToDelete,i);
				end;
		end;
		
	Result :=  DeleteIndexesFromGroupDataArray(attackGroupsArray, attackGroupIndexesToDelete);
	SetLength(attackGroupIndexesToDelete, 0);	
end;

function DeleteDeadIdleGroupsFromArray(idleGroupsArray: array of GroupIdleData): array of GroupIdleData;
var idleGroupIndexesToDelete: array of integer;
var i: integer;
begin
	for i := 0 to Length(idleGroupsArray) - 1 do
		begin
			if(States.GroupDead(idleGroupsArray[i].groupId)) then
				begin
					idleGroupIndexesToDelete := AddElementToIntegerArray(idleGroupIndexesToDelete,i);
				end;
		end;
		
	Result := DeleteIndexesFromGroupIdleDataArray(idleGroupsArray, idleGroupIndexesToDelete);
	SetLength(idleGroupIndexesToDelete, 0);	
end;

function IsGroupAssignedToAnyDefencePosition(playerId: integer; groupId: integer): boolean;
var defencePosition: TKMDefencePositionInfo;
var returnValue: boolean;
var i: integer;
begin
	returnValue := false;
	for i := 0 to States.StatAIDefencePositionsCount(playerId) - 1 do
		begin
			defencePosition := States.AIDefencePositionGetByIndex(playerId, i);	
			if(defencePosition.GroupID = groupId) then
				begin
					returnValue := true;
					Break;
				end;
		end;
	Result := returnValue;
end;

function AddIdleGroupsToAttackGroupsArray(idleGroupsArray: array of GroupIdleData; attackGroupsArray: array of GroupData): array of GroupData;
var groupData: GroupData; 
var i: integer;
begin
	for i := 0 to Length(idleGroupsArray) - 1 do
		begin
			if(idleGroupsArray[i].groupIdleCount > 3) then
				begin
					groupData.groupId := idleGroupsArray[i].groupId;
					groupData.groupHaltTime := -1;
				
					if(GetIndexInGroupDataArray(attackGroupsArray,groupData.groupId) = -1) then
						begin
							attackGroupsArray := AddElementToGroupDataArray(attackGroupsArray,groupData);
						end;
				end;
		end;
	Result := attackGroupsArray;
end;

function UpdateGroupIdleDataArray(playerId: integer; idleGroupsArray: array of GroupIdleData): array of GroupIdleData;
var allPlayerGroups: array of integer;
var groupId: integer; 
var i: integer;
var groupIdleData: GroupIdleData;
var groupIdleDataIndex: integer;
begin
	allPlayerGroups := States.PlayerGetAllGroups(playerId);
	for i := 0 to Length(allPlayerGroups) - 1 do
		begin
			groupId := allPlayerGroups[i];
			groupIdleDataIndex := GetIndexInGroupIdleDataArray(idleGroupsArray, groupId);
			if(groupIdleDataIndex <> -1) then 
				begin
					if((States.GroupIdle(groupId)) and (not IsGroupAssignedToAnyDefencePosition(playerId,groupId))) then 
						begin
							idleGroupsArray[groupIdleDataIndex].groupIdleCount :=  idleGroupsArray[groupIdleDataIndex].groupIdleCount + 1;
						end
					else
						begin
							idleGroupsArray[groupIdleDataIndex].groupIdleCount := 0;
						end;
				end
			else
				begin 
					groupIdleData.groupId := groupId;
					groupIdleData.groupIdleCount := 0;
					idleGroupsArray := AddElementToGroupIdleDataArray(idleGroupsArray, groupIdleData);
				end;
		end;
	Result := idleGroupsArray;	
end;

procedure OnMissionStart_Attacks;
begin
	militaryUnitTypes := [utMilitia, utAxeFighter, utSwordFighter, utBowman, utCrossbowman, utLanceCarrier, utPikeman, utScout, utKnight, utBarbarian, utRebel, utRogue, utWarrior, utVagabond];

end;