Unit Prereq BuildingClass Array

Afforess

The White Wizard
Joined
Jul 31, 2007
Messages
12,239
Location
Austin, Texas
Firaxis made, In CvUnitInfo, a int called "PrereqBuilding" which allows someone to set a Building Type requirement for a unit. Well, in their infinite wisdom, it uses, literally, BUILDINGS, not BUILDINGCLASS's. Which means civilizations that have a UB, and need that unit are SOL. So, I set out to fix this by adding a PrereqBuildingClass. And while I was at it, I was going to make it an array, so units could require more than 1 buildingclass.

Things went fairly smoothly, setting up CvInfos. However, after a search, I found that Firaxis put their CanTrain code in CvPlot (Of all places!) Now, I am having issues getting my code to work. I think it's fairly sound logically, but my compiler disagrees. Here's my addition to CvPlot:

Code:
bool CvPlot::canTrain(UnitTypes eUnit, bool bContinue, bool bTestVisible) const
{
    CvCity* pCity = getPlotCity();
    BuildingClassTypes ePrereqBuildingClass; //Afforess
...
        for (int iI = 0; iI < GC.getNumBuildingClassInfos(); iI++) //Afforess
        {
            if (GC.getUnitInfo(eUnit).getPrereqBuildingClass(iI))
            {
            ePrereqBuildingClass = ((BuildingClassTypes)(GC.getBuildingClassInfo((BuildingClassTypes)(GC.getUnitInfo(eUnit).getPrereqBuildingClass(eUnit)))));
            
                if (ePrereqBuildingClass != NO_BUILDINGCLASS)
                {
                    return false;
                }
            }
        }
...
}

The bolded line is where the compiler is choking. Error message:

error C2440: 'type cast' : cannot convert from 'CvBuildingClassInfo' to 'BuildingClassTypes'

I know this means that I'm trying to force the compiler to convert variable types, but I don't see what exactly I did wrong. Hopefully someone can shed some light here.
 
You extract the building class type from the CvUnitInfo with

GC.getUnitInfo(eUnit).getPrereqBuildingClass(eUnit)​

[Side note: why are you passing eUnit to getPrereqBuildingClass()? I think this requires that you loop over the array. More on this later.]

Then you cast it to a BuildingClassTypes. That's where you should stop. But the problem is that you're using that value to lookup the CvBuildingClassInfo and cast that object to a BuildingClassTypes value (an int) using

(BuildingClassTypes)(GC.getBuildingClassInfo(...))​

You really want

ePrereqBuildingClass = (BuildingClassTypes) GC.getUnitInfo(eUnit).getPrereqBuildingClass(eUnit);​

On to the real problem. The reason canTrain() is in CvPlot is because it is used to test whether or not you can upgrade to a particular unit. Upgrading can happen outside of cities on bare plots. This does not bode well for your change unless you make it so you can only upgrade to these units in cities with the prereq buildings (makes sense).

Going further, this is an array you are adding, so you actually need to loop through all the buildings in the array and check if the city on the plot (if there is one) has them. The logic will look something like this:

Code:
for each building type
	if CvUnitInfo.getPrereqBuilding(type)
		if not isCity() or getPlotCity()->getNumActiveBuilding(type) == 0
			return false

This assumes the array is a bool array where each element is true if the unit requires that building type.
 
You extract the building class type from the CvUnitInfo with
GC.getUnitInfo(eUnit).getPrereqBuildingClass(eUnit)​
[Side note: why are you passing eUnit to getPrereqBuildingClass()? I think this requires that you loop over the array. More on this later.]

Then you cast it to a BuildingClassTypes. That's where you should stop. But the problem is that you're using that value to lookup the CvBuildingClassInfo and cast that object to a BuildingClassTypes value (an int) using
(BuildingClassTypes)(GC.getBuildingClassInfo(...))​
You really want
ePrereqBuildingClass = (BuildingClassTypes) GC.getUnitInfo(eUnit).getPrereqBuildingClass(eUnit);​
On to the real problem. The reason canTrain() is in CvPlot is because it is used to test whether or not you can upgrade to a particular unit. Upgrading can happen outside of cities on bare plots. This does not bode well for your change unless you make it so you can only upgrade to these units in cities with the prereq buildings (makes sense).

