new reasons for city unhappiness/unhealth?

davidlallen

Deity
Joined
Apr 28, 2008
Messages
4,743
Location
California
In the city screen, you can hover the mouse over your happiness and healthiness values to see a breakdown of the reasons. Like, -2 happiness due to war weariness, +4 due to units stationed in the city, etc. All of the building happiness is combined into one value and displayed as, "Some buildings are making us happy".

Is there any way to add a category in python? Is this handled inside an advisor screen somehow? I would like to add some other categories which should be treated separately. I can associate them with real or fake buildings, but I'd like to explain in the screen what these really mean.

There are two cases.

1. A biologically oriented civ can cause plagues in cities. If they put a plague on you, some of your cities may have -2 health. I am doing this today as a fake building with -2 health; but I'd prefer (a) not to have any visible building, and (b) to have this -2 show up separately in the healthiness summary

2. Some buildings may provide high technology using methods the "man on the street" may not like. For example, at times, adding a nuclear power plant may cause protesters. The specific example, from the Dune universe, is adding automated factories that use thinking machines. There was a huge revolt called the Butlerian Jihad which wiped out all computers before. But, some players may risk re-introducing them to get a big hammer bonus. I would like to display the unhappiness due to this type of building separately; today it is just a -1 happiness on the early building and -2 on the later building.

If I can display them separately, I will be encouraged to add more of a system behind them, where the health/happy penalties can change over time.

Any leads?
 
CvMainInterface.py handles the City Screen, I know that. There's a function:

Code:
def updateCityScreen( self ):

Wherever in the function something relating to happiness or health is dealt with (like updating religions, yields, bonuses, buildings, etc.) there seems to be a part of the code dealing with happiness and health. The building code, for instance, is below, with happiness and health parts highlighted.

Code:
				i = 0
				iNumBuildings = 0
				for i in range( gc.getNumBuildingInfos() ):
					if (pHeadSelectedCity.getNumBuilding(i) > 0):

						for k in range(pHeadSelectedCity.getNumBuilding(i)):
							
							szLeftBuffer = gc.getBuildingInfo(i).getDescription()
							szRightBuffer = u""
							bFirst = True
							
[COLOR="Red"]							if (pHeadSelectedCity.getNumActiveBuilding(i) > 0):
								iHealth = pHeadSelectedCity.getBuildingHealth(i)

								if (iHealth != 0):
									if ( bFirst == False ):
										szRightBuffer = szRightBuffer + ", "
									else:
										bFirst = False
										
									if ( iHealth > 0 ):
										szTempBuffer = u"+%d%c" %( iHealth, CyGame().getSymbolID(FontSymbols.HEALTHY_CHAR) )
										szRightBuffer = szRightBuffer + szTempBuffer
									else:
										szTempBuffer = u"+%d%c" %( -(iHealth), CyGame().getSymbolID(FontSymbols.UNHEALTHY_CHAR) )
										szRightBuffer = szRightBuffer + szTempBuffer

								iHappiness = pHeadSelectedCity.getBuildingHappiness(i)

								if (iHappiness != 0):
									if ( bFirst == False ):
										szRightBuffer = szRightBuffer + ", "
									else:
										bFirst = False
										
									if ( iHappiness > 0 ):
										szTempBuffer = u"+%d%c" %(iHappiness, CyGame().getSymbolID(FontSymbols.HAPPY_CHAR) )
										szRightBuffer = szRightBuffer + szTempBuffer
									else:
										szTempBuffer = u"+%d%c" %( -(iHappiness), CyGame().getSymbolID(FontSymbols.UNHAPPY_CHAR) )
										szRightBuffer = szRightBuffer + szTempBuffer[/COLOR]

								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

Here's some more stuff in that function relating to this:

