Adding new Resource Yields to Future Techs

Check out setYieldHelp(). This is called near the top of setCommerceHelp() and is responsible for the Base Commerce and 50% Bureaucracy modifier lines. Here you will want to add your code for shrines and eventually techs.

One trick: the function uses

Code:
int iBaseProduction = city.getBaseYieldRate(eYieldType);

to get the value to show for Base X, but doesn't that already include your shrine yields? You'll need to calculate the shrine and tech yields and subtract them from the above value. Then you can show the base, shrine, and techs as separate lines with a Total line after them.

You need to decide how nice you want it to look, and that involves more code. If there's only one source (no shrine or techs), do you want Base and Total lines? Or just a Base? What about if there are no modifiers? Obviously the easiest is to always show a Base and Total. But do you want a separate Subtotal and Total if there are modifiers?

Here are all the possible sources shown.

Code:
Base Commerce: 12C
Shrines: 5C
Techs: 2C
------------------------
Subtotal Commerce: 19C
* +50% for Capital
------------------------
Total Commerce: 28C

Do you want to make the hover this long? Maybe you want to omit the subtotal line.
 
:dance:

Take a look at this screenie!!
ShrineYieldsScreenShot04.jpg


EF, if I could send you a nice bottle of wine, I would! :D

I don't think the Yields widget needs a Subtotal line, the breakdowns always say "90% of [Subtotal]" anyways, so I think an extra line is superfluous.

Now, my only remaining concerns are getting a widget for FOOD up and running (I should be able to just copy the BULL entries for that), and getting the Yield for the Shrine to show in the building list, the same way the Palace's does. Do you know where that's coded?
 
For commerce values you have CvCity::getBuildingCommerceByBuilding(), but there's nothing similar for yields. You'll need to add this new function and call it from CvMainInterface.py. Now, there is getBuildingYieldChange() which I believe is how the +2:hammers: for AP religion buildings is handled, and perhaps your new function should call this other one and add in the value from it.

Code:
int CvCity::getBuildingYieldByBuilding(BuildingTypes eBuilding, YieldTypes eYield) const
{
    int iYield = getBuildingYieldChange(GC.getBuildingInfo(eBuilding).getBuildingClass(), eYield);
    iYield += getGlobalReligionYield(eYield); // oopsies!
    return iYield;
}

Now we have a problem: getGlobalReligionYield() returns the total yield for all shrines, but the building list needs the value for each individual shrine. This is why I advised earlier to track the yields per-religion. You'll have to look at your code and figure out the best way to go about this.

You could start by creating a function that calculates the value from scratch for a single yield and religion combination. Then you can change the other functions if you want or just use them separately.

Keep in mind that when exposing functions to Python you cannot have two functions with the same name in the same class that differ only by parameter types. This is called function overloading and is acceptable in C++, but you cannot expose both functions to Python or the EXE. Instead you need different function names or to only expose one of the functions.
 
OK. But how then does the game display the Yields for Palace's & Supermarkets? These give +8 :commerce: & +1 :food: respectively.

And for my Shrines, do you think it would be possible for me to use the CountReligionLevels function? Then the value is already worked out, I just need to figure out which religion the shrine is for.

Also, I found a bug. The code I've entered only makes the ReligionYields value display in my capital. No idea why. Here are my changes to void CvGameTextMgr::setYieldHelp() (original code in blue):
Code:
	CvPlayer& owner = GET_PLAYER(city.getOwnerINLINE());

	int iBuildingYield = 0;
	for (int i = 0; i < GC.getNumBuildingInfos(); i++)
	{
		CvBuildingInfo& infoBuilding = GC.getBuildingInfo((BuildingTypes)i);
		if (city.getNumBuilding((BuildingTypes)i) > 0 && !GET_TEAM(city.getTeam()).isObsoleteBuilding((BuildingTypes)i))
		{
			for (int iLoop = 0; iLoop < city.getNumBuilding((BuildingTypes)i); iLoop++)
			{
				iBuildingYield += infoBuilding.getYieldChange(eYieldType);
			}
		}
	}
	int iBaseProduction = city.getBaseYieldRate(eYieldType) - city.getGlobalReligionYield(eYieldType) - iBuildingYield;

