## Sid Meier's Civilization 4
## Copyright Firaxis Games 2005
## 
## Author   : 12 Monkeys - Tool Collection
## Date     : 24.03.2006
## version  : 1.2
## 

from CvPythonExtensions import *
import CvUtil
import PyHelpers
import CvEventInterface

PyPlayer = PyHelpers.PyPlayer
gc = CyGlobalContext()
localText = CyTranslator()

######################################################
###     small debug function (stolen from Stone-D's ToolKit)
######################################################

def debug(argsList):
    printToScr = True
    printToLog = True
    message = "%s" %(argsList)
    if (printToScr):
        CyInterface().addImmediateMessage(message,"")
    if (printToLog):
        CvUtil.pyPrint(message)
    return 0
    
######################################################
###     Class used for Re-Init Interceptor Mission 
######################################################

class ReInitInterceptorMision:

    def __init__(self):
        self.firsttime = true

    def doReInit(self):
        iPlayer = PyPlayer(CyGame().getActivePlayer())
        unitList = iPlayer.getUnitList()
        # Loop for all players cities
        for ii in range(len(unitList)):
            pLoopUnit = unitList[ii]
            # get the group the unit belongs to. 
            pGroup = pLoopUnit.getGroup()
            # check if unit is doing air patrol
            if (pGroup.getActivityType() == ActivityTypes.ACTIVITY_INTERCEPT):
                # remove mission (this one shold be obsolete, but who knows ;)
                pGroup.popMission()
                pPlot = pGroup.plot()
                # add new mission -> fortify
                pGroup.pushMission(MissionTypes.MISSION_FORTIFY, 0, 0, 0, false, false, MissionAITypes.NO_MISSIONAI, pPlot, pLoopUnit)
                # add new mission -> air patrol
                pGroup.pushMission(MissionTypes.MISSION_AIRPATROL, 0, 0, 0, false, false, MissionAITypes.NO_MISSIONAI, pPlot, pLoopUnit)

######################################################
###     Calculates index of an XP point
######################################################

def GetIdxOfXP(iXP):
    iLevel = 0
    iIdx = 0
    while iLevel <= iXP:
        iIdx += 1
        if iIdx == 1:
            iLevel = 2
        else:
            iLevel += (iIdx-1)*2+1
    return iIdx-1

######################################################
###     Calculates the number of possible promotions between two XP values
######################################################

def GetPossiblePromotions(iXP1, iXP2):
    iIdx1 = GetIdxOfXP(iXP1)
    iIdx2 = GetIdxOfXP(iXP2)
    return iIdx2-iIdx1+1
    
######################################################
###     creates a list of possible upgrades for a unit
######################################################

def getPossibleUpgrades(pUnit):
    lList = []
    for i in range(gc.getNumUnitInfos()):
        if pUnit.canUpgrade(i, true):
            lList.append(i)
    return lList

######################################################
###     creates a list of possible promotions for a unit
######################################################

def getPossiblePromos(pUnit):
    lList = []
    for i in range(gc.getNumPromotionInfos()):
        if pUnit.canAcquirePromotion(i):
            lList.append(i)
    return lList
    
######################################################
###     checks if there are any possible upgrade for the unit
######################################################

def checkAnyUpgrade(pUnit):
    return (len(getPossibleUpgrades(pUnit)) > 0)

######################################################
###     function returns a string which contains all the 
###     units characteristics the units get by promotions
######################################################
    
