View Full Version : [PYTHON] SD ToolKit


Stone-D
Dec 06, 2005, 02:28 AM
SD-ToolKit 1.22
by Stone-D ( Laga Mahesa )
For Civilization IV
Compatibility : 1.00+
Data Source : 1.09
New Game Req : N/A


Well, for a start, it isn't something for a regular player to use, so if you're not a python modder, stop now.

During my python escapades, I've had cause to make a small number of global functions that I believe others will benefit from, especially end users. This is NOT a standalone mod. It is a python module that can be incorporated into other projects. The major function of this library allows modders to store data without conflicting with anything else.

I felt that this was something that needed to be addressed sooner or later.

The following is a brief API :


Basic Utilities
===============
sdEcho( string )
Outputs 'string' to the python debug log AND onto the screen.
Either output mode can be toggled using two boolean variables inside the function definition.

sdGetTimeInt( turn_int )
Converts a year string of the form "1234 AD" or "1234 BC" to an integer. BC is negative.

sdGameYearsInt()
Returns the total span of years a given game should last, irrespective of victory conditions.
Standard Firaxis gamespeeds cover exactly 6,050 total years, but others do not - for example, my SD-Glacial mod covers 6,200 years.


Data Storage
============
Internal Functions :
sdModInit( 'MyModName_string' )
Initializes a central reservoir of custom variables for your mod's use.
For internal use. You should not need to actively use this function.

sdModFixCase( 'MyModName_string', Mod_dictionary )
For internal use. You should not use this function.

sdModLoad( 'MyModName_string' )
Loads previously initialized data from the central reservoir.
For internal use. You should not need to actively use this function.

sdModSave( 'MyModName_string', Mod_dictionary )
Saves a mod's entire variable data to the central reservoir.
For internal use. You should not need to actively use this function.

MOD Functions :
sdEntityInit( 'MyModName_string', 'UniqueName_string', Entity_dictionary )
Initializes a unique data entity (city, unit, plot).
UniqueName_string = Any string used to indentify the entity, such as 'Alcatraz' or '5435' or 'Warrior55'
Entity_dictionary = A python dictionary containing the data set for the 'Entity', such as {'var1' : 56, 'var2' : 'My Rectum', 'var3' : [1, 2, 3]}

sdEntityWipe( 'MyModName_string', 'UniqueName_string' )
Removes an entity that has been previously initialized by sdEntityInit.
An entity can be anything that contains a set of data... a city, a unit or your dog's name. Use your imagination.

sdEntityExists( 'MyModName', 'UniqueName_string' )
Checks whether or not an entity has been initialized by sdEntityInit.
Returns bool False on failure, bool True on success.

sdGetVal( 'MyModName_string', 'UniqueName_string', 'VariableName_string' )
Fetches a specific variable's value from the entity's data set.
Will raise an exception on failure.

sdSetVal( 'MyModName_string', 'UniqueName_string', 'VariableName_string', any_value )
Stores a specific variable's value within the entity's data set.
Returns bool False on failure, bool True on success.

sdDelVal( 'MyModName_string', 'UniqueName_string', 'VariableName_string' )
Removes a specific variable from the entity's data set.
Returns bool False on failure, bool True on success.

sdGetGlobal( 'MyModName_string', 'GlobalVariableName_string' )
Fetches a specific variable's value from the mod's global data set, which is automatically initialized.
Will raise an exception on failure.

sdSetGlobal( 'MyModName_string', 'GlobalVariableName_string', any_value )
Stores a specific variable's value within the mod's global data set, which is automatically initialized.

sdDelGlobal( 'MyModName_string', 'GlobalVariableName_string' )
Removes a specific variable from the mod's global data set.
Returns bool False on failure, bool True on success.


To clarify, the saved data is stored with saved games and will be restored when a game is reloaded by the user. Saved data will not clash with other mods using this toolkit.


History
=-=-=-=

v1.00 :
Initial Release.

v1.10 :
BASIC-UTILITIES Addition : sdGetTimeInt( turn )
BASIC-UTILITIES Addition : sdGameYearsInt()
SD-DATA-STORAGE Addition : sdModFixCase( 'MyModName', Mod_dictionary )
SD-DATA-STORAGE Addition : sdEntityWipe( 'MyModName', 'UniqueName_string' )
Now checks for changed case in mod names.
Changed all defs so the leading characters are lower case.