[COLOR="Navy"]	szBuffer.append(gDLL->getText("TXT_KEY_MISC_HELP_BASE_YIELD", info.getTextKeyWide(), iBaseProduction, info.getChar()));
	szBuffer.append(NEWLINE);[/COLOR]

	int iBaseModifier = 100;
	if (0 != iBuildingYield)
	{
		szBuffer.append(gDLL->getText("TXT_KEY_MISC_HELP_BUILDINGS_YIELDS", iBuildingYield, info.getChar()));
		szBuffer.append(NEWLINE);
	}

	int iShrineYield = city.getGlobalReligionYield(eYieldType);
	if (0 != iShrineYield)
	{
		szBuffer.append(gDLL->getText("TXT_KEY_MISC_HELP_SHRINE_YIELD", iShrineYield, info.getChar()));
		szBuffer.append(NEWLINE);
	}

EDIT: Wait, nevermind that code snippet. I just realized that the game is ignoring completely the second city with a Shrine, it's not calculating a Shrine Yield for it at all. I need to go back the the debug DLL.
 
OK. But how then does the game display the Yields for Palace's & Supermarkets? These give +8 :commerce: & +1 :food: respectively.

I don't know. Look in CvMainInterface where it draws the building list in the City Screen. Sadly, I've forgotten a lot of what I've learned about Civ4 as I've expanded the areas in which I work. There's a lot of code to keep straight.

And for my Shrines, do you think it would be possible for me to use the CountReligionLevels function? Then the value is already worked out, I just need to figure out which religion the shrine is for.

Sure, you can reproduce the calculations if you want. I can tell you with my experience that the more you can limit code duplication, the fewer problems you will have. If you have a function that adds up the yields for all shrines, and you need a function that calculates the yields for a single shrine, you're far better off having the first function use the second function.

Code:
int CvCity::calculateGlobalReligionYield(YieldTypes eYield) const
{
    int iTotal = 0;

    for each relition
        íTotal += calculateGlobalReligionYield(religion, eYield);
}

int CvCity::calculateGlobalReligionYield(ReligionTypes eReligion, YieldTypes eYield) const
{
    ... calculate value for single religion ...
}

Notice how the first function that calculates the total uses the second function that calculates the value for a single religion. This minimizes code duplication which minimizes the chance of bugs and increases ease of coding.

Ideally each function should do exactly one thing and require 5-15 lines of code. This is obviously a rough generalization, but the shorter your functions are the lower the chance they will contain bugs and the more understandable they become.
 
OK, I can't get the Food Rate widget to display for the life of me...

Here's what I've done:
  • exactly copied the entirety of void CvGameTextMgr::setFoodHelp() into CGTM.cpp, with appropriate entry into CGTM.h
    • I deleted the bit at the end referring to "BUG - Building Additional Food"
  • made an entry in CvDefines.h for DOUBLE_SEPARATOR
  • copied void CvDLLWidgetData::parseFoodModHelp() into CvDLLWidgetData.cpp, with appropriate entry into the header.
  • copied case WIDGET_FOOD_MOD_HELP: into CvDLLWidgetData.cpp, with appropriate entry into CvEnums.h.
  • changed the widget call for both "PopulationInputText" screen.setLabel's in CvMainInterface.py so that it calls WidgetTypes.WIDGET_FOOD_MOD_HELP instead of WidgetTypes.WIDGET_GENERAL.
Unfortunately, this results in the City Screen coming up with no widgets of any kind. All the information is gone, just the backgrounds load up. And if I change the "PopulationInputText" back to WIDGET_GENERAL, then everything else works, but the Food widget doesn't load up...

:wallbash:
 
You need to expose the new WidgetTypes value to Python in CyInfoInterface.cpp with a def() call.
 
I just select my shortcut in the Start Menu (I organize my SM a lot) and it launches a second VS2008 process. You can probably open a second project in the same process though, I would assume by the File menu.
 
Thanks guys. I hadn't considered it possible to start a second instance. Much easier this way. :D

And it's starting to look good! Purdy 'n' all!!

But (of course),

I'm getting a failed Assert in CvXMLLoadUtilitySet.cpp, @ line 1295. It happens when the Civ4 loading screenlet is at InitXML(uncached). I don't know why, I've never been in this file, and my XML files are all in order. It doesn't even seem to cause a problem, as the game loads and functions normally if I ignore it...

This is the error:
Code:
Assert Failed