Going further, this is an array you are adding, so you actually need to loop through all the buildings in the array and check if the city on the plot (if there is one) has them. The logic will look something like this:

Code:
for each building type
    if CvUnitInfo.getPrereqBuilding(type)
        if not isCity() or getPlotCity()->getNumActiveBuilding(type) == 0
            return false
This assumes the array is a bool array where each element is true if the unit requires that building type.

Okay. Something like this then:

Code:
        for (int iI = 0; iI < GC.getNumBuildingClassInfos(); iI++) //Afforess
        {
            if (GC.getUnitInfo(eUnit).getPrereqBuildingClass(iI))
            {
                if (!isCity() || getPlotCity()->getNumActiveBuilding(BuildingClassType) == 0)
                {
                    return false;
                }
            }
        }
 
Hey, you didn't compile that before posting! :nono:

Replace

getNumActiveBuilding(BuildingClassType)​

with

getNumActiveBuilding((BuildingClassTypes)iI)​
 
Hey, you didn't compile that before posting! :nono:

Replace
getNumActiveBuilding(BuildingClassType)​
with
getNumActiveBuilding((BuildingClassTypes)iI)​

Sorry. Now my compiler is complaining that BuildingClassTypes should be BuildingTypes...

Should I be using getNumActiveBuildingClass? Is that even a function?
 
If that's a function, yes use it, but I doubt it's in CvCity. Instead you need to use the player's CvCivilizationInfo to get the correct building type from the building class. Use getOwnerINLINE() to get the player ID, then getCivilizationType(), etc.

Look first in CvInfos.cpp for the function that gives you the building type from the class for a specific civilization. Then search the SDK for uses of that function, and I'm sure you'll find a block of code you can copy to do all this.
 
If that's a function, yes use it, but I doubt it's in CvCity. Instead you need to use the player's CvCivilizationInfo to get the correct building type from the building class. Use getOwnerINLINE() to get the player ID, then getCivilizationType(), etc.

Look first in CvInfos.cpp for the function that gives you the building type from the class for a specific civilization. Then search the SDK for uses of that function, and I'm sure you'll find a block of code you can copy to do all this.

This function?

Code:
CivilizationTypes CvCity::getCivilizationType() const
{
    return GET_PLAYER(getOwnerINLINE()).getCivilizationType();
}

edit:

wait, I think you meant something more like this:
Code:
BuildingTypes eBuilding = (BuildingTypes)GC.getCivilizationInfo(getCivilizationType()).getCivilizationBuildings(i);
 
Yes, the last part. And pass in whatever is the building class you're checking for "i".

Code:
getNumActiveBuilding((BuildingTypes)GC.getCivilizationInfo(getPlotCity()->getCivilizationType()).getCivilizationBuildings(iI);)
 
Yes, the last part. And pass in whatever is the building class you're checking for "i".

Code:
getNumActiveBuilding((BuildingTypes)GC.getCivilizationInfo(getPlotCity()->getCivilizationType()).getCivilizationBuildings(iI);)

Great. Thanks. It compiled. Now to see if it works.
 
I'm curious, you said that CvUnitInfo already has a BuildingTypes prereq. Where is that field checked?
 
Hmm. I'm getting a strange assert on loading. Also, the effects don't seem to be working:

Assert Failed

File: CvXMLLoadUtilitySet.cpp
Line: 1575
Expression: bSuccess
Message: OWN TYPE - dependency not found: (null), in file: "modules\Afforess\Military Training\"

----------------------------------------------------------

XML:
Code:
			<PrereqBuildingClasses>
				<PrereqBuildingClass>BUILDINGCLASS_FORGE</PrereqBuildingClass>
			</PrereqBuildingClasses>