v1.20 :
Some minor cleanups and optimizations.
Added comments to the functions with examples and descriptions.
SD-DATA-STORAGE Addition : sdEntityExists( 'MyModName', 'UniqueName_string' )
SD-DATA-STORAGE Addition : sdDelVal( 'MyModName', 'UniqueName_string', 'VariableName_string' )
SD-DATA-STORAGE Addition : sdGetGlobal( 'MyModName', 'GlobalVariableName' )
SD-DATA-STORAGE Addition : sdSetGlobal( 'MyModName', 'GlobalVariableName', any_value )
SD-DATA-STORAGE Addition : sdDelGlobal( 'MyModName', 'GlobalVariableName' )
Boolean return values added to some functions. See API section.

v1.21 :
Fixed a major bug in sdDelGlobal.

v1.22 :
Fixed another bug in sdDelGlobal.

Zurai
Dec 06, 2005, 08:19 AM
Awesome. I was looking for a way to spit out debug text to the debug log, and I was wondering how I was going to save my variables in the game save file. This seems to solve both those problems - thank you!

Requies
Dec 06, 2005, 08:47 AM
Awesome. I was looking for a way to spit out debug text to the debug log, and I was wondering how I was going to save my variables in the game save file. This seems to solve both those problems - thank you!

Wait does it really save it to the game file? From the looks of the functions, it doesn't look like it does (though it may). I guess the question is what is the "central depository."

Also, spitting out debug text to the debug log is easy. Just use CvUtil.pyPrint.

If you had asked, I'm sure someone would have told you.

These tools look interesting, though.

Req

Stone-D
Dec 06, 2005, 09:35 AM
Zurai: Hehe, glad someone's using it!

Yes, it does save it to the game file. Some entities/methods in Civ4 have a variable called szScriptData - cvGameInterface, cvUnit, cvCity for example. I use the cvGameinterface one, the top level 'node'.

Although the code used to save the variables is very simple, I am anticipating a time when such mods will collide - pick one, you cannot mix because they overwrite that vital variable.

This module allows for multiple mods to use the same variable to store their data in a hierarchy.

Save(YourModName, YourEntityName, YourVariableName, the_value)

Save ('Mylon', 'Washington', 'TimeToLive', 558)
Save ('Requies', 'Warrior_Elite_34', 'KillCount', 12)

etc.

The Great Apple
Dec 08, 2005, 12:54 PM
Great job! This will make combining mods so much easier!

Requies
Dec 08, 2005, 01:53 PM
Zurai: Hehe, glad someone's using it!

Yes, it does save it to the game file. Some entities/methods in Civ4 have a variable called szScriptData - cvGameInterface, cvUnit, cvCity for example. I use the cvGameinterface one, the top level 'node'.

Although the code used to save the variables is very simple, I am anticipating a time when such mods will collide - pick one, you cannot mix because they overwrite that vital variable.

This module allows for multiple mods to use the same variable to store their data in a hierarchy.

Save(YourModName, YourEntityName, YourVariableName, the_value)

Save ('Mylon', 'Washington', 'TimeToLive', 558)
Save ('Requies', 'Warrior_Elite_34', 'KillCount', 12)

etc.

I guess I'm still kind of confused as to what this does. Do you mean you store PYTHON variables onto a CIV 4 SAVE file? Or does it save some different variable?

Can you give an example of HOW someone would use this IRL?

Req

Stone-D
Dec 08, 2005, 06:14 PM
Yes, python... for an example, download "SD Culture Conquest". For a quicky, here ya go :


def sdCultureConquestCityAcquired(self, argsList):
# Called at city acquired event.
loser,playerType,city,bConquest,bTrade = argsList
sdSetVal('sdCultureConquest', city.getName(), 'CaptureState', 1)
sdSetVal('sdCultureConquest', city.getName(), 'loser', loser)
sdSetVal('sdCultureConquest', city.getName(), 'oCulture', city.getCulture(loser))
# Decrease this to 1 in order to avoid losing another population point due to starvation
city.setOccupationTimer(1)
return 0

.... blah blah....