def getPromotionInfoText(pUnit):
    szPromotionInfo = u""
    bBlitz = false
    bAmphib = false
    bRiver = false
    bEnemyRoads = false
    bAlwaysHeal = false
    bHillsDoubleMove = false
    bImmuneToFirstStrikes = false
    lFeatureDoubleMove = [false]*gc.getNumFeatureInfos()
    iVisibilityChange = 0
    iMovesChange = 0
    iMoveDiscountChange = 0
    iWithdrawalChange = 0
    iCollateralDamageChange = 0
    iBombardRateChange = 0
    iFirstStrikesChange = 0
    iChanceFirstStrikesChange = 0
    iEnemyHealChange = 0
    iNeutralHealChange = 0
    iFriendlyHealChange = 0
    iSameTileHealChange = 0
    iAdjacentTileHealChange = 0
    iCombatPercent = 0
    iCityAttackPercent = 0
    iCityDefensePercent = 0
    iHillsDefensePercent = 0
    lFeatureDefensePercent = [0]*gc.getNumFeatureInfos()
    lUnitCombatModifierPercent = [0]*gc.getNumUnitCombatInfos()
    for i in range(gc.getNumPromotionInfos()):
        if pUnit.isHasPromotion(i):
            if gc.getPromotionInfo(i).isBlitz():
                bBlitz = true
            if gc.getPromotionInfo(i).isAmphib():
                bAmphib = true
            if gc.getPromotionInfo(i).isRiver():
                bRiver = true
            if gc.getPromotionInfo(i).isEnemyRoute():
                bEnemyRoads = true
            if gc.getPromotionInfo(i).isAlwaysHeal():
                bAlwaysHeal = true
            if gc.getPromotionInfo(i).isHillsDoubleMove():
                bHillsDoubleMove = true
            if gc.getPromotionInfo(i).isImmuneToFirstStrikes():
                bImmuneToFirstStrikes = true
            for ii in range(gc.getNumFeatureInfos()):
                if (gc.getPromotionInfo(i).getFeatureDoubleMove(ii)):
                    lFeatureDoubleMove[ii] = true
            iTemp = gc.getPromotionInfo(i).getVisibilityChange()
            if iTemp > 0:
                iVisibilityChange += iTemp
            iTemp = gc.getPromotionInfo(i).getMovesChange()
            if iTemp > 0:
                iMovesChange += iTemp               
            iTemp = gc.getPromotionInfo(i).getMoveDiscountChange()
            if iTemp > 0:
                iMoveDiscountChange += iTemp
            iTemp = gc.getPromotionInfo(i).getWithdrawalChange()
            if iTemp > 0:
                iWithdrawalChange += iTemp
            iTemp = gc.getPromotionInfo(i).getCollateralDamageChange()
            if iTemp > 0:
                iCollateralDamageChange += iTemp
            iTemp = gc.getPromotionInfo(i).getBombardRateChange()
            if iTemp > 0:
                iBombardRateChange += iTemp
            iTemp = gc.getPromotionInfo(i).getFirstStrikesChange()
            if iTemp > 0:
                iFirstStrikesChange += iTemp
            iTemp = gc.getPromotionInfo(i).getChanceFirstStrikesChange()
            if iTemp > 0:
                iChanceFirstStrikesChange += iTemp
            iTemp = gc.getPromotionInfo(i).getEnemyHealChange()
            if iTemp > 0:
                iEnemyHealChange += iTemp
            iTemp = gc.getPromotionInfo(i).getNeutralHealChange()
            if iTemp > 0:
                iNeutralHealChange += iTemp
            iTemp = gc.getPromotionInfo(i).getFriendlyHealChange()
            if iTemp > 0:
                iFriendlyHealChange += iTemp
            iTemp = gc.getPromotionInfo(i).getSameTileHealChange()
            if iTemp > 0:
                iSameTileHealChange += iTemp
            iTemp = gc.getPromotionInfo(i).getAdjacentTileHealChange()
            if iTemp > 0:
                iAdjacentTileHealChange += iTemp
            iTemp = gc.getPromotionInfo(i).getCombatPercent()
            if iTemp > 0:
                iCombatPercent += iTemp
            iTemp = gc.getPromotionInfo(i).getCityAttackPercent()
            if iTemp > 0:
                iCityAttackPercent += iTemp
            iTemp = gc.getPromotionInfo(i).getCityDefensePercent()
            if iTemp > 0:
                iCityDefensePercent += iTemp
            iTemp = gc.getPromotionInfo(i).getHillsDefensePercent()
            if iTemp > 0:
                iHillsDefensePercent += iTemp
            for ii in range(gc.getNumFeatureInfos()):
                iTemp = gc.getPromotionInfo(i).getFeatureDefensePercent(ii)
                if (iTemp > 0):
                    lFeatureDefensePercent[ii] += iTemp
            for ii in range(gc.getNumUnitCombatInfos()):
                if gc.getPromotionInfo(i).getUnitCombat(ii):
                    iTemp = gc.getPromotionInfo(i).getUnitCombatModifierPercent(ii)
                    if iTemp > 0:
                        lUnitCombatModifierPercent[ii] += iTemp
                
    if bBlitz:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_BLITZ_TEXT", ()) + "\n"
    if bAmphib:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_AMPHIB_TEXT", ()) + "\n"
    if bRiver:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_RIVER_ATTACK_TEXT", ()) + "\n"
    if bEnemyRoads:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_ENEMY_ROADS_TEXT", ()) + "\n"
    if bAlwaysHeal:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_ALWAYS_HEAL_TEXT", ()) + "\n"
    if bHillsDoubleMove:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_HILLS_MOVE_TEXT", ()) + "\n"
    if bImmuneToFirstStrikes:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_IMMUNE_FIRST_STRIKES_TEXT", ()) + "\n"
    szTemp = u""
    for ii in range(gc.getNumFeatureInfos()):
        if lFeatureDoubleMove[ii]:
            if len(szTemp) > 0:
                szTemp += u", "
            szTemp += gc.getFeatureInfo(ii).getDescription()
    if len(szTemp) > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_DOUBLE_MOVE_TEXT", (szTemp, )) + "\n"
    if iVisibilityChange > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_VISIBILITY_TEXT", (iVisibilityChange, )) + "\n"
    if iMovesChange > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_MOVE_TEXT", (iMovesChange, )) + "\n"
    if iMoveDiscountChange > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_MOVE_DISCOUNT_TEXT", (iMoveDiscountChange, )) + "\n"
    if iWithdrawalChange > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_WITHDRAWAL_TEXT", (iWithdrawalChange, )) + "\n"
    if iCollateralDamageChange > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_COLLATERAL_DAMAGE_TEXT", (iCollateralDamageChange, )) + "\n"
    if iBombardRateChange > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_BOMBARD_TEXT", (iBombardRateChange, )) + "\n"
    if iFirstStrikesChange == 1:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_FIRST_STRIKE_TEXT", (iFirstStrikesChange, )) + "\n"
    if iFirstStrikesChange > 1:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_FIRST_STRIKES_TEXT", (iFirstStrikesChange, )) + "\n"
    if iChanceFirstStrikesChange == 1:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_FIRST_STRIKE_CHANCE_TEXT", (iChanceFirstStrikesChange, )) + "\n"
    if iChanceFirstStrikesChange > 1:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_FIRST_STRIKES_CHANCE_TEXT", (iChanceFirstStrikesChange, )) + "\n"
    if iEnemyHealChange > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_HEALS_EXTRA_TEXT", (iEnemyHealChange, )) + localText.getText("TXT_KEY_PROMOTION_ENEMY_LANDS_TEXT", ()) + "\n"
    if iNeutralHealChange > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_HEALS_EXTRA_TEXT", (iNeutralHealChange, )) + localText.getText("TXT_KEY_PROMOTION_NEUTRAL_LANDS_TEXT", ()) + "\n"
    if iFriendlyHealChange > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_HEALS_EXTRA_TEXT", (iFriendlyHealChange, )) + localText.getText("TXT_KEY_PROMOTION_FRIENDLY_LANDS_TEXT", ()) + "\n"
    if iSameTileHealChange > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_HEALS_SAME_TEXT", (iSameTileHealChange, )) + localText.getText("TXT_KEY_PROMOTION_DAMAGE_TURN_TEXT", ()) + "\n"
    if iAdjacentTileHealChange > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_HEALS_ADJACENT_TEXT", (iAdjacentTileHealChange, )) + localText.getText("TXT_KEY_PROMOTION_DAMAGE_TURN_TEXT", ()) + "\n"
    if iCombatPercent > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_STRENGTH_TEXT", (iCombatPercent, )) + "\n"
    if iCityAttackPercent > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_CITY_ATTACK_TEXT", (iCityAttackPercent, )) + "\n"
    if iCityDefensePercent > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_CITY_DEFENSE_TEXT", (iCityDefensePercent, )) + "\n"
    if iHillsDefensePercent > 0:
        szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_HILLS_DEFENSE_TEXT", (iHillsDefensePercent, )) + "\n"
    szTemp = u""
    for ii in range(gc.getNumFeatureInfos()):
        iTemp = lFeatureDefensePercent[ii]
        if iTemp > 0:
            szTemp = gc.getFeatureInfo(ii).getDescription()
            szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_DEFENSE_TEXT", (iTemp, szTemp, )) + "\n"
    szTemp = u""
    for ii in range(gc.getNumUnitCombatInfos()):
        iTemp = lUnitCombatModifierPercent[ii]
        if iTemp > 0:
            szTemp = gc.getUnitCombatInfo(ii).getDescription()
            szPromotionInfo += localText.getText("TXT_KEY_PROMOTION_VERSUS_TEXT", (iTemp, szTemp, )) + "\n"     
    return u"<font=2>" + szPromotionInfo + u"</font>"
    
