[PYTHON] SD ToolKit

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
 
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.
 
this thread should be stickied and moved to the tutorials, guides and reference section.
 
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
 
Teg_Navanis said:
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 :)
 
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):
Spoiler :
Code:
# 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.
 
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?
 
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
Code:
iOldInt = SdToolKitAdvanced.sdObjectGetVal("MyMod", pCyCity, "iMyInt")

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

3. Store iNewInt...
Code:
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?
 
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).
 
Top Bottom