def sdCultureConquestEndGameTurn(self):
# Called at the end of the turn. Odd, that, isn't it?
for i in range(gc.getMAX_PLAYERS()):
iPlayer = PyPlayer(i)
if ((iPlayer.isAlive()) or (iPlayer.isBarbarian())):
cityList = iPlayer.getCityList()
for c in range(len(cityList)):
city = cityList[c]
self.sdCultureConquestValidateCity(city)
if (sdGetVal('sdCultureConquest', city.getName(), 'CaptureState') > 0):
CaptureCount = sdGetVal('sdCultureConquest', city.getName(), 'CaptureCount')
CaptureState = sdGetVal('sdCultureConquest', city.getName(), 'CaptureState')

....blah blah....

CaptureCount += 1
CaptureState = 0
sdSetVal('sdCultureConquest', city.getName(), 'CaptureCount', CaptureCount)
sdSetVal('sdCultureConquest', city.getName(), 'CaptureState', CaptureState)

The Great Apple
Dec 09, 2005, 11:41 AM
I'm bumping into errors...

File "CvEventInterface", line 25, in onEvent

File "CvEventManager", line 164, in handleEvent

File "CvDynExp", line 236, in onUnitCreated

File "CvDynExp", line 383, in setupScriptData

File "SdToolKit", line 62, in SdEntityInit

File "SdToolKit", line 55, in SdModSave

PicklingError: Can't pickle <type 'Boost.Python.instance'>: import of module Boost.Python failed

It happens in this code:
scriptDict = { 'iTERRAIN_COAST': 0,
'iTERRAIN_HILL': 0,
'iFEATURE_FOREST': 0,
'iFEATURE_RIVER': 0,
'iFEATURE_JUNGLE': 0,
'iUNITCOMBAT_ARCHER' : 0,
'iUNITCOMBAT_MOUNTED' : 0,
'iUNITCOMBAT_MELEE' : 0,
'iUNITCOMBAT_SIEGE' : 0,
'iUNITCOMBAT_GUN' : 0,
'iUNITCOMBAT_ARMOR' : 0,
'iCITY_ATTACK' : 0,
'iCITY_DEFENCE' : 0,
'iCOLLAT_DAMAGE': 0,
'iCOMBAT' : 0,
'iMOVEMENT' : 0,
'iSTATIC' : 0,
'iHEAL': 0,
'iWITHDRAW': 0,
'iBLITZ': 0,
'iRIVTER': 0
}
SdEntityInit('DynExp', pUnit, scriptDict)

Am I doing something wrong? I've looked at your file, and apart from the try: and except: thingies (which I have no idea what are...) it's the same format.

EDIT: And does it work if your entity is not a dictionary? I have something which only needs one piece of information!

Requies
Dec 09, 2005, 11:50 AM
Yes, python... for an example, download "SD Culture Conquest". For a quicky, here ya go :


def sdCultureConquestCityAcquired(self, argsList):
# Called at city acquired event.
loser,playerType,city,bConquest,bTrade = argsList
sdSetVal('sdCultureConquest', city.getName(), 'CaptureState', 1)
sdSetVal('sdCultureConquest', city.getName(), 'loser', loser)
sdSetVal('sdCultureConquest', city.getName(), 'oCulture', city.getCulture(loser))
# Decrease this to 1 in order to avoid losing another population point due to starvation
city.setOccupationTimer(1)
return 0

.... blah blah....

def sdCultureConquestEndGameTurn(self):
# Called at the end of the turn. Odd, that, isn't it?
for i in range(gc.getMAX_PLAYERS()):
iPlayer = PyPlayer(i)
if ((iPlayer.isAlive()) or (iPlayer.isBarbarian())):
cityList = iPlayer.getCityList()
for c in range(len(cityList)):
city = cityList[c]
self.sdCultureConquestValidateCity(city)
if (sdGetVal('sdCultureConquest', city.getName(), 'CaptureState') > 0):
CaptureCount = sdGetVal('sdCultureConquest', city.getName(), 'CaptureCount')
CaptureState = sdGetVal('sdCultureConquest', city.getName(), 'CaptureState')

....blah blah....

CaptureCount += 1
CaptureState = 0
sdSetVal('sdCultureConquest', city.getName(), 'CaptureCount', CaptureCount)
sdSetVal('sdCultureConquest', city.getName(), 'CaptureState', CaptureState)


Hmmmm, ok. I guess it doesn't really apply to me since I don't really create mods....

