ProcessBuilding

Tholish

Emperor
Joined
Jul 5, 2002
Messages
1,344
Location
Japan
I'm trying to make it so that buildings don't work when conditions would preclude them being constructed. For instance, a temple should not continue to create happiness if the religion for it no longer exists in the city .

The first thing I wanted to do with this though was I wanted to make a Steel Foundry that requires Iron and Coal and produces Steel, but I want it to stop working if the supply of Iron is cut off.

Thus, I tried to modify CvCity like this

Code:
void CvCity::processBuilding(BuildingTypes eBuilding, int iChange, bool bObsolete)//this is the function I'm dealing with
{
...//didn't include a bunch of irelevant stuff I didn't change
//this is the block that I assume normally gives a building free bonuses 
		if (GC.getBuildingInfo(eBuilding).getFreeBonus() != NO_BONUS)
		{
			changeFreeBonus(((BonusTypes)(GC.getBuildingInfo(eBuilding).getFreeBonus())), (GC.getGameINLINE().getNumFreeBonuses(eBuilding) * iChange));
		}

//and I followed it with this, intending to change free bonuses to zero if the prereq bonus is not there
//Tholish NoFreeBonusWithoutPrereqBonus START
		if (GC.getBuildingInfo(eBuilding).getPrereqAndBonus() != NO_BONUS)		{
			if (!hasBonus((BonusTypes)GC.getBuildingInfo(eBuilding).getPrereqAndBonus()))
			{
				if (GC.getBuildingInfo(eBuilding).getFreeBonus() != NO_BONUS)
				{
					changeFreeBonus(((BonusTypes)(GC.getBuildingInfo(eBuilding).getFreeBonus())), (0 * iChange));
				}
			}
		}
//Tholish NoFreeBonusWithoutPrereqBonus END
...

I have tried to mess with Process Building in other ways, and any time I change anything about it, I get something that compiles but causes a loss of interface on game start. In the example above, I get a loss of interface if any building has the conditions for it to work as I want, but otherwise it works fine.

Am I misunderstanding what ProcessBuilding does? Why the loss of interface? Error popup says its related to tech tree and shows the science advisor screen with nothing filled in, then I get the main screen with no HUD.
 
A better way to to check is at the end of each turn. I have vicinity resource code, and if a resource no longer is in the city vicinity, the buildings turn off, but if the resource comes back so does the building. It's fairly trivial code:

Code:
void CvCity::checkVicinityBonus()
{

	PROFILE("CvCity::checkVicinityBonus()");
	bool bHasVicinityBonus = false;
	bool bNeedsVicinityBonus = false;
	BuildingTypes eLoopBuilding;
	for (int iI = 0; iI < GC.getNumBuildingClassInfos(); iI++)
	{
		eLoopBuilding = ((BuildingTypes)(GC.getCivilizationInfo(getCivilizationType()).getCivilizationBuildings(iI)));

		if (eLoopBuilding != NO_BUILDING)
		{
			if (!GET_TEAM(getTeam()).isObsoleteBuilding(eLoopBuilding))
			{
				if (getNumBuilding(eLoopBuilding) > 0)
				{
					for (int iJ = 0; iJ < GC.getNUM_BUILDING_PREREQ_OR_BONUSES(); iJ++)
					{
						if ((GC.getBuildingInfo(eLoopBuilding).getPrereqVicinityBonus() != NO_BONUS) || (GC.getBuildingInfo(eLoopBuilding).getPrereqOrVicinityBonuses(iJ) != NO_BONUS))
						{
							bNeedsVicinityBonus = true;
							for (int iK = 0; iK < getNumCityPlots(); iK++)
							{
								CvPlot* pLoopPlot = plotCity(getX_INLINE(), getY_INLINE(), iK);
								if (!pLoopPlot == NULL)
								{
									if ((pLoopPlot->getBonusType() == GC.getBuildingInfo(eLoopBuilding).getPrereqVicinityBonus()) || (pLoopPlot->getBonusType() == GC.getBuildingInfo(eLoopBuilding).getPrereqOrVicinityBonuses(iJ)))
									{
										if (pLoopPlot->getOwnerINLINE() != NO_PLAYER)
										{
											if (pLoopPlot->getOwnerINLINE() == getOwnerINLINE())
											{
												if (pLoopPlot->isHasValidBonus() && pLoopPlot->isConnectedTo(this))
												{
													bHasVicinityBonus = true;
													break;
												}
											}
										}
									}
								}
							}
							if (bHasVicinityBonus)
								break;
						}
					}
					//We have the resource, turn on any buildings that are disabled
					if (bNeedsVicinityBonus && bHasVicinityBonus)
					{
						if (getNumBuilding(eLoopBuilding) > getNumActiveBuilding(eLoopBuilding))
						{
							setNumRealBuilding(eLoopBuilding, (getNumBuilding(eLoopBuilding) - getNumActiveBuilding(eLoopBuilding)));
						}
					}
					//we lack the nessecary resource, turn off the building
					else if (bNeedsVicinityBonus && !bHasVicinityBonus)
					{
						setNumRealBuilding(eLoopBuilding, (-getNumActiveBuilding(eLoopBuilding)));
					}
				}
			}
		}
	}
}
I was worried checking all the buildings would be an expensive call, but it takes less than 1 ms to run for all the cities total, and it is checked in CvCity::doTurn. You can replace the plot check code with whatever you need.
 
I didn't know Afforess had posted and came up with this

Code:
void CvCity::doDecay()
{
	int iI;

	for (iI = 0; iI < GC.getNumBuildingInfos(); iI++)
	{
		BuildingTypes eBuilding = (BuildingTypes) iI;
//Tholish UnbuildableBuildingDeletion START
		if (getNumRealBuilding((BuildingTypes)iI) > 0)
		{
			if (!canConstruct(eBuilding))
					{
						setNumRealBuilding(((BuildingTypes)iI), 0);
					}
		}
//Tholish UnbuildableBuildingDeletion END

Which seems to work. I tested it, started an ancient era game, placed a Harbor in a city before Sailing, the next turn it vanished.

DoDecay checks every city every turn and has everything you need already in it since it is looking to see if progress on buildings that have been started but not finished before switching to another one needs to be reduced.

You never know, problems might crop up.

EDIT: Surprise! A problem cropped up. It deletes all buildings. CanConstruct might be wrong, or used wrong.
 
I didn't know Afforess had posted and came up with this

Code:
void CvCity::doDecay()
{
	int iI;

	for (iI = 0; iI < GC.getNumBuildingInfos(); iI++)
	{
		BuildingTypes eBuilding = (BuildingTypes) iI;
//Tholish UnbuildableBuildingDeletion START
		if (getNumRealBuilding((BuildingTypes)iI) > 0)
		{
			if (!canConstruct(eBuilding))
					{
						setNumRealBuilding(((BuildingTypes)iI), 0);
					}
		}
//Tholish UnbuildableBuildingDeletion END

Which seems to work. I tested it, started an ancient era game, placed a Harbor in a city before Sailing, the next turn it vanished.

DoDecay checks every city every turn and has everything you need already in it since it is looking to see if progress on buildings that have been started but not finished before switching to another one needs to be reduced.

You never know, problems might crop up.

EDIT: Surprise! A problem cropped up. It deletes all buildings. CanConstruct might be wrong, or used wrong.

I believe canConstruct returns negative if the building is already built, so... :p
 
I'm not a professional programmer and could get something wrong or missed something, but is that your entire code that checks if the building should be destroyed? Because it seems that you check if the building has been built, then you check if you can build it again and if not, you destroy it. That could be the problem I guess.

EDIT: Aww, you posted it first Androrc the Orc...
 
Ah, you're right. So, I bypassed it. Made a clone of the CanBuild function (both the one in CvCity and the one in CvPlayer) renamed and customized it and used that one instead.

Tested it on a Play Now start and it works. Barracks and Palace don't go away, Hindu Monastery and Factory do.

Sources included in download, thanks for the help and advice.

http://forums.civfanatics.com/downloads.php?do=file&id=14615
 
Ah, you're right. So, I bypassed it. Made a clone of the CanBuild function (both the one in CvCity and the one in CvPlayer) renamed and customized it and used that one instead.

Recipe for disaster... Why not add a new parameter to CanConstruct that ignores existing buildings?
 
Side note: I think you want to check for this at the start of CvCity::doTurn(). Nothing can happen during a city's turn to make a resource available/unavailable, yet things can definitely happen between the end turn (when you make the changes) and the next turn (where the city's buildings will take effect).

Of course, you also want the effects to change after the AIs move but before the player starts their turn--long before doTurn() comes into play. In BUG I added the BeginActivePlayerTurn event for this, but I don't know what the equivalent would be in the SDK. Ideally you'd do these checks when the player starts their moves and again at the start of doTurn() to catch what the player has done during their moves.
 
OK, I'll move the call to CanKeep to DoTurn, just in case there's a problem with something happening with a building having an effect and then going away, though its seems to work fine in DoDecay, which is called by DoTurn. For elegant code perhaps that's best since this is not what DoDecay is FOR.

Doing it with CanBuild would be kind of hard and I'd have to add a bunch of stuff just to manage the sidestep. While the total amount of code might be smaller if I tried to make CanBuild do double duty, the code that doesn't actually contribute to anything is less with a separate function. Its so much easier to just make another function, and there you can access each action in isolation from what it does in CanBuild.

Also maybe it would be really classy if I made XML fields to make it easy to reset exactly which unbuildability conditions made deletions. For instance, one modder might want to obsolete buildings that have lost prereq bonuses but keep buildings that have lost prereq rels. But really, since its a separate function in the dll, its not that hard and Civ4BuildingInfos is already way long and adding all that extra XML and dll support for it would just be massive work for something that woudl be used rarely.

So I think this way is not only easy to make, its also easy to use, a happy medium


In cloning CanBuild I took out all the parameters except eBuilding because they are used in CanBuild mainly by the check for prereq buildings, which I didn't make CanKeep check for. So the way I did it, you can lose your Library (if there were some reason for it) and keep the University. (in fact this provides an xml workaround to exempting a building from the general rule of unbuildable buildings being removed--have the prereqs be in an added prereq building) Also, no check for number of cities requirement, so the Palace doesn't go away.
 
OK, I'll move the call to CanKeep to DoTurn, just in case there's a problem with something happening with a building having an effect and then going away, though its seems to work fine in DoDecay, which is called by DoTurn. For elegant code perhaps that's best since this is not what DoDecay is FOR.

Using a separate, nearly identical function to CanConstruct is not a wise idea. If, in the future, you add a new building prereq, and forget to update CanBuild, you will suddenly be surprised when CanBuild no longer works as expected. Buildings that are not supposed to exist will fail to be removed, and buildings that should exist may be destroyed incorrectly. Keeping two functions that are nearly identical will catch you up in the future. You should just add a new parameter to CanConstruct and use it that way, so that any future changes you make to CanConstruct are always compatible.
 
Top Bottom