######################################################
###     removes the pedia links from a string
######################################################  

def removeLinks(szText):
    # replace link starts
    while szText.find("<link=") != -1:
        iStartPos = szText.find("<link=")
        iEndPos = szText.find(">", iStartPos)
        szText = szText[:iStartPos]+szText[iEndPos+1:]          
    # replace link ends
    szText = szText.replace("</link>", "")      
    return szText

######################################################
###     removes the font literals from a texts
######################################################

def removeFonts(szText):
    # replace link starts
    while szText.find("<font=") != -1:
        iStartPos = szText.find("<font=")
        iEndPos = szText.find(">", iStartPos)
        szText = szText[:iStartPos]+szText[iEndPos+1:]          
    # replace link ends
    szText = szText.replace("</font>", "")      
    return szText
    
######################################################
###     removes the color literals from a texts
######################################################

def removeColor(szText):
    # replace link starts
    while szText.find("<color=") != -1:
        iStartPos = szText.find("<color=")
        iEndPos = szText.find(">", iStartPos)
        szText = szText[:iStartPos]+szText[iEndPos+1:]          
    # replace link ends
    szText = szText.replace("</color>", "")     
    return szText
    
######################################################
###     calculates the heal factor per round for a unit. 
###     Takes consideration of city-buildings and 
###     units incl. their promotions on the actual and 
###     adjacent plots. Does only work for player units.
######################################################