Thanks for the explanation

Req

Stone-D
Dec 09, 2005, 02:33 PM
I'm bumping into errors...

PicklingError: Can't pickle <type 'Boost.Python.instance'>: import of module Boost.Python failed

Hrrm. I'll play around with it shortly. Gonna upload 1.2 after posting this, but there's nothing changed which would fix that... err, I think. :)

[QUOTE]Am I doing something wrong? I've looked at your file, and apart from the try: and except: thingies (which I have no idea what are...) it's the same format.


Try: Attempt the following code. If an error occurs, then...
Except: ... do this.

They're kind of a kludge in that code, haven't got around to putting proper checks in... but they work and don't result in annoying python errors mid-game. Good for the users.

EDIT: And does it work if your entity is not a dictionary? I have something which only needs one piece of information!

Forever? ;)

I'll add such functionality if the answer is yes, although the space saved versus the annoyance of multiple saving defs might be worth thinking about. :D

Stone-D
Dec 09, 2005, 02:54 PM
Just uploaded version 1.1. See the first post for details.

Stone-D
Dec 09, 2005, 03:29 PM
[QUOTE=The Great Apple]I'm bumping into errors...

File "CvDynExp", line 236, in onUnitCreated

File "CvDynExp", line 383, in setupScriptData

File "SdToolKit", line 62, in SdEntityInit

File "SdToolKit", line 55, in SdModSave

PicklingError: Can't pickle <type 'Boost.Python.instance'>: import of module Boost.Python failed

It happens in this code:
scriptDict = { 'iTERRAIN_COAST': 0,
'iRIVTER': 0
}
SdEntityInit('DynExp', pUnit, scriptDict)

Is scriptdict being declared elsewhere, perhaps as a global?

Check out this google search, there're four results. Seems to be a pickling limitation - can't pickle base objects.

Google Results (http://www.google.com/search?service=Google&btnG=Google%2BSearch&hl=en&q=PicklingError%3A+Can%27t+pickle+%3Ctype+%27Boost .Python.instance%27%3E%3A+import+of+module+Boost.P ython+failed)

Stone-D
Dec 09, 2005, 03:30 PM
I'm bumping into errors...

Could you upload CvDynExp.py or send it to my email? laga AT tbi fullstop co fullSTOP id

The Great Apple
Dec 09, 2005, 04:04 PM
Here it is:


Is scriptdict being declared elsewhere, perhaps as a global?
Nope - I did a search, and the only two instances of it were in that quote...

As for the base objects thing - I have absolutely no idea what a base object is!

EDIT: File removed

Stone-D
Dec 09, 2005, 10:32 PM
Got it. You were passing a CyUnit object to SdEntityInit, it has to be a string. I've fixed it, added a function that generates a unique name for every unit, inserted above setupscriptdata.


PY:Mod Data Initialized : DynExp True
PY:Cumae has grown
PY:Initializing Galley_0000352286
PY:Entity Initialized : Galley_0000352286
PY:Initializing Worker_0000360479
PY:Entity Initialized : Worker_0000360479
PY:Initializing Euclid (Great Scientist)_0000466948
PY:Entity Initialized : Euclid (Great Scientist)_0000466948
PY:Initializing Galley_0001105965
PY:Entity Initialized : Galley_0001105965
PY:Athens has grown
PY:City Athens's culture has expanded
PY:Initializing Galley_0001056799
PY:Entity Initialized : Galley_0001056799
PY:Corinth has grown
PY:Initializing Archer_0001064978
PY:Entity Initialized : Archer_0001064978
PY:Initializing Spearman_0001040406
PY:Entity Initialized : Spearman_0001040406
PY:Seattle has grown
PY:Initializing Archer_0001048615
PY:Entity Initialized : Archer_0001048615
PY:Initializing Galley_0000786507
PY:Entity Initialized : Galley_0000786507
PY:Initializing Archer_0000311314
PY:Entity Initialized : Archer_0000311314
PY:City Minoan's culture has expanded
PY:City Acquired Event: Minoan
iTurn: 253

Stone-D
Dec 09, 2005, 10:35 PM
BTW, I got a crapload of other errors, but they were due to me running this from an existing savegame. You could avoid that by using the Try: / Except: method, though admittedly this mod would be best run from a fresh game.

Nice work so far, looking good!

Zuul
Dec 10, 2005, 06:05 AM
Just uploaded version 1.1. See the first post for details.

Nice. Maybe you could add: current version, release date, and change log to your first post :-).

