[PYTHON] Getting Info from Arrays in CIV4CivilizationInfos.xml

LPlate2

Warlord
Joined
Dec 27, 2018
Messages
299
Hi,

I'm trying to create some new arrays in CIV4CivilizationInfos.xml. On checking the existing arrays in this file, I'm not clear on how to call them in python (or at least when I try, I always get a False response). So I don't know if I'm making a mistake in how I set up my arrays in the dll or if its just python errors.

The basic format of the arrays I want to create matches
Spoiler :
Code:
           <FreeBuildingClasses>
               <FreeBuildingClass>
                   <BuildingClassType>BUILDINGCLASS_PALACE</BuildingClassType>
                   <bFreeBuildingClass>1</bFreeBuildingClass>
               </FreeBuildingClass>
           </FreeBuildingClasses>

The bTest in the following python always seems to yield a False response,
Spoiler :
Code:
       infoCiv = gc.getCivilizationInfo(pPlayer.getCivilizationType())
       bTest = infoCiv.isCivilizationFreeBuildingClass(0)
       if bTest:

Per CvInfos.h,
Code:
DllExport bool isCivilizationFreeBuildingClass(int i) const;               // Exposed to Python
, so I thought the above should work. What am I doing wrong?

Similarly, can someone give an example of how to use the equivalent integer call, e.g. getCivilizationUnits(int i) in python?

Looking at the arrays configured for CvCivilizationInfo, it seems that for any array in the format above, python is typically only configured to return a boolean or integer
Spoiler :
Code:
       .def("getCivilizationBuildings", &CvCivilizationInfo::getCivilizationBuildings, "int (int i)")
       .def("getCivilizationUnits", &CvCivilizationInfo::getCivilizationUnits, "int (int i)")
       .def("getCivilizationFreeUnitsClass", &CvCivilizationInfo::getCivilizationFreeUnitsClass, "int (int i)")
       .def("getCivilizationInitialCivics", &CvCivilizationInfo::getCivilizationInitialCivics, "int (int i)")

       .def("isLeaders", &CvCivilizationInfo::isLeaders, "bool (int i)")
       .def("isCivilizationFreeBuildingClass", &CvCivilizationInfo::isCivilizationFreeBuildingClass, "bool (int i)")
       .def("isCivilizationFreeTechs", &CvCivilizationInfo::isCivilizationFreeTechs, "bool (int i)")
       .def("isCivilizationDisableTechs", &CvCivilizationInfo::isCivilizationDisableTechs, "bool (int i)")
. Is this understanding correct and is there a reason for it?

thanks
 
Code:
           <FreeBuildingClasses>
               <FreeBuildingClass>
                   <BuildingClassType>BUILDINGCLASS_PALACE</BuildingClassType>
                   <bFreeBuildingClass>1</bFreeBuildingClass>
               </FreeBuildingClass>
           </FreeBuildingClasses>