File:  CvXMLLoadUtilitySet.cpp
Line:  1295
Expression:  bSuccess
Message:
The failing code:
Code:
template <class T>
void CvXMLLoadUtility::SetGlobalClassInfo(std::vector<T*>& aInfos, const char* szTagName, bool bTwoPass)
{
    char szLog[256];
    sprintf(szLog, "SetGlobalClassInfo (%s)", szTagName);
    PROFILE(szLog);
    logMsg(szLog);

    // if we successfully locate the tag name in the xml file
    if (gDLL->getXMLIFace()->LocateNode(m_pFXml, szTagName))
    {
        // loop through each tag
        do
        {
            SkipToNextVal();    // skip to the next non-comment node

                T* pClassInfo = new T;

                FAssert(NULL != pClassInfo);
                if (NULL == pClassInfo)
                {
                    break;
                }

                bool bSuccess = pClassInfo->read(this);[COLOR=Red]  //  the screenshots below are from this line.[/COLOR]
                FAssert(bSuccess);  [COLOR=Red] // Line 1295[/COLOR]
                if (!bSuccess)
                {
                    delete pClassInfo;
                    break;
                }
And here're some screenies from VS2008:
FAssertScreenShot01.jpg
FAssertScreenShot02.jpg


I have no idea what any of this means, or how to interpret it... :confused:
 
Thank-you xienwolf, I feel much better now.

So, I think I found the spot in CvMainInterface.py that controls the Commerce & Yield displays for the building list on the left of the city screen. The part that appears to handles Yields is shown here:
Code:
for j in range( YieldTypes.NUM_YIELD_TYPES):
	iYield = gc.getBuildingInfo(i).getYieldChange(j) + pHeadSelectedCity.getNumBuilding(i) * pHeadSelectedCity.getBuildingYieldChange(gc.getBuildingInfo(i).getBuildingClassType(), j)

	if (iYield != 0):
		if ( bFirst == False ):
			szRightBuffer = szRightBuffer + ", "
		else:
			bFirst = False
												
		if ( iYield > 0 ):
			szTempBuffer = u"%s%d%c" %( "+", iYield, gc.getYieldInfo(j).getChar() )
			szRightBuffer = szRightBuffer + szTempBuffer
		else:
			szTempBuffer = u"%s%d%c" %( "", iYield, gc.getYieldInfo(j).getChar() )
			szRightBuffer = szRightBuffer + szTempBuffer

and I changed it like this:
Code:
for j in range( YieldTypes.NUM_YIELD_TYPES):
	[COLOR="Red"]iShrineYield = gc.getBuildingInfo(i).getGlobalReligionYields(j)[/COLOR]

	if ([COLOR="Red"]iShrineYield [/COLOR]!= 0):
		if ( bFirst == False ):
			szRightBuffer = szRightBuffer + ", "
		else:
			bFirst = False
											
		if ( [COLOR="Red"]iShrineYield [/COLOR]> 0 ):
			szTempBuffer = u"%s%d%c" %( "+", [COLOR="Red"]iShrineYield[/COLOR], gc.getYieldInfo(j).getChar() )
			szRightBuffer = szRightBuffer + szTempBuffer
		else:
			szTempBuffer = u"%s%d%c" %( "", [COLOR="Red"]iShrineYield[/COLOR], gc.getYieldInfo(j).getChar() )
			szRightBuffer = szRightBuffer + szTempBuffer

which didn't work. It causes all the widgets to not load, and a few other errors. I think I need to leave in the 2nd & 3rd arguments (is that the right term?) from the original second line, but the 3rd one references getBuildingYieldChange, which isn't called ever in my Shrine Yields calculations. In fact, the only time CvBuildingInfo::getGlobalReligionYields() is called is in the 'Pedia... So, I'm stuck again...
 
I'm confused. Are you trying to remove the original yield display and replace it with the shrine yields? The CyCity::getBuildingYieldChange() is responsible for the +2:hammers: from AP religion I believe. The CvBuildingInfo::getYieldChange() call handles the +1:food: from Supermarket. I have no idea why only the former is multiplied by the number of buildings in the city and not both values.

Regardless, I would think you'd want to add the shrine yields to the existing values. To do that you need to call a function on CyCity since the value depends on the city. You can't use the +1:commerce: you've specified on the building type. Your CvCity functions use those values to calculate the full effect for the city with the shrine.

Have you exposed any of your new CvCity functions to Python via CyCity? If not, you need to do that first before you can make these values show up in the City Screen.

All you have is a function that calculates the total shrine yield for all shrines in a city. You need a function that tells you the shrine income for any single building--shrine or not (0 for non-shrines obviously). Or you can reproduce the full calculation in Python if you wish.

As per my post on Nov 16, 2009 12:44 AM, I would recode the function that calculates the total shrine yield into two functions: one that loops over all religions summing up the results of calling the other function that calculates the value for a single religion. This way you can use the second function from the City Screen if the building has shrine yields.
 
Sorry, I wasn't clear. The first bit of code is what already exists in CvMainInterface.py, and (I believe) figures out the Yields to display in the Building List (the actual list, not the hovers). It uses getBuildingInfo().getYieldChange() to do this, as well as getBuildingYieldChange.

I can't uses this code bit myself, as my ShrineYields are not built into the BuildingYield processes. I posted it only as an illustration of what I was using as a template for my attempt to get the ShrineYields to show in the Building List. The Building hovers for Shrines work fine.

I have not exposed any of my functions in CyCity. I'll take care of that now, thank-you.

I think my function currently is capable of figuring out the value of the :commerce: output for an individual Shrine. Here it is:
Code:
int CvCity::getGlobalReligionYield(YieldTypes eIndex) const												 
{
	FAssertMsg(eIndex >= 0, "eIndex expected to be >= 0");
	FAssertMsg(eIndex < NUM_YIELD_TYPES, "eIndex expected to be < NUM_YIELD_TYPES");
	return m_aiGlobalReligionYield[eIndex];
}


int CvCity::getGlobalReligionYieldByReligion(YieldTypes eIndex, ReligionTypes eReligion) const
{
	int iYield;
	
	FAssertMsg(eIndex >= 0, "eIndex expected to be >= 0");
	FAssertMsg(eIndex < NUM_YIELD_TYPES, "eIndex expected to be < NUM_YIELD_TYPES");
	FAssertMsg(eReligion >= 0, "eReligion expected to be >= 0");
	FAssertMsg(eReligion < GC.getNumReligionInfos(), "GC.getNumReligionInfos expected to be >= 0");

	iYield = 0;

	if (isHasReligion(eReligion))
	{
	for (int iI = 0; iI < GC.getNumBuildingInfos(); iI++)
		{
			if (GC.getBuildingInfo((BuildingTypes)iI).getGlobalReligionYields() == eReligion)
			{
				if (getNumActiveBuilding((BuildingTypes)iI) > 0)
				{
					iYield += getNumActiveBuilding((BuildingTypes)iI) * GC.getReligionInfo(eReligion).getGlobalReligionYield(eIndex) * GC.getGameINLINE().countReligionLevels(eReligion);
				}
			}
		}
	}
	return iYield;
}

void CvCity::updateGlobalReligionYield(YieldTypes eIndex)
{
	int iOldYield = getGlobalReligionYield(eIndex);
	int iNewYield = 0;

	for (int iI = 0; iI < GC.getNumReligionInfos(); iI++)
	{
		iNewYield += getGlobalReligionYieldByReligion(eIndex, (ReligionTypes)iI);
	}

	if (iOldYield != iNewYield)
	{
		m_aiGlobalReligionYield[eIndex] = iNewYield;
		FAssert(getGlobalReligionYield(eIndex) >= 0);

		changeBaseYieldRate(eIndex, (iNewYield - iOldYield));
	}
}

void CvCity::updateGlobalReligionYield()
{
	int iI;

	for (iI = 0; iI < GC.getNumReligionInfos(); iI++)
	{
	if (hasShrine((ReligionTypes)iI))	
	{
		for (iI = 0; iI < NUM_YIELD_TYPES; iI++)
		{
			updateGlobalReligionYield((YieldTypes)iI);
		}
	}
	}
}
... OK, I see what you're referring to now. The for (iI = 0; iI < GC.getNumReligionInfos(); iI++) in void CvCity::updateGlobalReligionYield() loops through every religion, and therefor all Shrines, and can't be told to check only a single religion. But, int CvCity::getGlobalReligionYieldByReligion(YieldTypes eIndex, ReligionTypes eReligion) const is built to check only the value for a single religion (ergo a single Shrine). I could build a new function, say CvCity::getGlobalReligionByYield(), that goes through a for (iI = 0; iI < NUM_YIELD_TYPES; iI++), that then calls CvCity::getGlobalReligionYieldByReligion(), and expose the ByYield function to Python. That would work, right?

EDIT: OK, I just realized that's a dumb idea, it doesn't work at all. Could you give me a pseudo-code suggestion?
EDIT 2: I think I can use a pair of nested for j in range() statements in Python to get out of this. The first to check for each Yield, the second to check for each religion. Then I can use the two j variables (I know, let's say j & k) to feed a specific Yield & Religion type to CvCity::getGlobalReligionYieldByReligion(, thus giving me a value only that Shrine.
 
I could see the first block you posted was the original. I thought you were replacing it by the second block you posted. What you need is a function on CvCity that returns the shrine yield for a given building type and yield.

Code:
int CvCity::getGlobalReligionYieldByBuilding(BuildingTypes eBuilding, YieldTypes eYield) const
{
    yield = 0
    get building info
    for each religion
        if building has global religion yields for the religion
            add count of building * religion's global yield * global religion level
    return yield
}

This function will only be used by the Python code.
 
I think this might do what you intended:
Code:
int CvCity::getGlobalReligionYieldByBuilding(YieldTypes eIndex, ReligionTypes eReligion) const
{
	int iBuildingYield = 0;

	for (int iI = 0; iI < GC.getNumBuildingInfos(); iI++)
	{
		for (iI = 0; iI < GC.getNumReligionInfos(); iI++)
		{
			if (GC.getBuildingInfo((BuildingTypes)iI).getGlobalReligionYields() != 0)
			{
				iBuildingYield += getNumActiveBuilding((BuildingTypes)iI) * GC.getReligionInfo(eReligion).getGlobalReligionYield(eIndex) * GC.getGameINLINE().countReligionLevels(eReligion);
			}
		}
	}
	return iBuildingYield;
}
 
Nope. I'll put it into English before psuedocode and try to explain. You want a function that will answer this question:

What religion yield amount does building type X provide?​

Fact: If X is not a shrine, it won't provide any religion yields.

You check this by inspecting isGlobalReligionYields(ReligionTypes) for every religion. For any religion for which it returns true, you add the yield for the religion times the number of building X's times the number of cities with the religion.

You will not need to loop over buildings. The function receives the one building we're interested in. You will need to loop over religions.

Take another stab at writing the code for this.
 
OK, since both the Yield type & the building are already fed in by Python, I think this will do it then:

Code:
int CvCity::getGlobalReligionYieldByBuilding(BuildingTypes eBuilding, YieldTypes eIndex) const
{
	int iBuildingYield = 0;

	for (iI = 0; iI < GC.getNumReligionInfos(); iI++)
	{  [COLOR="Red"]// Loop through each religion[/COLOR]

		if (GC.getBuildingInfo((eBuilding)iI).getGlobalReligionYields() != 0)
		{  [COLOR="Red"]// If the ReligionYield value for this building under religion iI is [I]not[/I] zero then:[/COLOR]

			iBuildingYield += getNumActiveBuilding((eBuilding)iI) * GC.getReligionInfo(ReligionTypes).getGlobalReligionYield(eIndex) * GC.getGameINLINE().countReligionLevels(ReligionTypes);
			  [COLOR="Red"]// Give me the product of (the number of active buildings of this type in this city (should always be 1)) times (the ReligionYield value for the Yield being checked for religion iI for the building being checked) times (the number of cities in the world with religion iI)[/COLOR]
		}
	}
	return iBuildingYield;
	  [COLOR="Red"]// return that product as the value of this function.[/COLOR]
}

Do I have that right?

And, using the above, my Python should look like this:
Code:
for j in range( YieldTypes.NUM_YIELD_TYPES):
	iShrineYield = pHeadSelectedCity.getGlobalReligionYieldByBuilding(i, j)
	  [COLOR="Red"]// Run getGlobalReligionYieldByBuilding, feeding it [B][I]i[/I][/B] for the building being checked, and [B][I]j[/I][/B] for the yield being checked.[/COLOR]

	if (iShrineYield != 0):
		if ( bFirst == False ):
			szRightBuffer = szRightBuffer + ", "
		else:
			bFirst = False
											
		if ( iShrineYield > 0 ):
			szTempBuffer = u"%s%d%c" %( "+", iShrineYield, gc.getYieldInfo(j).getChar() )
			szRightBuffer = szRightBuffer + szTempBuffer
		else:
			szTempBuffer = u"%s%d%c" %( "", iShrineYield, gc.getYieldInfo(j).getChar() )
			szRightBuffer = szRightBuffer + szTempBuffer
I think that's better...

EDIT: No, this doesn't work either. I've just realized that the IF statement on the C++ isn't checking the right thing...
EDIT 2: ok, C++ changed thusly:
Code:
if (GC.getBuildingInfo(eBuilding).getGlobalReligionYields() != 0)
{
	iBuildingYield += getNumActiveBuilding(eBuilding) * GC.getReligionInfo(ReligionTypes(iI)).getGlobalReligionYield(eIndex) * GC.getGameINLINE().countReligionLevels(ReligionTypes(iI));
}
Compiling now...

EDIT 3: Nope. Still doesn't work. It compiled just fine, but the city screen still doesn't load its widgets. I'm wondering if my Python is correct? Am I allowed to feed the DLL two variables that way?
 
Back
Top Bottom