The Great Apple
Dec 10, 2005, 06:18 AM
BTW, I got a crapload of other errors, but they were due to me running this from an existing savegame. You could avoid that by using the Try: / Except: method, though admittedly this mod would be best run from a fresh game.
Ta very much!

I think I will shove the Try: / Except: thing in. I also need to add a "sdEntityWipe" fuction in.

Thanks for the great work!

Stone-D
Dec 10, 2005, 06:50 AM
Zuul: LOL, ok, I'll do that in a bit. =)

TGA: No problemo. Heh, I actually got carried away and started adding a try/except bit before reminding myself I wasn't working on my own code. Such things happen when you're coding at 4:30 am. :D

The Great Apple
Dec 10, 2005, 06:59 AM
TGA: No problemo. Heh, I actually got carried away and started adding a try/except bit before reminding myself I wasn't working on my own code. Such things happen when you're coding at 4:30 am. :D
Hehe.

I am having ever so slight problems with the game printing "Entity Initialized : Wolf_0000024577" and so forth.

I've poked around, and it seems to be coming from the toolkit. As I'm not really sure I want it to print that message every time a unit is made I've commented that bit out here. Is this just a testing relic?

EDIT: And what happens if two units are initialised with the same number? (I know chances are very very very slim)

Stone-D
Dec 10, 2005, 08:14 AM
Yes, its a testing relic. its still kinda handy for my own modding, I should comment that stuff out for release though.

As for duplicate numbers, that number is taken from the unit's own internal ID, with unit.getID() or something. If two units showed up with duplicate IDs, I reckon the game would bug out anyway, irrespective of any modding. :P

Stone-D
Dec 11, 2005, 12:42 PM
Just uploaded version 1.2. See the first post for details.

Stone-D
Dec 14, 2005, 11:26 AM
Uploaded 1.21, bugfix.

Stone-D
Dec 15, 2005, 01:31 PM
Uploaded 1.22, another bugfix. :P

The Great Apple
Jan 13, 2006, 05:08 PM
I don't know if you are still working on this, but if you do release another version would it be possable to have a function which can get all the data on a unit as a dictionary? It would be handy, as I'm finding in several places I'm having to get all the information from a unit, and then reload it back onto the unit (forcing upgrades)... meaning that everytime I think up a new thing to tie to the unitID I have to fix all the functions.

Lord Olleus
Jan 15, 2006, 05:49 AM
this thread should be stickied and moved to the tutorials, guides and reference section.

Zuul
Jan 15, 2006, 09:24 AM
Unfortunatly Stone-D havent been here since 18 december.

DCMage
Jan 15, 2006, 10:54 AM
Shouldnt this be in the Tools section not Mods section??

The Great Apple
Jan 15, 2006, 01:05 PM
Probably.

I managed to work out the function I needed. It wasn't to hard with the code in place ready to be altered.

Teg_Navanis
Apr 10, 2006, 11:25 AM
You might be interested in this file. It adds the following functions:

sdObjectInit (= sdEntityInit)

sdObjectWipe (= sdEntityWipe)

sdObjectExists (= sdEntityExists)

sdObjectGetVal (= sdGetVal)

sdObjectSetVal ( =sdSetVal)


The only difference is that instead of a 'UniqueName_string' , they demand an object. The object can be:
- CyCity object
- CyGame object
- CyPlayer object
- CyPlot object
- CyUnit object
- PyCity object


So, if the difference is so small, why sdObject*** instead of sdEntity***?
It is way faster! The data is no longer stored in a single place, but separately for each object. Thus, sdObjectGetVal and sdObjectSetVal are up to 5000% faster than sdGetVal and sdSetVal (tested with Unit Statistics, 250 units).

Almost forgot the file (http://forums.civfanatics.com/attachment.php?attachmentid=122978&d=1144684593)

The Great Apple
Apr 10, 2006, 01:34 PM
You might be interested in this file. It adds the following functions:

sdObjectInit (= sdEntityInit)

sdObjectWipe (= sdEntityWipe)

sdObjectExists (= sdEntityExists)

sdObjectGetVal (= sdGetVal)

sdObjectSetVal ( =sdSetVal)
Thanks for this - I'd just started on writing my own version of this, but I'm happy to use yours :)

