## Copyright ClassicThunder 2006-2007
## CreateNests by Sto
## Everything else by ClassicThunder

from CvPythonExtensions import *
import CvUtil
import CvScreensInterface
import CvDebugTools
import CvWBPopups
import PyHelpers
import Popup as PyPopup
import CvCameraControls
import CvTopCivs
import sys
import CvWorldBuilderScreen
import CvAdvisorUtils
import CvTechChooser
import pickle
import math
import string
from CvPythonExtensions import *
import CvMapGeneratorUtil

gc = CyGlobalContext()
localText = CyTranslator()
PyPlayer = PyHelpers.PyPlayer
PyInfo = PyHelpers.PyInfo
					
class DragonTools:
	def __init__(self):
		self.DragonLairs = []
		self.RampageTurn = 0
		self.Dragon = 0
		self.AttackingDragons = 0

	def CreateNests(self): ##Writen By Sto. You're awsome man!!
			# initializing datas :
			gc = CyGlobalContext()
			map = CyMap()
			dice = gc.getGame().getSorenRand()
			iW = map.getGridWidth()
			iH = map.getGridHeight()
			iBonusDragon = gc.getInfoTypeForString('BONUS_DRAGONROOST')
			iUnitDragon = gc.getInfoTypeForString('UNIT_LAIRDRAGON')
			bPlayer = gc.getPlayer(gc.getBARBARIAN_PLAYER())
	
			# the minimum distance between a dragon and an unit on the same area (by air)
			iRangeUnit = 8
	
			# the minimum range between a dragon and an unit , even if not on the same area (by air).
			iAbsoluteRangeUnit = 3
	
			# the minimum distance between two dragons on the same area (by air)
			iMaxRangeDragons = 8
			iMinRangeDragons = 6
	
			# the number of time that the function try to place a nest per area , per range of dragons.
			# As the plot is aleatory selected and if the size of the area is at the limit ,
			# sometimes the first plot can prevent the second to be placed ( for an example )
			iPass = 10
	
			# if all nest nest can't be placed on islands or players area , the percent of distance to check if a player have access to a nest
			# i.e if the nearest player to a nest is at 12 squares , take players at 1.40 * 12 like possible players to get the nest .
			# must be a float number ( 1.40 = 140 % )
			percentDistance = 1.40
	
			# if all nest nest can't be placed on islands or players area , the indent distance to check if a player have access to a nest
			# i.e if the nearest player to a nest is at 12 squares , take players between 12 and 15 like possible players to get the nest . ( for small maps )
			indentDistance = 3
	
			#create library to stock plot coordinates per areas into two libraries : with starting locations and without
			areaPlotsPlayers = {}
			areaPlotsIslands = {}
	
			areas = CvMapGeneratorUtil.getAreas()
			for area in areas :
					if area.isWater() : continue
					 # else create a list where to store plot coordinates into the libraries
					if area.getNumStartingPlots()>0 : areaPlotsPlayers[area.getID()]=[]
					else : areaPlotsIslands[area.getID()]=[]
	
			# create the list of player and starting plots . used later .
			playerList = []
			startPlotList = []
			nbPlayerMax=gc.getMAX_CIV_PLAYERS()
			staX=[0 for i in range(nbPlayerMax)]
			staY=[0 for i in range(nbPlayerMax)]
			for iPlayer in range(nbPlayerMax):
					if gc.getPlayer(iPlayer).isEverAlive() and (iPlayer!=gc.getBARBARIAN_PLAYER()): #just in case barbarian becomes a playable civ ...
							startPlot = gc.getPlayer(iPlayer).getStartingPlot()
							staX[iPlayer]=startPlot.getX()
							staY[iPlayer]=startPlot.getY()
							playerList.append(iPlayer)
							startPlotList.append((startPlot.getX(),startPlot.getY()))
			nbNestToPlace = len(playerList) # note that nbNestToPlace is used as well for nb nest to place or for number of players in the file .
	
			# create the list of team . used to check visibility .
			teamList=[]
			for iTeam in range(gc.getMAX_TEAMS()):
					iLoopTeam = gc.getTeam(iTeam)
					if iLoopTeam.isNone() : continue
					if iLoopTeam.isBarbarian() : continue
					if not iLoopTeam.isAlive() : continue
					teamList.append(iTeam)
	
			# create a mask of coordinates plot <= iRangeUnit squares around every non barbarian units
			maskPlots = []
	
			# create a mask to be sure that a nest is not on an island close to a starting plot
			absoluteMask = []
			
			for iX in range(iW) :
					for iY in range(iH) :
							pLoopPlot = map.plot(iX, iY)
							if pLoopPlot.getNumUnits()==0 : continue
	
							# if the unit is barbarian , just remove the barbarian plot from the possible nest placement
							if pLoopPlot.getUnit(0).isBarbarian() :
									maskPlots.append((iX, iY))
									continue
	
							# if the plot has a non barbarian unit , make sure it's a starting plot
							# you can remove this test because you ask for a minimum of 8 squares from every unit .
							# I think that checking starting plot is enought ( in case the starting units are not on the same plot of the settler ... )
							# In case there is non barbarian units far from starting plots , the visible test is enough .
							if not (iX,iY) in startPlotList : continue
	
							# else add iRangeUnit square around to mask and absolute mask
							maskPlots += makeMask(iX,iY,iRangeUnit)
							absoluteMask += makeMask(iX,iY,iAbsoluteRangeUnit)
	
			# create the list of plots per area
			for iX in range(iW) :
					for iY in range(iH) :
							pLoopPlot = map.plot(iX, iY)
							
							if pLoopPlot.isWater() : continue
							if pLoopPlot.isImpassable() : continue
							if pLoopPlot.isOwned() : continue # made for scenarios/scripts with barbarian cities at the beginning of the game
							
							bVisible=False
							for iTeam in teamList :
									if pLoopPlot.isVisible(iTeam ,False) : bVisible=True
							if bVisible : continue
	
							iArea = pLoopPlot.getArea()
	
							if iArea in areaPlotsPlayers.keys() :
									if (iX,iY) in maskPlots : continue
									areaPlotsPlayers[iArea].append((iX,iY))
							elif iArea in areaPlotsIslands.keys() :
									if (iX,iY) in absoluteMask : continue
									areaPlotsIslands[iArea].append((iX,iY))
							else :
									print " ERROR : can't store plot : "+repr((iX,iY))
	
			# first try to place every nest on players areas and save datas if all nest can't be placed.
			nestList = []
			posssibleNestList = []
			bNearPlayer = False
	
			for iRangeDragons in range( iMaxRangeDragons, iMinRangeDragons-1, -1):
					if bNearPlayer : break
					for iT in range(iPass):
							listNestPlots = []
							for iArea in areaPlotsPlayers.keys() :
									nbNestToPlaceArea = map.getArea(iArea).getNumStartingPlots()
									listAreaPlots = areaPlotsPlayers[iArea]
									for iTest in range(iPass):
											listNestPlotsTemp = []
											listAreaPlotsTemp = [item for item in listAreaPlots] # duplicate the list . now i can modify it without modifing the original
											while True :
													if len(listAreaPlotsTemp)==0 : break
													if len(listNestPlotsTemp)==nbNestToPlaceArea : break
													iChoice = dice.get( len(listAreaPlotsTemp) , " Nest Plot coordinates ( players ). " )
													(iX,iY) = listAreaPlotsTemp[iChoice]
													listNestPlotsTemp.append((iX,iY))
	
													# create a mask of coordinates where the nest can t be placed
													maskPlotsTemp = makeMask(iX,iY,iRangeDragons)
	
													# remove the mask plots from the possible nest plot list
													listAreaPlotsTemp = [ item for item in listAreaPlotsTemp if not item in maskPlotsTemp ]
	
											if len(listNestPlotsTemp)==nbNestToPlaceArea : break # all nest placed in this area .
	
									listNestPlots += listNestPlotsTemp
	
							if len(listNestPlots) == nbNestToPlace: #all nest placed
									bNearPlayer = True
									nestList = listNestPlots
									print " All nests can be placed near players ..."
									break
	
							else : #store datas
									posssibleNestList.append(list(listNestPlots))
									
			if not bNearPlayer : # try to place nests on islands , and save datas if all nests can't be placed on islands
					bNearIsland = False
					posssibleIslandNestList = []
	
					listArea = [ areaPlotsIslands[iArea] for iArea in areaPlotsIslands.keys() ] # change the library into a list
					
					# calculate the max number of nests per Areas
					lenlistArea = [ len(item) for item in listArea ]
					nbTilesTot = float(sum(lenlistArea ))
					nbMaxNests = [ int(lenlistArea[i]*nbNestToPlace/nbTilesTot)+1 for i in range(len(lenlistArea)) ]
					
					# try to place nests
					for iRangeDragons in range( iMaxRangeDragons, iMinRangeDragons-1, -1):
							if bNearIsland : break
	
							for iT in range(iPass):
									listNestPlots = []
									nbNestsPlaced = 0
									listAreaTemp = [item for item in listArea]
									nbMaxNestsTemp = [item for item in nbMaxNests]
									
									while True :
											if nbNestsPlaced == nbNestToPlace : break
											if len(listAreaTemp)==0 : break
	
											# place first on biggest islands
											lLen = [ len(item) for item in listAreaTemp ]
											maxL = max(lLen)
											indexList = [ lLen.index(item) for item in lLen if item==maxL ]
											iList = indexList[dice.get( len(indexList) , " island choice " )]
											listAreaPlotsTemp = [ item for item in listAreaTemp[iList] ]
											nbPlacedArea = 0
											while True :
													if nbNestsPlaced == nbNestToPlace : break
													if nbPlacedArea == nbMaxNestsTemp[iList] : break
													if len(listAreaPlotsTemp)==0 : break
	
													iChoice = dice.get( len(listAreaPlotsTemp) , " Nest Plot coordinates ( islands ) . " )
													(iX,iY) = listAreaPlotsTemp[iChoice]
													listNestPlots.append((iX,iY))
													nbPlacedArea += 1
													nbNestsPlaced += 1
	
													# create a mask of coordinates where the nest can t be placed
													maskPlotsTemp = makeMask(iX,iY,iRangeDragons)
	
													# remove the mask plots from the possible nest plot list
													listAreaPlotsTemp = [ item for item in listAreaPlotsTemp if not item in maskPlotsTemp ]
	
											del(listAreaTemp[iList])
											del(nbMaxNestsTemp[iList])
	
									if len(listNestPlots) == nbNestToPlace: #all nest placed
											bNearIsland = True
											nestList = listNestPlots
											print " All nests can be placed on islands ..."
											break
	
									else : #store datas
											posssibleIslandNestList.append(list(listNestPlots))
	
			if len(nestList)==0 : # Nests can't be all placed near players or on islands
					if len(posssibleNestList)>0 :
							# first , take only the largest list of nests near players
							maxL = max([len(item) for item in posssibleNestList])
							pListNests = [item for item in posssibleNestList if len(item)==maxL]
	
							# try to equilibrate nests :
							coefPlayers = [ [ 0 for i in range(nbNestToPlace) ] for j in range(len(pListNests)) ]
	
							for iLNest in range(len(pListNests)):
									for (iX,iY) in pListNests[iLNest] :
											# calculate path distance with each starting point .
											pathTemp = [ map.calculatePathDistance(map.plot(iX,iY),map.plot(staX[playerList[iP]],staY[playerList[iP]])) for iP in range(nbNestToPlace) ]
	
											# pathdist return -1 if no path , so replace -1 with 10000
											for iP in range(nbNestToPlace):
													if pathTemp[iP]==-1:pathTemp[iP]=10000
	
											# now check and set values for players that are near from the possible nest
											minDist = min(pathTemp)
											if minDist == 10000 : continue # no available player near nest ( in case of an unreachable nest on the area )
	
											for iP in range(nbNestToPlace) :
													if pathTemp[iP]<=minDist*percentDistance or pathTemp[iP]<=minDist+indentDistance :
															coefPlayers[iLNest][iP]+=1
	
							# now choose the best list of nest near players :
							# first take the lists with the maximum of players that can reach a nest
							tpCoefList = [ [ i for i in item if i!=0 ] for item in coefPlayers ]
							maxL = max([len(item) for item in tpCoefList])
							tpIdList = [ tpCoefList.index(item) for item in tpCoefList if len(item)==maxL ]
	
							# now choose the list with the minimum max coef ... to limit at the minimum a player to be near many nest
							maxCoef = 100000
							finalId = -1
							for idList in tpIdList :
									maxTp = max(coefPlayers[idList])
									if maxTp < maxCoef :
											finalId = idList
											maxCoef = int(maxTp)
	
							if finalId != -1 :
									nestList += pListNests[finalId]
	
							nPlacedNearPlayer = int(len(nestList))
							print " Nests placed near players : "+str(nPlacedNearPlayer)
	
	
							# now place the rest of nests on islands
							nbMaxToPlaceIslands = nbNestToPlace - nPlacedNearPlayer
							nbPlacedIslands = 0
	
							if nbMaxToPlaceIslands > 0 : # should be all the time true ... but just in case
									toMuchList = []
									equalList = []
									toLessList = []
									for item in posssibleIslandNestList :
											if len(item)==nbMaxToPlaceIslands :equalList.append(item)
											elif len(item)<nbMaxToPlaceIslands :toLessList.append(item)
											else :toMuchList.append(item)
	
									if len(equalList)>0:
											iChoice = dice.get( len(equalList) , " Nest Plot List ( islands equal) . " )
											nestList += equalList[iChoice]
											nbPlacedIslands = nbMaxToPlaceIslands
											print " equal list chosen "
									elif len(toMuchList)>0: # in case there is to much choice , choose the most disparate one
											minL = min([len(item) for item in toMuchList])
											tpList = [ item for item in toMuchList if len(item)==minL ]
											iChoice = dice.get( len(tpList) , " Nest Plot List ( islands too many ) . " )
											tpListNest = tpList[iChoice]
											nbPl = 0
											while True :
													if len(tpListNest) == 0 : break # should not be true ... just in case
													if nbPl == nbMaxToPlaceIslands : break
													iChoice = dice.get( len(tpListNest) , " Nest Plot ( islands too many ) . " )
													nestList.append(tpListNest[iChoice])
													del(tpListNest[iChoice])
													nbPl += 1
											nbPlacedIslands = nbMaxToPlaceIslands
											print " too many list chosen "
									elif len(toLessList)>0: # in case there is not enough nest to place choose the largest one
											maxL = max([len(item) for item in toLessList])
											tpList = [ item for item in toLessList if len(item)==maxL ]
											iChoice = dice.get( len(tpList) , " Nest Plot List ( islands too less ) . " )
											nestList += tpList[iChoice]
											nbPlacedIslands = maxL
											print " too less list chosen "
									else : # no nest can be placed on islands : probably no islands on the map
											print " no nest on islands "
							else :
									print " ERROR : there is no nest to place on islands "
	
			print repr(nestList) # debug tool
	
			#Now create nests :
			for (iX,iY) in nestList :
					pPlot = map.plot(iX, iY)
					if pPlot.isNone():
							print " ERROR plot is none: "+repr((iX,iY)) # debug tool
							continue
					pPlot.setBonusType( -1) # better to clear the plot bonus before adding another
					pPlot.setBonusType( iBonusDragon)
					bPlayer.initUnit( iUnitDragon, iX, iY, UnitAITypes.NO_UNITAI )
					self.DragonLairs.append(map.plotNum(iX, iY))
	
	
			## COMMENT ALL THIS PART BEFORE RELEASE
	
			# A message to check where nests were spawn .
			##if bNearPlayer : CyInterface().addImmediateMessage( "Nests spawn on players areas : "+str(nbNestToPlace)+" nests spawned." , "")
			##elif bNearIsland : CyInterface().addImmediateMessage( "Nests spawn on islands : "+str(nbNestToPlace)+" nests spawned ." , "")
			##else : CyInterface().addImmediateMessage( "Nests spawn on player area : "+str(nPlacedNearPlayer)+" , nests spawned on islands : "+str(nbPlacedIslands)+" , nb nest to place : "+str(nbNestToPlace) , "")
	
			# debug : create signs to have a quick look of the result ... 
			##map.setRevealedPlots(0,True,False)
			##for iPlayer in playerList : CyEngine().addSign(CyMap().plot(staX[iPlayer],staY[iPlayer]),0,"Start Plot")
			##for (iX,iY) in nestList : CyEngine().addSign(CyMap().plot(iX,iY),0,"NEST NEST")
				
	def DragonDoTurn(self):
		if (len(self.DragonLairs) != 0):
			if (CyGame().getGameTurn() == 0):
				self.RampageTurn = (40 + gc.getGame().getSorenRandNum( 11 ,"First Dragon Attack") )	
				self.RampageTurn = 1
			elif (CyGame().getGameTurn() == self.RampageTurn):
				self.RampageTurn = int(( CyGame().getGameTurn() + 30 + gc.getGame().getSorenRandNum(11, "Dragon Attacks")	))
				if ((self.AttackingDragons + 1) <= len(self.DragonLairs) ):
					index = self.DragonLairs[self.Dragon]
					plot = CyMap().plotByIndex(index)
					unit = gc.getPlayer( gc.getBARBARIAN_PLAYER() ).initUnit( gc.getInfoTypeForString('UNIT_ATTACKDRAGON'), plot.getX(), plot.getY(), UnitAITypes.NO_UNITAI)
					unit.setUnitAIType(5)
					self.AttackingDragons = self.AttackingDragons + 1
					if ( (self.Dragon + 1) < (len(self.DragonLairs)) ):
						self.Dragon = self.Dragon + 1
					else:
						self.Dragon = 0
	
	def NewNest(self, pUnit):
		if (len(self.DragonLairs) != 0):
			if (pUnit.getUnitType() == gc.getInfoTypeForString('UNIT_ATTACKDRAGON')):
				pPlot = pUnit.plot()
				if ( (pPlot.getImprovementType() == gc.getInfoTypeForString('IMPROVEMENT_CITY_RUINS')) or (pPlot.isCity() == True) ):
					(pPlot.getPlotCity()).kill()
					pPlot.setBonusType( gc.getInfoTypeForString('BONUS_DRAGONROOST') )
					pUnit.kill(True ,0)
					self.AttackingDragons = self.AttackingDragons - 1
					gc.getPlayer( gc.getBARBARIAN_PLAYER() ).initUnit( gc.getInfoTypeForString('UNIT_LAIRDRAGON'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI)
					self.DragonLairs.append( CyMap().plotNum(pPlot.getX(),pPlot.getY()) )
	
	def SaveData(self):
		CSString = (str(self.Dragon) + "|" + str(self.RampageTurn) + "|" + str(self.AttackingDragons))
		gc.getGame().setScriptData( pickle.dumps( CSString ))
		
	def GetData(self):
		Data = pickle.loads(gc.getGame().getScriptData())
		Numbers = string.split(Data,"|")
		self.Dragon = int(Numbers[0])
		self.RampageTurn = int(Numbers[1])
		self.AttackingDragons = int(Numbers[2])
		self.DragonLairs = []
		if (CyGame().getGameTurn() != 0):
			for index in range (CyMap().numPlots()):
				pPlot = CyMap().plotByIndex(index)
				if (pPlot.getBonusType(-1) == gc.getInfoTypeForString('BONUS_DRAGONROOST')):
					self.DragonLairs.append(index)
					
def makeMask(sX,sY,iRange):
        map = CyMap()
        iW = map.getGridWidth()
        iH = map.getGridHeight()
        maskTemp=[]
        for dX in range(-iRange,iRange+1):
                for dY in range(-iRange,iRange+1):
                        pX = sX + dX
                        if map.isWrapX() : pX = pX % iW # if the map is wrapX . make the coordinate ( 0 <= pX < iW )
                        pY = sY + dY
                        if map.isWrapY() : pY = pY % iH # if the map is wrapY . make the coordinate ( 0 <= pY < iH )
                        maskTemp.append((pX, pY))
        return maskTemp