def getPlotHealFactor(pUnit):

    # heal rates for certain areas. They are usually stored in the GlobalDefines.XML but can't be read out with a standard API function.
    # So I placed them here as constants.
    ENEMY_HEAL_RATE             = 5
    NEUTRAL_HEAL_RATE           = 10
    FRIENDLY_HEAL_RATE          = 15
    CITY_HEAL_RATE              = 20

    
    # set/reset some variables
    pPlot = pUnit.plot()
    iSameTileHealFactor         = 0
    iAdjacentTileHealFactor     = 0
    iBuildingHealFactor         = 0
    iSelfHealFactor             = 0
    iPromotionHealFactor        = 0
    iTileHealFactor             = 0
    iActivePlayer               = CyGame().getActivePlayer()
    pActivePlayer               = gc.getPlayer(iActivePlayer)
    iActivePlayerTeam           = pActivePlayer.getTeam()
    eDomain                     = gc.getUnitInfo(pUnit.getUnitType()).getDomainType()
    
    # a sea or air unit in a city, behaves like a land unit
    if pPlot.isCity():
        eDomain = DomainTypes.DOMAIN_LAND

    # calculate the adjacent-tile heal-factor caused by other units (only the unit with the highest factor counts)
    for dx in range(-1, 2):
        for dy in range(-1, 2):
            # ignore same tile. Adjacent-tile healing does not work on the same tile.
            if not (dx == 0 and dy == 0):
                pLoopPlot = CyMap().plot(pPlot.getX()+dx, pPlot.getY()+dy)
                # loop through all units on the plot
                for i in range(pLoopPlot.getNumUnits()):
                    pLoopUnit = pLoopPlot.getUnit(i)
                    eLoopUnitDomain = gc.getUnitInfo(pLoopUnit.getUnitType()).getDomainType()
                    # a sea or air unit in a city, behaves like a land unit
                    if pLoopPlot.isCity():
                        eLoopUnitDomain = DomainTypes.DOMAIN_LAND
                    # adjacent-tile heal does only work if the units have the same domain type
                    if (eDomain == eLoopUnitDomain):
                        if (pLoopUnit.getTeam() == iActivePlayerTeam):
                            if (pLoopUnit.getAdjacentTileHeal() > iAdjacentTileHealFactor):
                                iAdjacentTileHealFactor = pLoopUnit.getAdjacentTileHeal()
    
    # calculate the same-tile heal-factor caused by other or same unit (only the unit with the highest factor counts)
    # the same-tile healing is also a kind of self-healing. Means : the promotion Medic I has also effect on the owner unit
    for i in range(pPlot.getNumUnits()):
        pLoopUnit = pPlot.getUnit(i)
        eLoopUnitDomain = gc.getUnitInfo(pLoopUnit.getUnitType()).getDomainType()
        # a sea or air unit in a city, behaves like a land unit
        if pLoopPlot.isCity():
            eLoopUnitDomain = DomainTypes.DOMAIN_LAND
        # same tile heal does only work if the units are of the same domain type
        if (eDomain == eLoopUnitDomain):
            if (pLoopUnit.getTeam() == iActivePlayerTeam):
                if (pLoopUnit.getSameTileHeal() > iSameTileHealFactor):
                    iSameTileHealFactor = pLoopUnit.getSameTileHeal()
                
    # only the highest value counts
    iTileHealFactor = max(iAdjacentTileHealFactor, iSameTileHealFactor)

    # calculate the self heal factor by the location and promotion
    iTeam = pPlot.getTeam()
    pTeam = gc.getTeam(iTeam)       
    iSelfHealFactor         = NEUTRAL_HEAL_RATE
    iPromotionHealFactor    = pUnit.getExtraNeutralHeal()
    if pPlot.isCity():
        iSelfHealFactor         = CITY_HEAL_RATE
        iPromotionHealFactor    = pUnit.getExtraFriendlyHeal()
    elif (iTeam == iActivePlayerTeam):
        iSelfHealFactor         = FRIENDLY_HEAL_RATE
        iPromotionHealFactor    = pUnit.getExtraFriendlyHeal()
    elif (iTeam != TeamTypes.NO_TEAM):
        if (pTeam.isAtWar(iActivePlayerTeam)):
            iSelfHealFactor         = ENEMY_HEAL_RATE
            iPromotionHealFactor    = pUnit.getExtraEnemyHeal()
    
    # calculate the heal factor by city buildings
    if pPlot.isCity():
        if (pPlot.getTeam() == iActivePlayerTeam):
            # EF: should probably allow friendly healing too, but this doesn't
            pCity = pPlot.getPlotCity()
            # loop for all buldings
            for iBuilding in range(gc.getNumBuildingInfos()):
                # check if city has that building
                if pCity.getNumActiveBuilding(iBuilding) > 0:
                    # sum up all heal rates 
                    iBuildingHealFactor += gc.getBuildingInfo(iBuilding).getHealRateChange() * pCity.getNumActiveBuilding(iBuilding)

    # return the sum of all heal factors
    return iTileHealFactor + iBuildingHealFactor + iSelfHealFactor + iPromotionHealFactor   

