1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

Help! Can't add new methods to CvEventManager!

Discussion in 'Civ4 - SDK/Python' started by deanej, Nov 23, 2007.

  1. deanej

    deanej Deity

    Joined:
    Apr 8, 2006
    Messages:
    4,859
    Location:
    New York State
    I'm attempting to make a mod that allows terrain to change with the seasons. However, the game doesn't even use the methods I made to allow this! Here's the code:

    Code:
    	def onGameStart(self, argsList):
    		'Called at the start of the game'
    		if (gc.getGame().getGameTurnYear() == gc.getDefineINT("START_YEAR") and not gc.getGame().isOption(GameOptionTypes.GAMEOPTION_ADVANCED_START)):
    			for iPlayer in range(gc.getMAX_PLAYERS()):
    				player = gc.getPlayer(iPlayer)
    				if (player.isAlive() and player.isHuman()):
    					popupInfo = CyPopupInfo()
    					popupInfo.setButtonPopupType(ButtonPopupTypes.BUTTONPOPUP_PYTHON_SCREEN)
    					popupInfo.setText(u"showDawnOfMan")
    					popupInfo.addPopup(iPlayer)
    		else:
    			CyInterface().setSoundSelectionReady(true)
    
    		if gc.getGame().isPbem():
    			for iPlayer in range(gc.getMAX_PLAYERS()):
    				player = gc.getPlayer(iPlayer)
    				if (player.isAlive() and player.isHuman()):
    					popupInfo = CyPopupInfo()
    					popupInfo.setButtonPopupType(ButtonPopupTypes.BUTTONPOPUP_DETAILS)
    					popupInfo.setOption1(true)
    					popupInfo.addPopup(iPlayer)
    
                    #Seasons Mod Start
    
    		#Terrain Types
    		iSnow = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_SNOW')
    		iSnowPermenant = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_SNOW_PERMENANT')
    		iPlains = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_PLAINS')
    		iPlainsPermenant = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_PLAINS_PERMENANT')
    		iTundra = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_TUNDRA')
    		iTundraSeasons = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_TUNDRA_SEASONS')
    		iGrassland = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_GRASS')
    		iGrasslandSeasons = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_GRASS_SEASONS')
    		
    		#Change terrain from base to seasons
    		for iPlotLoop in range(CyMap().numPlots()):
                        pPlot = CyMap().plotByIndex(iPlotLoop)
    
                        if(pPlot.getTerrainType() == iSnow):
                            pPlot.setTerrainType(iSnowPermenant,True,True)
    
                        if(pPlot.getTerrainType() == iPlains):
                            pPlot.setTerrainType(iPlainsPermenant,True,True)
    
                        if(pPlot.getTerrainType() == iTundra):
                            pPlot.setTerrainType(iTundraSeasons,True,True)
    
                        if(pPlot.getTerrainType() == iGrassland):
                            pPlot.setTerrainType(iGrasslandSeasons,True,True)
    
    		#Change to correct season
    		if(iGameTurn % 4 == 0):
                        self.setSeasonFall()
                        self.setSeasonWinter()
                    elif(iGameTurn % 4 == 3):
                        self.setSeasonFall()
                    elif(iGameTurn % 4 == 1):
                        self.setSeasonFall()
                        self.setSeasonWinter()
                        self.setSeasonSpring()
                    #Seasons Mod End
    Code:
    	def onBeginGameTurn(self, argsList):
    		'Called at the beginning of the end of each turn'
    		iGameTurn = argsList[0]
    		CvTopCivs.CvTopCivs().turnChecker(iGameTurn)
    
    		#Seasons Mod Start
    		#Changing of the seasons
    		if(iGameTurn % 4 == 0):
                        self.setSeasonWinter()
                    elif(iGameTurn % 4 == 3):
                        self.setSeasonFall()
                    elif(iGameTurn % 4 == 2):
                        self.setSeasonSummer()
                    elif(iGameTurn % 4 == 1):
                        self.setSeasonSpring()
                    #Seasons Mod End
    and in the very end of the file:
    Code:
            #Seasons Mod Start
    	def setSeasonWinter(self):
                iGrasslandSeasons = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_GRASS_SEASONS')
                iPlainsSeasons = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_PLAINS_SEASONS')
    
                print "Changing to winter..."
    
                for iPlotLoop in range(CyMap().numPlots()):
                    pPlot = CyMap().plotByIndex(iPlotLoop)
    
                    if(pPlot.getTerrainType() == iGrasslandSeasons):
                        pPlot.setTerrainType(iPlainsSeasons,True,True)
    
            def setSeasonFall(self):
                iTundraSeasons = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_TUNDRA_SEASONS')
                iSnowSeasons = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_SNOW_SEASONS')
    
                print "Chaging to fall..."
    
                for iPlotLoop in range(CyMap().numPlots()):
                    pPlot = CyMap().plotByIndex(iPlotLoop)
    
                    if(pPlot.getTerrainType() == iTundraSeasons):
                        pPlot.setTerrainType(iSnowSeasons,True,True)
    
            def setSeasonSummer(self):
                iTundraSeasons = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_TUNDRA_SEASONS')
                iSnowSeasons = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_SNOW_SEASONS')
    
                print "Changing to summer..."
    
                for iPlotLoop in range(CyMap().numPlots()):
                    pPlot = CyMap().plotByIndex(iPlotLoop)
    
                    if(pPlot.getTerrainType() == iSnowSeasons):
                        pPlot.setTerrainType(iTundraSeasons,True,True)
    
            def setSeasonSpring(self):
                iGrasslandSeasons = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_GRASS_SEASONS')
                iPlainsSeasons = CvUtil.findInfoTypeNum(gc.getTerrainInfo,gc.getNumTerrainInfos(),'TERRAIN_PLAINS_SEASONS')
    
                print "Changing to spring..."
    
                for iPlotLoop in range(CyMap().numPlots()):
                    pPlot = CyMap().plotByIndex(iPlotLoop)
    
                    if(pPlot.getTerrainType() == iPlainsSeasons):
                        pPlot.setTerrainType(iGrasslandSeasons,True,True)
                
            #Seasons Mod End
     
  2. deanej

    deanej Deity

    Joined:
    Apr 8, 2006
    Messages:
    4,859
    Location:
    New York State
  3. deanej

    deanej Deity

    Joined:
    Apr 8, 2006
    Messages:
    4,859
    Location:
    New York State
    Anyone? I'm stumped, and there are no tutorials on making your own event manager to help.
     
  4. Seven05

    Seven05 Warmonger

    Joined:
    Dec 5, 2005
    Messages:
    2,056
    Gender:
    Male
    Location:
    USA
    Sorry, not a clue here since I don't mess with the event manager much. If you try doing it in the DLL you can look at how global warming is done and mimic that, might be a better place too considering how slow python will be iterating over the entire map to change terrains.
     
  5. deanej

    deanej Deity

    Joined:
    Apr 8, 2006
    Messages:
    4,859
    Location:
    New York State
    Indeed - I noticed that it is way slow when converting the terrain on the game start - and was even slower for the two times where it actually worked in-game.
     
  6. Seven05

    Seven05 Warmonger

    Joined:
    Dec 5, 2005
    Messages:
    2,056
    Gender:
    Male
    Location:
    USA
    Here's some ideas I had after thinking about this and looking at the SDK a bit more.

    First, you may run into problems if you simply change terrain from one type to another, for instance changing plains into tundra. The biggest concern would be workers, if a worker is building a farm on a plain tile that suddenly changes to tundra what would happen? Or, if you have a workshop (-1 food) and change to a tile with no food production, what would happen?

    So, my first suggestion would be to make seasonal terrains in the XML files, you wouldn't need new graphics but they'd be a nice touch. Anyway, you simply make copes of every terrain with seasonal names such as TERRAIN_GRASS_WINTER and TERRAIN_GRASS_FALL as needed. Then, in the XML you point the seasonal terrains to the appropriate graphics and, if desired, change the yields. This way you can make sure that you avoid problems with yield values changing to something that may cause errors. Also, you can then add your new terrains to the improvements in the XML so an improvement normally allowed on a particular terrain can be built regardless of the season. You don't want a 'summer' set of terrains though, use the defaults for them so you don't have to mess with map scripts.

    Once you have that, you can check for season changes at the beginning of the turn and then go through and change the terrains as needed. The code won't be too complicated since the 'base' terrain will always be known, that is if you have TERRAIN_GRASS_WINTER on a tile you know it's a grassland tile and you know what to change it to without needing any fancy code :)

    Features may be a problem although it should be easy enough to set the forest variant based on the underlying terrain. It just might look wierd unless you make some new tree types so you can avoid changing from 'leafy' trees (as they're called in the game) to a pine forest (although pine to snowy pine would look fine).
     
  7. EmperorFool

    EmperorFool Deity

    Joined:
    Mar 2, 2007
    Messages:
    9,633
    Location:
    Mountain View, California
    Back in April or so I helped write code to do just this for ww2commander. The slowness lies entirely in regenerating the 3D terrain/texture mapping. The function to change the terrain type for a CyPlot takes a parameter allowing you to skip this step: the terrain is changed (if you mouseover a spot that changed, you see the new type and effects), but the graphics don't change. The whole map can be changed near-instantaneously.

    However, there's no way to make the game regenerate all of the map graphics at once. The theory I had was that doing it once at the end would be faster than for each plot one-by-one because doing one plot actually requires doing the other 8 plots around it. But it didn't matter since there's no way to do that.

    When I did small areas (100 plots) it took about 30 seconds, IIRC. How are you planning on choosing which tiles to change and when?

    As per your question regarding CvEventManager, download BUG (see my sig) or any of the other mods that use Dr. Elmer Jiggle's CvCustomEventManager. In it you'll find that along with examples for using it. Note you must also copy the couple changes from CvEventInterface.py as well to create his manager rather than the stock one. Once you hook it up, it's really easy to add new events.

    Here's one version of the code I had. I don't know what state it's in. Note that it is itself an EventManager. It was designed to have only two phases: winter freeze and spring thaw.

    Grass -> Tundra
    Plains -> Ice

    WinterEventManager.py
    Code:
    ##-------------------------------------------------------------------
    ## Tests winter freeze global map change
    ##-------------------------------------------------------------------
    
    from CvPythonExtensions import *
    import CvEventManager
    
    gc = CyGlobalContext()
    
    class WinterEventManager(CvEventManager.CvEventManager):
    
    	def __init__(self):
    		CvEventManager.CvEventManager.__init__(self)
    		
    		self.TERRAIN_GRASS_DATA = "G"
    		self.TERRAIN_PLAINS_DATA = "P"
    
    		self.TERRAIN_GRASS = gc.getInfoTypeForString("TERRAIN_GRASS")
    		self.TERRAIN_PLAINS = gc.getInfoTypeForString("TERRAIN_PLAINS")
    		self.TERRAIN_SNOW = gc.getInfoTypeForString("TERRAIN_SNOW")
    		self.TERRAIN_TUNDRA = gc.getInfoTypeForString("TERRAIN_TUNDRA")
    
    	def onBeginGameTurn(self, argsList):
    		CvEventManager.CvEventManager.onBeginGameTurn(self, argsList)
    		iGameTurn = argsList[0]
    		szMessage = "Game Turn %d" %(iGameTurn)
    		CyInterface().addMessage(CyGame().getActivePlayer(), True, 10, szMessage, "", 2, None, ColorTypes(8), 0, 0, False, False)
    
    		bFreeze = False
    		if iGameTurn % 2 == 0:
    			szMessage = "Winter Freeze"
    			bFreeze = True
    		else:
    			szMessage = "Spring Thaw"
    			bFreeze = False
    		
    		iGrass = 0
    		iPlains = 0
    		map = CyMap()
    		szMessage += " / Map: %d x %d" %(map.getGridWidth(), map.getGridHeight())
    		for y in range(map.getGridHeight()):
    			for x in range(map.getGridWidth()):
    				plot = map.plot(x, y)
    				iTerrainType = plot.getTerrainType()
    				if bFreeze:
    					if iTerrainType == self.TERRAIN_GRASS:
    						plot.setTerrainType(self.TERRAIN_SNOW, True, False)
    						plot.setScriptData(self.TERRAIN_GRASS_DATA)
    						iGrass += 1
    					elif iTerrainType == self.TERRAIN_PLAINS:
    						plot.setTerrainType(self.TERRAIN_SNOW, True, False)
    						plot.setScriptData(self.TERRAIN_PLAINS_DATA)
    						iPlains += 1
    				else:
    					if iTerrainType == self.TERRAIN_SNOW:
    						szData = plot.getScriptData()
    						if szData == self.TERRAIN_GRASS_DATA:
    							plot.setTerrainType(self.TERRAIN_GRASS, True, False)
    							iGrass += 1
    						elif szData == self.TERRAIN_PLAINS_DATA:
    							plot.setTerrainType(self.TERRAIN_PLAINS, True, False)
    							iPlains += 1
    
    		szMessage += " / Grass: %d / Plains: %d" %(iGrass, iPlains)
    		CyInterface().addMessage(CyGame().getActivePlayer(), True, 10, szMessage, "", 2, None, ColorTypes(8), 0, 0, False, False)
    
    #		'Called at the beginning of each turn'
    #		CvEventManager.CvEventManager.onBeginGameTurn(self, argsList)
    #
    #		iGameTurn = argsList[0]
    #		szMessage = "Game Turn %d / Grass: %d / Plains: %d / Snow: %d" %(iGameTurn, self.TERRAIN_GRASS, self.TERRAIN_PLAINS, self.TERRAIN_SNOW)
    #		CyInterface().addMessage(CyGame().getActivePlayer(), True, 10, szMessage, "", 2, None, ColorTypes(8), 0, 0, False, False)
    #		szMessage = "Plot 50,50: %d" %(CyMap().plot(50,50).getTerrainType())
    #		CyInterface().addMessage(CyGame().getActivePlayer(), True, 10, szMessage, "", 2, None, ColorTypes(8), 0, 0, False, False)
    #
    #		if iGameTurn == 0:
    #			# start in spring -- no need to thaw
    #			return
    #		
    #		bFreeze = False
    #		iQuarter = iGameTurn % 4
    #		if iQuarter == 3:
    #			szMessage = "Winter Freeze"
    #			bFreeze = True
    #		elif iQuarter == 0:
    #			szMessage = "Spring Thaw"
    #			bFreeze = False
    #		else:
    #			return
    #		
    #		iGrass = 0
    #		iPlains = 0
    #		map = CyMap()
    #		szMessage += " / Map: %d x %d" %(map.getGridWidth(), map.getGridHeight())
    #		for y in range(map.getGridHeight()):
    #			for x in range(map.getGridWidth()):
    #				plot = map.plot(x, y)
    #				iTerrainType = plot.getTerrainType()
    #				if bFreeze:
    #					if iTerrainType == self.TERRAIN_GRASS:
    #						plot.setTerrainType(self.TERRAIN_SNOW, True, False)
    #						plot.setScriptData(self.TERRAIN_GRASS_DATA)
    #						iGrass += 1
    #					elif iTerrainType == self.TERRAIN_PLAINS:
    #						plot.setTerrainType(self.TERRAIN_SNOW, True, False)
    #						plot.setScriptData(self.TERRAIN_PLAINS_DATA)
    #						iPlains += 1
    #				else:
    #					if iTerrainType == self.TERRAIN_SNOW:
    #						szData = plot.getScriptData()
    #						if szData == self.TERRAIN_GRASS_DATA:
    #							plot.setTerrainType(self.TERRAIN_GRASS, True, False)
    #							iGrass += 1
    #						elif szData == self.TERRAIN_PLAINS_DATA:
    #							plot.setTerrainType(self.TERRAIN_PLAINS, True, False)
    #							iPlains += 1
    #
    #		szMessage += " / Grass: %d / Plains: %d" %(iGrass, iPlains)
    #		CyInterface().addMessage(CyGame().getActivePlayer(), True, 10, szMessage, "", 2, None, ColorTypes(8), 0, 0, False, False)
    
    CvEventInterface.py
    Code:
    from CvPythonExtensions import *
    import WinterEventManager
    
    eventManager = WinterEventManager.WinterEventManager()
    
    def getEventManager():
    	return eventManager
    
    def onEvent(argsList):
    	'Called when a game event happens - return 1 if the event was consumed'
    	return getEventManager().handleEvent(argsList)
    
    def applyEvent(argsList):
    	context, playerID, netUserData, popupReturn = argsList
    	return getEventManager().applyEvent(argsList)
    
    def beginEvent(context, argsList=-1):
    	return getEventManager().beginEvent(context, argsList)
    
    After brainstorming, what we came up with was to have several stages (4 weeks of freezing, 4 of thawing, though in the end whatever you wanted). Also, any terrain could turn into any other terrain. It required a pre-built map, though, and you had to specify all of the plot coordinates and changes.

    I played with using WorldBuilder to specify cultural ownership on the map, one civ per stage. Then I'd have a script that processes the map to write out the plot coordinates to a file as Python code. I only coded up bits of this. For one thing, WB doesn't allow specifying culture. I had to hack WB to do it by using the bonus placement.

    Hopefully someone can pick this up and run with it. I don't think you'd see any speed gains moving to C++ unless you can find a C++ API that allows you to regenerate the map graphics in one shot. That or change few numbers of plots at a time.
     
  8. deanej

    deanej Deity

    Joined:
    Apr 8, 2006
    Messages:
    4,859
    Location:
    New York State
    One thing I thought of was to change the idea of terrains to biomes. This way, only the yields need to be changed with the seasons. Is there a method to change tile yields in python? I also thought of adding random events such as global warming or an ice age.
     
  9. EmperorFool

    EmperorFool Deity

    Joined:
    Mar 2, 2007
    Messages:
    9,633
    Location:
    Mountain View, California
    Looking at the C++ code in the DLL, it seems that the yields are strictly calculated from the terrain type. I don't see any way to add in an adjustment.

    With some C++ work you could alter the DLL to add this capability. If you do this, first try it on a small scale to make sure that the game engine itself accepts your change (shows it correctly on the map).
     

Share This Page