Question about pCity pointers.

General Tso

Panzer General
Joined
Oct 12, 2007
Messages
1,547
Location
U. S. of A.
In my mod cities can't be destroyed. If I store the values of all the pCity pointers, are they guaranteed to be valid for the rest of the game? Or, could their values be changed by the game? I currently find the cities by searching through a list of cities and matching their name and I would like to eliminate the search.
 
They would not be valid. When you capture a city (change owners in any manner) the game actually creates a completely new city, copies all the data of the old city into it, and then deletes the old city. So you now need a new pointer for that city.
 
You best bet is to store a tuple (player ID, city ID) in Python. You'll need to update this whenever the city changes hands. You can attach a string to each CyCity using CyCity.setScriptData(value) and getScriptData() to read it. These are saved with the game automatically. You can use this to track how cities change hands as this data is copied when the city is recreated I believe.
 
Thanks for the information. I was afraid that there would be problems with that. Some of the my code involving cities is used to update part of CvMainInterface so I would like to use a city pointer instead of the city ID. Do either of you know if onCityAcquired and/or onCityAcquiredAndKept would catch all of the times that the city is changed. If either of you feel confident that they do, I'll just update my city pointers at that time instead of finding them when I update the display.
 
You'll have to run a test to make sure that onCityAcquiredAndKept is fired when a city is gifted, liberated, or acquired by cultural pressure. BTW, you aren't saving these in script data are you? That would be a Really Bad Idea(tm).
 
I'm not saving them as script data. Why would that be a bad idea?
 
A Python Cy object is merely a proxy that holds a pointer to a C++ Cy object which itself holds a pointer to a C++ Cv object (a wrapper around a wrapper around an object if you like). All the C++ pointers are merely addresses in memory where the object's data is stored. When you reload a game, all those objects get recreated, and their addresses most likely change, invalidating any saved pointers.

I have no idea how Python would pickle the pointers. Civ uses a Python-to-C++ framework called Boost. I haven't looked into its internals, but saving memory addresses across program runs is generally unsafe.

Also, using names to identify cities will break as soon as a player renames their city. If you need to keep track of a city from founding to razing, I would assign your own unique ID to each city when it's founded.

To do this you keep track of a single "next city ID" that you store in CyGame's script data (search for Stone-D's sdToolkit to make this easier--actually the advanced version will help). In onCityFounded, grab the value, assign it to the city using its script data, and increment the master value in CyGame. Then you can store a dictionary of ID -> whatever data you need to keep about the city.

This is only necessary if you need to link cities together, however. If you just want to store data about a city, put it in its script data.
 
Thank you, that was very informative. I should have figured that one out, I've already had pickle problems with pointers (it just didn't involve Civ4). The issues that you mention concerning renaming, razing, etc. shouldn't be a problem since there is no city founding, razing, or renaming possible in my mod. I have much of the data I use for cities in my own class called PgCity (my cities don't work the same as normal CivCities). I think I'll try to move anything that is needed in the "high speed" part of the code from pCity to PgCity. I'll have to check and see if they are things that won't be changed during the course of the game, or at least can be updated somewhere else. Thanks for the help, I think you may have set me off on the right direction.
 
Knowing that you know about pickle already gives me a better idea of where your skills are currently, so let's see if this helps a little more.

The first method I would code as it's the easiest to write is to store your PgCity pickled in the CyCity's script data. Every time you need to do something with PgCity you'll need to unpickle it and repickle it if you change its state. This could obviously have some performance problems, but I find it's best to code what works first and then optimize if necessary.

I looked over the code that assigns CvCity IDs, and it's a bit complicated. Civ has its own resizing array class (FFreeListTrashArray), and it encodes the index in the array where the object is placed and a monotonically increasing value into a single ID when the object is first created. This means a CvCity ID is unique only within the player that owns it. When a city changes hands, it gets a new ID.

If you want to keep the PgCity's unpickled while the game is loaded you can store them in a dict() using (player ID, city ID) as the key. These should be stored in the PgCity so that you can locate it in the dict using its old key, remove it, and add it under the new key.

Code:
class PgCity:

    def __init__(self, city):
        self.update(city)
        ... but don't store city ...

    def update(self, city):
        self.key = (city.getOwner(), city.getID())

    def getKey(self):
        return self.key

...

# event manager

cities = dict()  # clear this in onLoadGame and onGameStart

...

onCityBuilt(argsList):
    city = argsList[0]
    pgCity PgCity(city)
    cities[pgCity.getKey()] = pgCity

onCityAcquiredAndKept(argsList):
    iOwner, city = argsList
    if city.getScriptData():
        pgCity = unpickle(city.getScriptData())
        del cities[pgCity.getKey()]
        pgCity.update(city)
        cities[pgCity] = pgCity
        city.setScriptData(pickle(pgCity))
 
Thanks alot for the help. I'm going to compare this to what I already have and see what I can come up with.
 
Top Bottom