Help! Can't add new methods to CvEventManager!

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
 
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.
 
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).
 
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.
 
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).
 
Back
Top Bottom