[PYTHON] Merging GreatDoctor mod with Bug

You code must also be storing a list into an object's script data that DynamicUnitNames (not a part of BUG) is using because that's the code that is getting the error. It is expecting a dictionary because it uses SdToolkit.
 
Then these separate ways of storing data shouldn't be conflicting, as far as I can see...

The way it is now is conflicting, we've got to change them to the separate ways that are out there to do the same thing (change bugdata or sdtoolkit)

You code must also be storing a list into an object's script data that DynamicUnitNames (not a part of BUG) is using because that's the code that is getting the error. It is expecting a dictionary because it uses SdToolkit.

I'm trying to convert it to use Bugdata. I looked up the examples you mentioned ReminderEventManager, UnitNamingEventManager, CvStrategyOverlay. Reminder and StratOverlay import SdToolKit while UnitNamingEventManager imports Bugdata. I'm reviewing the code..
 
This is OT so I've put the rest in a spoiler.
Spoiler :
Global variables and functions in one module are available to any other module that imports it by placing the module's name in front.
I've been thinking about what you said above and it has taken awhile to sink in.

So, my previous problem with modding RFC (as mentioned in an earlier post) can really be solved by setting the class instance holding all the stored values in the mod as a global value in every module accessing it. Because then other modules, not connected to the Event Manager/Handler, can also access these values. And this regardless of what or how many instances that module has spawned.

I don't know if I'm making any sense, but since all RFC modules already also are self-contained classes, it would be as easy as adding a __init__ definition to every module/class:
Code:
        def __init__(self, scriptDict):
                global scriptDict
Because most modules are initialized with a constructor from the CvRFCEventHandler module. By adding this method also interface, advisor and other independent modules would still be able to create their own instances of these module's classes, and they would be able to access the scriptDict originally setup in the StoredData module (with a constructor in the Event Handler).

Just as a reference to what I'm talking about: The RFC mod currently uses a scriptDict that is saved as scriptData in the CyGame instance. This scriptDict is unpickled and pickled several times on any given turn. (Changing a stored value actually involves loading and unpickling the entire scriptDict two times, before it is pickled and stored all over again.) As far as I can tell this is causing lag issues.

I realize this is done in order to have all parts of the mod share the same data. What I'm trying to achieve is to limit pickling and storing the scriptDict to preSave only. By disabling autosave this would potentially take away a lot of the current lag!
 
Ok now I'm really wondering what's up. I thought, as everyone was thinking, that the error I was getting had something to do with the mod saving data and that was somehow conflicting with dynamiccivnames.

Now I'm not so sure, I've gone through the code and removed all lines that save scriptdata and replaced them with calls to the sdtoolkit. I even blanked out the whole onbegingameturn function so it's just an empty nothing and still I get an error onbeginplayerturn from dynamiccivnames. Wth?
 
Ok now I'm really wondering what's up. I thought, as everyone was thinking, that the error I was getting had something to do with the mod saving data and that was somehow conflicting with dynamiccivnames.
These things might not be very obvious...

Now I'm not so sure, I've gone through the code and removed all lines that save scriptdata and replaced them with calls to the sdtoolkit. I even blanked out the whole onbegingameturn function so it's just an empty nothing and still I get an error onbeginplayerturn from dynamiccivnames. Wth?
You really need to post all exceptions here so that we can locate the error.

Like if you "blank out" the contents of a function (the body) you would probably get a syntax error or something like that. Because there is no such thing as a non-body of a function. If you wanna replace it with a non-statement, you use:
Code:
pass
Its sort of a place-holder statement.
 
These things might not be very obvious...


You really need to post all exceptions here so that we can locate the error.

Like if you "blank out" the contents of a function (the body) you would probably get a syntax error or something like that. Because there is no such thing as a non-body of a function. If you wanna replace it with a non-statement, you use:
Code:
pass
Its sort of a place-holder statement.

There was no other exception other than the one I've already posted. It doesn't show up until I end the turn. It didin't give an error without 'pass'

EDIT:

I think now the problem was I was loading a savegame and testing the python, it had perhaps the corrupt data saved with it
 
There was no other exception other than the one I've already posted. It doesn't show up until I end the turn. It didin't give an error without 'pass'
Without knowing what you've done exactly, an empty body should result in an exception - on initialization or whenever the Python is reloaded. Otherwise I'd suspect that module isn't even being used! (It won't load if it isn't imported by another module to begin with.)

I think now the problem was I was loading a savegame and testing the python, it had perhaps the corrupt data saved with it
Yeah, the point of storing scriptData is that it gets saved along with the save game. Like in the CyGame, CyPlayer or CyPlot instance. But having scriptData present that isn't used any more can't hurt either - supposing you've commented out all the code that is accessing it.