This looks fine. I assume you also added the new tags to the schema? (I think you'd get an error otherwise)

Per CvInfos.h,
Code:
DllExport bool isCivilizationFreeBuildingClass(int i) const;               // Exposed to Python
, so I thought the above should work. What am I doing wrong?
This is also fine, except you should delete the "DllExport". It is only necessary for functions the exe calls, and it obviously doesn't call your new function.

However, there is a lot of other stuff you have to add: the variable to store the data in CvCivilizationInfos, its initialization, the function implementation and reading the value from XML. Do you have all that?

Similarly, can someone give an example of how to use the equivalent integer call, e.g. getCivilizationUnits(int i) in python?
For this, just look how getCivilizationUnits() works. Search for "getCivilizationUnits" and "m_piCivilizationUnits".

Looking at the arrays configured for CvCivilizationInfo, it seems that for any array in the format above, python is typically only configured to return a boolean or integer
Code:
       .def("getCivilizationBuildings", &CvCivilizationInfo::getCivilizationBuildings, "int (int i)")
       .def("getCivilizationUnits", &CvCivilizationInfo::getCivilizationUnits, "int (int i)")
       .def("getCivilizationFreeUnitsClass", &CvCivilizationInfo::getCivilizationFreeUnitsClass, "int (int i)")
       .def("getCivilizationInitialCivics", &CvCivilizationInfo::getCivilizationInitialCivics, "int (int i)")

       .def("isLeaders", &CvCivilizationInfo::isLeaders, "bool (int i)")
       .def("isCivilizationFreeBuildingClass", &CvCivilizationInfo::isCivilizationFreeBuildingClass, "bool (int i)")
       .def("isCivilizationFreeTechs", &CvCivilizationInfo::isCivilizationFreeTechs, "bool (int i)")
       .def("isCivilizationDisableTechs", &CvCivilizationInfo::isCivilizationDisableTechs, "bool (int i)")
. Is this understanding correct and is there a reason for it?
I'm not entirely sure what you mean. You can return a wide range of other types, too. There is no reason why you couldn't have a "string array", in fact, there is one:
Code:
.def("getUnitNames", &CvUnitInfo::getUnitNames, "string (int i)")
It's just that many properties are sets of infotypes (represented as bool arrays), or mappings from infotypes to other infotypes (represented as int array).
 
Hi,

The examples I gave above from CIV4CivilizationInfos.xml and the CvInfos.cpp are from the existing FFH sourcecode. i.e. There should be zero risk of these having been set up incorrectly. However, I can't seem to get the associated python functions to work for these.
If I can't get the existing python functions to work, then even if I make all of the applicable changes to CvInfos.cpp, CvInfos,h and CyInfoInterface2.cpp for my new arrays, I'll be in the same position of not knowing exactly how to get the new functions to work using python.

This is also fine, except you should delete the "DllExport". It is only necessary for functions the exe calls, and it obviously doesn't call your new function.
Thanks for this bit of advice. A mistake like this is par for the course in my cut and paste school of learning to mod. I do believe that I have included all of the required entries for my new arrays but to see if they work, I first want to make sure that I can properly use the similar arrays on which I've based them.

My key question is what's wrong with the following?
Spoiler :
Code:
      infoCiv = gc.getCivilizationInfo(pPlayer.getCivilizationType())
       bTest = infoCiv.isCivilizationFreeBuildingClass(0)
       if bTest:
Every Civilization has a free building class, i.e. its palace, so why does the above not result in a True?
Spoiler :
Code:
           <FreeBuildingClasses>
               <FreeBuildingClass>
                   <BuildingClassType>BUILDINGCLASS_PALACE</BuildingClassType>
                   <bFreeBuildingClass>1</bFreeBuildingClass>
               </FreeBuildingClass>
           </FreeBuildingClasses>

How does one determine in python what the BuildingClassType is for such an array? Have I misunderstood what isCivilizationFreeBuildingClass is returning? Is it just giving a 1 or 0 or is it actually returning an integer associated with an Infotype within the array?

It's just that many properties are sets of infotypes (represented as bool arrays), or mappings from infotypes to other infotypes (represented as int array).
On revisiting the xml and sourcecode, the arrays where a call is in the form:
.def("getCivilizationBuildings", &CvCivilizationInfo::getCivilizationBuildings, "int (int i)") seems to be used are those where the components within the array are first the class type and then the type, e.g.
Spoiler :
Code:
               <Unit>
                   <UnitClassType>UNITCLASS_BASIUM</UnitClassType>
                   <UnitType>NONE</UnitType>
               </Unit>
Whereas, those where a call is in the form:
.def("isCivilizationFreeBuildingClass", &CvCivilizationInfo::isCivilizationFreeBuildingClass, "bool (int i)") are those where the components within the array are the type and then a boolean (which in all cases seems to be 1), e.g.
Spoiler :
Code:
           <FreeBuildingClasses>
               <FreeBuildingClass>
                   <BuildingClassType>BUILDINGCLASS_PALACE</BuildingClassType>
                   <bFreeBuildingClass>1</bFreeBuildingClass>
               </FreeBuildingClass>
           </FreeBuildingClasses>
For the arrays that I'm interested in adding at the moment this second one is definitely the type of array I want to use.
 
Last edited:
Every Civilization has a free building class, i.e. its palace, so why does the above not result in a True?
Maybe I understand your problem now. Your call
Code:
infoCiv.isCivilizationFreeBuildingClass(0)
returns True if and only if the building class with ID 0 is free for the civilization infoCiv.

The building class with ID 0 (the first one in the file) is BUILDINGCLASS_HERON_THRONE in FfH/MNAI. What you probably want is
Code:
infoCiv.isCivilizationFreeBuildingClass( gc.getInfoTypeForString( "BUILDINGCLASS_PALACE" ) )

Here, gc.getInfoTypeForString( "BUILDINGCLASS_PALACE" ) returns the ID of BUILDINGCLASS_PALACE.


An explanation for the different array types:

CivilizationFreeBuildingClass is a set of building classes. The game is representing the set as a bool array, where the i-th position in the array is true if the building class with ID i is in the set, and false otherwise. Accordingly, the length of the array is the number of building classes. For example, if you have 5 building classes, and BUILDINCLASS_PALACE has ID 3, then your XML would result in the following array (recall that we start counting from 0):
Code:
(false, false, false, true, false)
The function call isCivilizationFreeBuildingClass(i) just returns the bool value at the i-th position in that array, i.e. whether i is in the set.

Basically, sets are represented as a mapping from the building IDs to one of the boolean values true and false.

Now if we view CivilizationFreeBuildingClass as a set, we'd expect something like this in XML:
Code:
<FreeBuildingClasses>
   <BuildingClassType>BUILDINGCLASS_PALACE</BuildingClassType>
   <BuildingClassType>BUILDINGCLASS_...</BuildingClassType>
</FreeBuildingClasses>
However, for some reason, the Civ4 developers choose to represent sets such as CivilizationFreeBuildingClass as a Buildingclass -> boolean mapping in XML, and . Thus, setting <bFreeBuildingClass> to 0 means that building class is not in the set. However, as all building classes are not in the set unless specified, an entry with <bFreeBuildingClass> set to 0 just does nothing. I don't really know why they did it in this overly verbose way, maybe they wanted support more modular mods, where it makes sense that a mod removes something from a set.

Now CivilizationBuildings is just a mapping from building classes to buildings. As both are represented as ints, you have an int -> int mapping in the code.
 
Perfect explanation. Thanks.

The boolean array python calls are working for me now, including those for the new array I added in the DLL.
 
Top Bottom