Schema:
Code:
	<ElementType name="PrereqBuildingClasses" content="eltOnly">
		<element type="PrereqBuildingClass" minOccurs="0" maxOccurs="*"/>
	</ElementType>
	<ElementType name="PrereqBuildingClass" content="eltOnly">
	</ElementType>
 
How are you loading those into the array? Perhaps they need to work in pairs:

Code:
<PrereqBuildingClasses>
	<PrereqBuildingClass>
		<PrereqBuildingClass>BUILDINGCLASS_FORGE</PrereqBuildingClass>
		<bPrereq>1</bPrereq>
	</PrereqBuildingClass>
</PrereqBuildingClasses>

It seems goofy to need it that way, but if you are using the TagPair funciton I think you need it.
 
How are you loading those into the array? Perhaps they need to work in pairs:

Code:
<PrereqBuildingClasses>
    <PrereqBuildingClass>
        <PrereqBuildingClass>BUILDINGCLASS_FORGE</PrereqBuildingClass>
        <bPrereq>1</bPrereq>
    </PrereqBuildingClass>
</PrereqBuildingClasses>
It seems goofy to need it that way, but if you are using the TagPair funciton I think you need it.

What should the schema be for that? Can you even have two tags like that, with the same name?
 
Oops, I didn't see you used that name already. The schema structure should look just like that for the tech-building-happiness. You'll need to change the third tag, of course.

Code:
<PrereqBuildingClasses>
	<PrereqBuildingClass>
		<[B]BuildingClassType[/B]>BUILDINGCLASS_FORGE</[B]BuildingClassType[/B]>
		<bPrereq>1</bPrereq>
	</PrereqBuildingClass>
</PrereqBuildingClasses>

Again, I'm only guess that this is the solution, but it seems to match similar structures elsewhere. Here's an example from CivilizationInfo: free techs:

Code:
			<FreeTechs>
				<FreeTech>
					<TechType>TECH_HUNTING</TechType>
					<bFreeTech>1</bFreeTech>
				</FreeTech>
				<FreeTech>
					<TechType>TECH_MINING</TechType>
					<bFreeTech>1</bFreeTech>
				</FreeTech>
			</FreeTechs>
 
Hmm. I still get that assert, and my Schema is this now:

Code:
	<ElementType name="BuildingClassType" content="textOnly"/>
	<ElementType name="bPrereq" content="textOnly" dt:type="boolean"/>
	<ElementType name="PrereqBuildingClasses" content="eltOnly">
		<element type="PrereqBuildingClass" minOccurs="0" maxOccurs="*"/>
	</ElementType>
	<ElementType name="PrereqBuildingClass" content="eltOnly">
		<element type="BuildingClassType" minOccurs="0" maxOccurs="*"/>
		<element type="bPrereq" minOccurs="0" maxOccurs="*"/>
	</ElementType>

XML:

Code:
			<PrereqBuildingClasses>
				<PrereqBuildingClass>
					<BuildingClassType>BUILDINGCLASS_FORGE</BuildingClassType>
					<bPrereq>1</bPrereq>
				</PrereqBuildingClass>
			</PrereqBuildingClasses>
 
Doesn't PrereqBuildingClass need to be defined with <ElementType> before it can be used with <element>? Also, you should drop the min/max for the value-holders so they each require exactly one:

Code:
<element type="BuildingClassType"/>
<element type="bPrereq"/>
 
Doesn't PrereqBuildingClass need to be defined with <ElementType> before it can be used with <element>?

I'm no schema expert, but the way I set it up, doesn't it declare it and define the elements at the same time? If I had set the schema up wrong, I would get LoadXML errors, not asserts...

Also, you should drop the min/max for the value-holders so they each require exactly one:

Code:
<element type="BuildingClassType"/>
<element type="bPrereq"/>

Okay.
 
Back
Top Bottom