But you probably still wanna start a new game - so that the sdToolkit is initialized properly. (There are some objects created once it is setup.) Also remember to store the default values for every piece of stored data on initialization, so that these values are in there from the get-go. (Some piece of code trying to access non-existing values would cause an exception.)
 
Exactly, the list is saved in the scriptdata of that saved game. You have two choices:

1. Start with a new game.

2. Fix the data. In onLoad you can check if the script data is a list, and if so fix it to what it should be.

Code:
# psuedocode
game = CyGame()
data = unpickle(game.getScriptData())
if type(data) == 'list':
    newData = dict()
    newData['myData'] = data
    game.setScriptData(pickle(newData))
 
SdToolkit and BugData require a dictionary as the root structure of the script data. They store dictionaries in that big dictionary, keyed by the mod ID with which they are initialized. By storing a list there, you mess up SdToolkit.

Best if you can start a new game. Do you have to load that saved game?
 
SdToolkit and BugData require a dictionary as the root structure of the script data. They store dictionaries in that big dictionary, keyed by the mod ID with which they are initialized. By storing a list there, you mess up SdToolkit.

Best if you can start a new game. Do you have to load that saved game?

No I don't have to load a game, since I narrowed a problem down to suspecting the problem was loading the game (and variables) I stopped doing that. I'm attempting to rewrite all data storage calls from scratch currently.
I'm not sure how the effect originally worked but here's what I'm attempting to achieve at present.

I'm wanting to store these variables:
* A Plagueflag (is plague on or not)
* A list of coordinates for plagued cities
* and a plague countdown timer


Now that I see that list I guess I don't really need the plagueflag because I can just check if there are coordinates stored. Hmm..
Anyway, here's my initialization code

Code:
	def initScriptData(self):
	
		iZero = 0
		aScriptData = [iZero]
		# Initialize PlagueData	
		self.msg("Initializing PlagueData.")
		
		if( not SDTK.sdObjectExists( "PlagueData", gc.getGame() ) ) :
			SDTK.sdObjectInit( "PlagueData", gc.getGame(), {} )
			SDTK.sdObjectSetVal( "PlagueData", gc.getGame(), "PlagueBool", aScriptData )
			SDTK.sdObjectSetVal( "PlagueData", gc.getGame(), "PlagueCoords", aScriptData )
			SDTK.sdObjectSetVal( "PlagueData", gc.getGame(), "PlagueCounter", aScriptData )			
		elif( SDTK.sdObjectGetVal("PlagueData", gc.getGame(), "PlagueBool") == None ) :
			SDTK.sdObjectSetVal( "PlagueData", gc.getGame(), "PlagueBool", aScriptData )
			SDTK.sdObjectSetVal( "PlagueData", gc.getGame(), "PlagueCoords", aScriptData )
			SDTK.sdObjectSetVal( "PlagueData", gc.getGame(), "PlagueCounter", aScriptData )

This seemed to work one time, then I quit the game and restart and then I got an error message saying something like object doesn't support assignment when it worked fine the time before.

This the code that worked fine once, then quit, start new game to test somethign else and it throws the exception.
Code:
	def setPlagueState(self, iValue):
		aszScriptData = SDTK.sdObjectGetVal("PlagueData", gc.getGame(), "PlagueBool")		
		aszScriptData[0] = iValue
		SDTK.sdObjectSetVal( "PlagueData", gc.getGame(), "PlagueBool", aszScriptData )
 
I'm attempting to rewrite all data storage calls from scratch currently.
Good for you! :king:

This the code that worked fine once, then quit, start new game to test somethign else and it throws the exception.
Once again: What is the exception?

If you know how to interpret them, an exception can in fact give you the answer to the question why the code doesn't work. Its not something random that the interpreter just throws together to annoy you.

By the way, the exceptions should be printed out into PythonErr.log and/or PythonErr2.log - simply copy paste the latest ones here once they occur.
 
@Baldyr, love the new Lena updates.

Code:
Traceback (most recent call last):
  File "BugEventManager", line 361, in _handleDefaultEvent
  File "GreatDoctorEventManager", line 59, in onModNetMessage
  File "GreatDoctorEventManager", line 217, in doEventPlague
  File "GreatDoctorEventManager", line 331, in setPlagueCounter
TypeError: object does not support item assignment

It works when I test the plague effect once, but I try the 2nd time and I get this.
 
@Baldyr, love the new Lena updates.
;)

Code:
TypeError: object does not support item assignment
:eek: It actually pretty much says what you told before... But the interesting bit is of course that it is a "TypeError", because someone more familiar with sdToolkit should be able to decipher this riddle. :p

And what does "File "GreatDoctorEventManager", line 331, in setPlagueCounter" say exactly?
 