Code:
				if ((pHeadSelectedCity.happyLevel() >= 0) or (pHeadSelectedCity.unhappyLevel(0) > 0)):
					if (pHeadSelectedCity.isDisorder()):
						szBuffer = u"%d%c" %(pHeadSelectedCity.angryPopulation(0), CyGame().getSymbolID(FontSymbols.ANGRY_POP_CHAR))
					elif (pHeadSelectedCity.angryPopulation(0) > 0):
						szBuffer = localText.getText("INTERFACE_CITY_UNHAPPY", (pHeadSelectedCity.happyLevel(), pHeadSelectedCity.unhappyLevel(0), pHeadSelectedCity.angryPopulation(0)))
					elif (pHeadSelectedCity.unhappyLevel(0) > 0):
						szBuffer = localText.getText("INTERFACE_CITY_HAPPY", (pHeadSelectedCity.happyLevel(), pHeadSelectedCity.unhappyLevel(0)))
					else:
						szBuffer = localText.getText("INTERFACE_CITY_HAPPY_NO_UNHAPPY", (pHeadSelectedCity.happyLevel(), ))

					screen.setLabel( "HappinessText", "Background", szBuffer, CvUtil.FONT_LEFT_JUSTIFY, xResolution - iCityCenterRow1X + 6, iCityCenterRow2Y, -0.3, FontTypes.GAME_FONT, WidgetTypes.WIDGET_HELP_HAPPINESS, -1, -1 )
					screen.show( "HappinessText" )

Sorry I can't be more helpful then just throw bits of code at you. But I suspect you'd need to edit the code I highlighted in red. How you'd edit it I have no idea.
 
Thanks for the pointer to the city screen code. That may be a good starting place. Sadly this function is 800 lines in vanilla, over 1100 lines in BUG, and not very well documented. I believe that the parts you highlighted are where the statistics per building are displayed. This is the arrow on the left in my screenie. (The background of the city screen for Dune Wars is totally changed by koma13 and deliverator.) What I really want is to find the hover help on the right, where the breakdown of total unhappiness is displayed. I am pretty sure that must be somewhere else, or at least, I cannot identify the part of updateCityScreen which shows this.

Any suggestions on where this hover help is computed?
 
I think it may be the second bit of code I quoted.

In that, it calls two functions happyLevel() and unhappyLevel(). Looking in CvCity.cpp I see this:

Code:
int CvCity::unhappyLevel(int iExtra) const
{
	int iAngerPercent;
	int iUnhappiness;
	int iI;

	iUnhappiness = 0;

	if (!isNoUnhappiness())
	{
		iAngerPercent = 0;

		iAngerPercent += getOvercrowdingPercentAnger(iExtra);
		iAngerPercent += getNoMilitaryPercentAnger();
		iAngerPercent += getCulturePercentAnger();
		iAngerPercent += getReligionPercentAnger();
		iAngerPercent += getHurryPercentAnger(iExtra);
		iAngerPercent += getConscriptPercentAnger(iExtra);
		iAngerPercent += getDefyResolutionPercentAnger(iExtra);
		iAngerPercent += getWarWearinessPercentAnger();

		for (iI = 0; iI < GC.getNumCivicInfos(); iI++)
		{
			iAngerPercent += GET_PLAYER(getOwnerINLINE()).getCivicPercentAnger((CivicTypes)iI);
		}

		iUnhappiness = ((iAngerPercent * (getPopulation() + iExtra)) / GC.getPERCENT_ANGER_DIVISOR());

		iUnhappiness -= std::min(0, getLargestCityHappiness());
		iUnhappiness -= std::min(0, getMilitaryHappiness());
		iUnhappiness -= std::min(0, getCurrentStateReligionHappiness());
		iUnhappiness -= std::min(0, getBuildingBadHappiness());
		iUnhappiness -= std::min(0, getExtraBuildingBadHappiness());
		iUnhappiness -= std::min(0, getFeatureBadHappiness());
		iUnhappiness -= std::min(0, getBonusBadHappiness());
		iUnhappiness -= std::min(0, getReligionBadHappiness());
		iUnhappiness -= std::min(0, getCommerceHappiness());
		iUnhappiness -= std::min(0, area()->getBuildingHappiness(getOwnerINLINE()));
		iUnhappiness -= std::min(0, GET_PLAYER(getOwnerINLINE()).getBuildingHappiness());
		iUnhappiness -= std::min(0, (getExtraHappiness() + GET_PLAYER(getOwnerINLINE()).getExtraHappiness()));
		iUnhappiness -= std::min(0, GC.getHandicapInfo(getHandicapType()).getHappyBonus());
		iUnhappiness += std::max(0, getVassalUnhappiness());
		iUnhappiness += std::max(0, getEspionageHappinessCounter());
	}

	return std::max(0, iUnhappiness);
}