######################################################
###     calculates the upgrade price for a unit 
###     nMode is :
###         0 : only the actual unit
###         1 : all units of the same type in the same selection group
###         2 : all units of the same type on the same plot
###         3 : all player units of the same type all over the map
######################################################
        
def getUpgradePrice(pUnit, iToUnitType, nMode):
    # single unit
    if nMode == 0:
        return pUnit.upgradePrice(iToUnitType)
    # selection group
    elif nMode == 1:
        pPlot = pUnit.plot()
        iPrice = 0
        for i in range(pPlot.getNumUnits()):
            pLoopUnit = pPlot.getUnit(i)
            if (pLoopUnit.getGroupID() == pUnit.getGroupID()) and (pLoopUnit.getUnitType() == pUnit.getUnitType()):
                iPrice += pLoopUnit.upgradePrice(iToUnitType)
        return iPrice
    # same plot
    elif nMode == 2:
        pPlot = pUnit.plot()
        iPrice = 0
        for i in range(pPlot.getNumUnits()):
            pLoopUnit = pPlot.getUnit(i)
            if (pLoopUnit.getUnitType() == pUnit.getUnitType()):
                iPrice += pLoopUnit.upgradePrice(iToUnitType)
        return iPrice
    # all players unit
    elif nMode == 3:
        pActPlayer = gc.getActivePlayer()
        iPrice = 0
        for i in range(pActPlayer.getNumUnits()):
            pLoopUnit = pActPlayer.getUnit(i)
            if (pLoopUnit.getUnitType() == pUnit.getUnitType()):
                iPrice += pLoopUnit.upgradePrice(iToUnitType)
        return iPrice
    return -1

######################################################
###     some keyboard hooks
######################################################

def bShift():
    return CvEventInterface.getEventManager().bShift

def bCtrl():
    return CvEventInterface.getEventManager().bCtrl

def bAlt():
    return CvEventInterface.getEventManager().bAlt
        
######################################################
###     END 
######################################################