Maian
Sep 15, 2006, 02:17 AM
I've written my own custom script data caching library, but for the sake of compatibility, I'm migrating to this API. One advantage your library is that it uses the cPickle library, while mine just uses the built-in repr.

However, I've noticed that the setter functions in your API are inefficient since they call setScriptData every time they are called. Instead, you should call setScriptData() only before the game is saved.

Here's my library for reference (it uses a very modified version of some CvCustomEventManager.py):

# Need CvPythonExtensions as a name in the global scope, in case Cy* classes are in script data.
import CvPythonExtensions

class ScriptDataCache(object):
'''
Allows custom data to be stored on Cv* objects that have a setScriptData() function.
The custom data is stored as a dictionary (a.k.a. map or hash table), each item being a key-value pair.
Usage:
dictionary = PyHelpers.ScriptData[<cyobj or UID>]
# use and modify the dictionary however you want
The key must be a Cy* object or the UID of a Cy* object.
The values to be stored must have a meaningful repr value, e.g. eval(repr(value)) doesn't raise an exception.
To store a Cy* object that has the method getID() or getUID(), get its UID:
uid = CvUtil.getCyUID(cyobj).
To retrieve the Cy* object from this UID:
cyobj = CvUtil.getCyObject(uid).
This function uses cvobj.getScriptData(), so the existing script data must be either a serialized dictionary or an empty string.

To store values whose repr value requires a name, that name must be present in PyHelper's global namespace. This can be done in another module as follows:
PyHelpers.<name> = <name>
For example, if an object has a repr value of 'Item(10)', in order for that value to be eval-ed successfully,
the module defining Item must have at the end:
PyHelpers.Item = Item

Note: cvojb.setScriptData() is called only right before the game is saved for efficiency reasons.
A side effect of this is that if this module is reloaded, all custom data since the game's last load is lost.
'''
def __init__(self):
self.__dct = None
def __realInit(self):
if self.__dct == None:
self.__dct = {}
import CvEventInterface
eventMgr = CvEventInterface.getEventManager()
def preSaveHandler(self, argsList):
for k in self.__dct:
cyobj = CvUtil.getCyObject(k)
cyobj.setScriptData(`self.__dct[k]`)
eventMgr.addEventHandler('OnPreSave', preSaveHandler)
def gameLoadHandler(self, argsList):
if ScriptData.__dct != None:
ScriptData.__dct.clear()
eventMgr.addEventHandler('OnLoad', gameLoadHandler)
eventMgr.addEventHandler('GameStart', gameLoadHandler)
def __getitem__(self, cyobj):
self.__realInit()
k = CvUtil.getCyUID(cyobj)
if k == cyobj:
cyobj = CvUtil.getCyObject(cyobj)
r = self.__dct.get(k)
if r == None:
sData = cyobj.getScriptData()
if len(sData) == 0:
r = {}
else:
r = eval(cyobj.getScriptData())
self.__dct[k] = r
return r
def __setitem__(self, cyobj, v):
self.__realInit()
k = CvUtil.getCyUID(cyobj)
if k == cyobj:
cyobj = CvUtil.getCyObject(cyobj)
self.__dct[k] = v
return v
def __delitem__(self, cyobj):
self.__realInit()
k = CvUtil.getCyUID(cyobj)
if k == cyobj:
cyobj = CvUtil.getCyObject(cyobj)
d = self.__dct.get(k)
if d == None:
self.__dct[k] = {}
else:
d.clear()
def __iter__(self):
self.__realInit()
return self.__dct.__iter__()
def __repr__(self):
return self.__dct.__repr__()
ScriptData = ScriptDataCache()

# I placed the following funcs in CvUtil.py

