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

HOWTO: save and load data in your mod

Discussion in 'Civ4 - Modding Tutorials & Reference' started by DireAussie, Nov 20, 2005.

  1. DireAussie

    DireAussie Chieftain

    Joined:
    Nov 12, 2005
    Messages:
    60
    How does it work?

    Certain classes in civ 4 expose a setScriptData() function and a getScriptData() function that you can use to put data into that will automatically be saved and loaded by civ 4 when someone saves/loads a game.

    The classes with these functions are:

    CyCity
    CyGame
    CyPlayer
    CyPlot
    CyUnit

    How do I do it?

    The first thing you should do is make a new IO class in its own module (.py file). In my example I show below I created a file called RandomEventsIO.py:

    Code:
    #import utility functions
    from CvPythonExtensions import *
    import pickle			
    
    # globals
    gc = CyGlobalContext()
    CyGameInstance = gc.getGame()
    
    class RandomEventsIO:
    
    	def setupScriptData( self ):
    		"Initialise the global script data dictionary for usage.  setupScriptData should ALWAYS set the variables for a NEW GAME"
    		
    		#this needs to hold all variables to be saved between games
    
    		scriptDictCyGame = 	{	'olympicCounter': 1,
    						'locustSwarmOccupyingPlotX': -1,  
    						'locustSwarmOccupyingPlotY': -1
    					}
    
    		CyGameInstance.setScriptData( pickle.dumps(scriptDictCyGame) )
    
    	def getOlympicCounter( self ):
    		"Returns olympicCounter"
    		scriptDictCyGame = pickle.loads( CyGameInstance.getScriptData() )
    		return scriptDictCyGame['olympicCounter']
    
    	def setOlympicCounter( self, newValue ):
    		"Sets olympicCounter."
    		scriptDictCyGame = pickle.loads( CyGameInstance.getScriptData() )
    		scriptDictCyGame['olympicCounter'] = newValue
    		CyGameInstance.setScriptData( pickle.dumps(scriptDictCyGame) )
    
    	def getLocustSwarmOccupyingPlot(self):
    		"get the plot the locust swarm is occupying"
    		scriptDictCyGame = pickle.loads( CyGameInstance.getScriptData() )
    		return scriptDictCyGame['locustSwarmOccupyingPlotX'], scriptDictCyGame['locustSwarmOccupyingPlotY']		
    
    	def setLocustSwarmOccupyingPlot(self, x, y):
    		"sets the plot the locust swarm is occupying"
    		scriptDictCyGame = pickle.loads( CyGameInstance.getScriptData() )
    		scriptDictCyGame['locustSwarmOccupyingPlotX'] = x
    		scriptDictCyGame['locustSwarmOccupyingPlotY'] = y
    		CyGameInstance.setScriptData( pickle.dumps(scriptDictCyGame) )
    
    The code is explained below:

    Code:
    from CvPythonExtensions import *
    import pickle	
    PythonExtensions are the exposed c++ functions that you can call in python. This gives you access to CyGame, CyPlot objects and functions etc
    pickle is a special python module that allows you to write stuff out to files and read stuff in from files without having to worry about the data type.

    Code:
    CyGameInstance = gc.getGame()
    This gets the CyGame instance from the globalcontext object. I will be reading/writing my script data out to this CyGame object.

    Code:
    def setupScriptData( self ):
    This function is to be called only when starting a new game. In your CvEventManager file you have a function called:

    def onGameStart(self, argsList):

    you can put a call to this function in here (see later)

    Code:
    scriptDictCyGame = 	
    {	
    'olympicCounter': 1,
    'locustSwarmOccupyingPlotX': -1,  
    'locustSwarmOccupyingPlotY': -1
    }
    This creates a "dictionary of values". All is basically is is an array with three elements but instead of doing scriptDictCyGame[0], scriptDictCyGame[1] and scriptDictCyGame[2] to get the values I can do scriptDictCyGame['olympicCounter'], scriptDictCyGame['locustSwarmOccupyingPlotX'] and scriptDictCyGame['locustSwarmOccupyingPlotY'] respectively to get the values.

    The values of these variables should be what you want them to be on initialisation.

    Code:
    CyGameInstance.setScriptData( pickle.dumps(scriptDictCyGame) )
    This writes the whole scriptDictCyGame array out to the CyGame class' script data. Since this is a new game we need to do this at the very start because at the start of a new game there's nothing at all saved in the script data.

    Using these variables

    Now that you have set these variables up, you need to make functions so you change the variables in your python code. I'll explain one of them below:

    Code:
    	def getOlympicCounter( self ):
    		"Returns olympicCounter"
    		scriptDictCyGame = pickle.loads( CyGameInstance.getScriptData() )
    		return scriptDictCyGame['olympicCounter']
    
    This gets the "olympicCounter" variable from the python code. Note how we first have to load the entire scriptDictCyGame array using pickle, then we can get the single value from the array.

    Code:
    	def setOlympicCounter( self, newValue ):
    		"Sets olympicCounter."
    		scriptDictCyGame = pickle.loads( CyGameInstance.getScriptData() )
    		scriptDictCyGame['olympicCounter'] = newValue
    		CyGameInstance.setScriptData( pickle.dumps(scriptDictCyGame) )
    
    This sets the "olympicCounter" variable to a new value. Note how you have to first load the entire array out, then change the one value, then save the entire array back in.

    OK, I have created this IO class. Now what?
    You'll need to create an instance of this new IO object. A starting point in doing this is in the CvEventManager.py module.

    You'll need to add:

    Code:
    import RandomEventsIO
    to the top

    in the def __init__(self): function you need to add (anywhere inside it):

    Code:
    self.randomEventsIO = RandomEventsIO.RandomEventsIO()
    This will create an object (instance) of the RandomEventsIO class when the EventClass object is created (which is as soon as civ 4 loads I think).

    Now you want your IO object to set itself up when the user starts a new game. To do that, find the

    def onGameStart(self, argsList):

    function, and add this to the bottom:

    self.randomEventsIO.setupScriptData()

    This will initialise the values and put them inside the CyGame script data.

    OK, its ready to use. How do I use/save/load my variables?

    At ALL times you must use the get and set functions for the relavent variable. You may need to pass the IO object off to other modules through the argument list of your functions. Other than that you dont have to worry about saving and loading as Civ 4 saves all the script data when the user saves the game and it loads the data back in when the user loads a game.

    What should I use, CyCity, CyGame, CyPlayer, CyPlot, CyUnit?

    That depends on what you need to save. If you want to save a piece of data about all units, for example, "number of kills", then you'll need to use the saveScriptData/loadScriptData provided by CyUnit, and you'll need to loop through every unit to do it.
     
  2. Major

    Major Chieftain

    Joined:
    Nov 16, 2005
    Messages:
    26
    Location:
    France
    Nice work ;)
    Yesterday I was looking for a way to save my own data and this mourning I found your post :D

    By using the ScriptData functions, I managed to "mod" the options screen: I have added some more options which allow me to enable or disable easily the mods I am using ingame :goodjob:

    Thanks again :)
     
  3. The Great Apple

    The Great Apple Big Cheese

    Joined:
    Mar 24, 2002
    Messages:
    3,361
    Location:
    Oxford, England
    For anybody digging up this old tutorial, I would very much recommend you use Stone-D's SD-Toolkit instead of this method. It allows for much greater mod compatability, and is actually easier to use that manually pickling.
     

Share This Page