Well, all I can say without getting into sdToolkit myself is that its every bit as advanced as I remember it to be. :D

You probably just have to figure out how assignment is done. Right now you're treating aszScriptData as if it was a list or a tuple. Perhaps its a dictionary instead? In that case it should probably be something like:
Code:
		aszScriptData["PlagueData"] = iValue
But don't take my word for it. :rolleyes: Perhaps you do assignment with a function/method instead? (Like sdObjectSetVal() that appears on the next line.)
 
If you don't mind tying your mod to BUG I recommend that you use the BugData module instead of SdToolkit. As I said above, it's fully save-compatible. The major benefit to using BugData is that it manages the load/save cycle for you automatically. It detects when you've changed the values and saves the next time it's needed.

Code:
import BugData
import BugUtil

BugUtil.fixSets(globals())

...

	def getCounter(self):
		table = BugData.getTable("Plague")
		if "counter" in table:
			return table["counter"]
		else:
			return 0

	def setCounter(self, iValue):
		table = BugData.getTable("Plague")
		table["counter"] = iValue

The same code slightly modified (change the names) will work for the boolean value. Note that within the table returned by getTable() the keys ("counter" here) only have to be unique within that table--not across all mods. Thus there's no reason to prefix everything with "Plague". This will save some room. Also notice the distinct lack of a save call here. BugData takes care of it for you.

For the coordinates you can use a set so that operations will be faster on it. By nesting it inside a sub-table you only pay the pickle/unpickle price when accessing the coordinates but not the counter.

Code:
	def isCityInfected(self, pCity):
		# the "cities" table contains a single set object "coords"
		# that contains (x,y) points
		table = BugData.findTable("Plague", "cities")
		if table:
			point = (pCity.getX(), pCity.getY())
			return point in table["coords"]
		return False

	def infectCity(self, pCity):
		table = BugData.getTable("Plague", "cities")
		point = (pCity.getX(), pCity.getY())
		if "coords" not in table:
			table["coords"] = set()
		table["coords"].add(point)

Without a more detailed description of exactly what happens in your mod, I can't know what other functions you'll need. Hopefully this gets you started. You operate on each table as you would a normal dictionary with key/value pairs.

Note: The fixSets() call at the top is to make this work on a Mac.
 
Thanks EF for the code. Let me tell you what I have in mind at the moment.

I reviewed the original effect and it has something like a base 10 turn chance, then 50% chance to go away. I recall playing Tsentom's mod and I think the plague seemed like it never went away lol. I didn't even look at the basic spreading algorithim.

So I've thought about what exactly I want the plague effect to do and I came up with this:

10-15 turns of spreading plague (effect is -8 unhealthiness). Once those turns are up, all of the cities affected by that outbreak to get better. Maybe at that point they can be immune to replaguing for a bit.

I'm thinking I will make medicine make you immune and I've got an early wonder as well that will make you immune to plagues. These are a couple if statements on the outbreak, easy to do.

So do you guys think my plan is good? Is there anything I'm missing?

This could potentially get really really complex with multiple plagues spreading from multiple cities for multiple players. Do my original variables cover this? I think I may need a unique id thing to tie a group of plagues together.


*PlagueFlag
*PlagueCounter
*ID (?)
*PlagueCities (-> coordinates in case player renames the city or something crazy -> But what if city is razed?)


I've looked into it and I'm not yet good, and I certainly don't know the bugdata way, to go through lists. But maybe I should look more at transversing dictionaries?
 
Well as I wrote the code above the coordinates are all stored in a set. A set is an unordered collection that allows no duplicates. So it's exactly like a dictionary of keys without values. You can iterate over a set just like a list. You can add and remove items from a set just like lists and dictionaries.

By storing the coordinates of each city you don't have to do anything when the city changes hands. If it gets razed just remove those coordinates from the set, and perhaps spread the plague to each neighboring city at a somewhat high percent chance.

You can easily loop over all cities to check for spread along trade routes.

If you want the plague to end at a different time for each city based on a timer, you need to track when the city became infected. That means you probably want to store a turn number with each city, so you'll want a dictionary after all.

Storing the BugData way doesn't change how you store your data or what objects you can use--only how you get at that data. As you can see above I store an int and a bool and a set. You can store whole objects if you want, too. Do not try to store CyXXX objects though as they are not valid when pickled.
 
Code:
	def setPlague(self, pTurns, pCoords):
		table = BugData.getTable("Plague")
		table["counter"] = pTurns
		table["city"] = pCoords

So if I do this will I be able to go back later; find the city, (ie table[x]) and take out both the counter and city (both entries)?

If it spreads to another city I can create a new entry using the same counter and onthe beginning of turn just remove all instances when the counter reaches 0.
 
Back
Top Bottom