def getCyUID(cyobj):
'''
Gets and returns a unique ID for a Cy* object or any object that has a getUID() or getID() method.
CyGame, CyUnit, CyPlayer, CyPlot, CySelectionGroup, CyArea, CyTeam, and CyCity are supported.
If the getUID() method exists, it must return a tuple in the format: (type, data1 [, data2 [, ...] ])
If the getID() method exists, it must return an integer.
If an object has both getUID() and getID() methods, the getUID() method is used instead of the getID() method.
Returns the passed value if it doesn't have a unique ID.
'''
typ = type(cyobj)
if typ == CyGame:
return (typ.__name__,)
elif typ == CyUnit:
return (typ.__name__, cyobj.getOwner(), cyobj.getID())
elif typ == CyPlayer:
return (typ.__name__, cyobj.getID())
elif typ == CyPlot:
return (typ.__name__, cyobj.getX(), cyobj.getY())
elif typ == CySelectionGroup:
return (typ.__name__, cyobj.getOwner(), cyobj.getID())
elif typ == CyArea:
return (typ.__name__, cyobj.getID())
elif typ == CyTeam:
return (typ.__name__, cyobj.getID())
elif typ == CyCity:
return (typ.__name__, cyobj.getOwner(), cyobj.getID())
else:
f = getattr(cyobj, 'getUID', None)
if f != None:
return f()
f = getattr(cyobj, 'getID', None)
if f != None:
return (typ, f())
return cyobj

def getCyObject(uid):
'''
Gets and returns an object from a unique ID or a type that has the class method getObject(). See getCyUID().
If the getObject() class method exists, it must accept arguments *uid[1:].
Returns the passed value if it's not a unique ID.
'''
if type(uid) == tuple and len(uid) > 0:
typ = uid[0]
if typ == CyGame.__name__:
return gc.getGame()
elif typ == CyUnit.__name__:
return gc.getPlayer(uid[1]).getUnit(uid[2])
elif typ == CyPlayer.__name__:
return gc.getPlayer(uid[1])
elif typ == CyPlot.__name__:
return CyMap().plot(uid[1], uid[2])
elif typ == CySelectionGroup.__name__:
return gc.getPlayer(uid[1]).getSelectionGroup(uid[2])
elif typ == CyArea.__name__:
return CyMap().getArea(uid[1])
elif typ == CyTeam.__name__:
return gc.getTeam(uid[1])
elif typ == CyCity.__name__:
return gc.getPlayer(uid[1]).getCity(uid[2])
else:
f = getattr(typ, 'getObject', None)
if f != None:
return f(*uid[1:])
return uid


EDIT: Actually both getter and setter functions are inefficient - the getter function calls getScriptData() every time as well.

jdog5000
Sep 16, 2006, 10:26 AM
Does this work for Warlords 'out of the box', or does it need some tweaking?

krille
Mar 02, 2008, 04:01 PM
Does it work for BtS 'out of the box', or does it need some tweaking?

No, really, I need some way to store variables for BtS modding. Is this it?

jdog5000
Mar 02, 2008, 04:03 PM
Seems to work just fine, I haven't had to make any changes to it.

krille
Mar 02, 2008, 04:18 PM
Wow, that's quick. Thanks for the reply!

I have another question. If I use sdObjectGetVal() before sdObjectSetVal(), I understand that an exception will be raised? What exception and how do I get around it or handle/deal with it?

I'm looking to do something like this:
1. Load iOldInt
iOldInt = SdToolKitAdvanced.sdObjectGetVal("MyMod", pCyCity, "iMyInt")

2. Code... (Compare iOldInt to iNewInt)

3. Store iNewInt...
SdToolKitAdvanced.sdObjectSetVal("MyMod", pCyCity, "iMyInt", iNewInt)

The thing is, the above code is run on every turn, which means the sdObjectGetVal function will be called before sdObjectSetVal is. So the first time my code is run, ("MyMod", pCyCity, "iMyInt") will be uninitialized. Do I have to handle this in some way? Try: Except:? Or will sdObjectGetVal simply return 0 the first time? Or what should I do?

Shouldn't there be a sdObjectValExists btw?

jdog5000
Mar 02, 2008, 04:30 PM
There's a python event on city founding (and changing hands), you can have a function initialize your variable when the city is created so that it will always exist later.

In circumstances where this isn't possible, you can check whether there are any MyMod settings for the city using sdObjectExists (I think, running on memory ...) and if not initialize your settings. In the version I'm using, if MyMod data has been initialized for an object, then it will return None if the specific variable isn't found instead of throwing an exception ... (you can get my version from my Revolution mod pack through my sig if you want, called SdToolKitCustom and is based off Teg Navanis' code just a few posts earlier in the thread).