int CvCity::happyLevel() const
{
	int iHappiness;

	iHappiness = 0;

	iHappiness += std::max(0, getLargestCityHappiness());
	iHappiness += std::max(0, getMilitaryHappiness());
	iHappiness += std::max(0, getCurrentStateReligionHappiness());
	iHappiness += std::max(0, getBuildingGoodHappiness());
	iHappiness += std::max(0, getExtraBuildingGoodHappiness());
	iHappiness += std::max(0, getFeatureGoodHappiness());
	iHappiness += std::max(0, getBonusGoodHappiness());
	iHappiness += std::max(0, getReligionGoodHappiness());
	iHappiness += std::max(0, getCommerceHappiness());
	iHappiness += std::max(0, area()->getBuildingHappiness(getOwnerINLINE()));
	iHappiness += std::max(0, GET_PLAYER(getOwnerINLINE()).getBuildingHappiness());
	iHappiness += std::max(0, (getExtraHappiness() + GET_PLAYER(getOwnerINLINE()).getExtraHappiness()));
	iHappiness += std::max(0, GC.getHandicapInfo(getHandicapType()).getHappyBonus());
	iHappiness += std::max(0, getVassalHappiness());

	if (getHappinessTimer() > 0)
	{
		iHappiness += GC.getDefineINT("TEMP_HAPPY");
	}


	return std::max(0, iHappiness);
}

So I think that second piece of python interface code calls these functions, which also call more functions like this:

Code:
int CvCity::getVassalHappiness() const
{
	int iHappy = 0;

	for (int i = 0; i < MAX_TEAMS; i++)
	{
		if (getTeam() != i)
		{
			if (GET_TEAM((TeamTypes)i).isVassal(getTeam()))
			{
				iHappy += GC.getDefineINT("VASSAL_HAPPINESS");
			}
		}
	}

	return iHappy;
}

int CvCity::getVassalUnhappiness() const
{
	int iUnhappy = 0;

	for (int i = 0; i < MAX_TEAMS; i++)
	{
		if (getTeam() != i)
		{
			if (GET_TEAM(getTeam()).isVassal((TeamTypes)i))
			{
				iUnhappy += GC.getDefineINT("VASSAL_HAPPINESS");
			}
		}
	}

	return iUnhappy;
}

Thus, it computes the happiness/unhappiness via C++ and then calls the functions from the python interface file.

So if you wanted to expand happiness, you'd have to probably add some new tags to your building info file, then add some new functions in the City SDK files, and then call them from the happyLevel() and unhappyLevel() functions.
 
Any text you see by placing your mouse over something is caused by a WIDGET, so you want to look in Python for where that item was placed, and see what WidgetType it is. Then you go to the DLL and check in CvDLLWidgetData.cpp and search for that widget name. Typically you have 2 functions, one for setHelp, and one for doAction. Most of the times setHelp calls to CvGameTextMgr.cpp, sometimes it builds the string locally though. IIRC, this one is built locally (in DLLWidget). It should be fairly clear how each happiness bit is added up, but it will require that you add a new function to the DLL for controlling your happiness/health additions, so that you have a way of knowing where it came from (or it will require Hardcoding the DLL if you go that ugly route)

EDIT: TC01's second set of code includes your Happiness WidgetName, because it is used to find your net Happiness value. WidgetTypes.WIDGET_HELP_HAPPINESS is the widget type, so search for WIDGET_HELP_HAPPINESS in the DLL and you'll find where you need to be.
 
Thanks for the info. I traced through to CvGameTextMgr::setHappyHelp, which is clearly building up the string I want. It is not editable through python. I will put this on the list of things to do, in case I ever succeed at building my own dll.
 
At best in Python you can append to a widget's hover help text, but you cannot modify it. :cry:

Given that you are a C++ guy and know about compilers, I'm really surprised at your reluctance to jump into SDK coding. Yes, the documentation is non-existent and comments are extremely rare, but I've managed to add quite a few things such as these in BULL by tracing through the code (by hand, I haven't attached the debugger yet).

You specifically said "succeed at building" the DLL. Does this mean you've tried following the instructions for building a DLL with VS or CodeBlocks? It was a PITA to be sure, but once you get it going it's pretty easy to keep it going. :)
 
Top Bottom