Widget Help

johnny_cash

Chieftain
Joined
Aug 4, 2009
Messages
32
Location
los angeles, ca
Hi,

I can't seem to find many resources on the forum (tutorials, etc.) on how widgets work in general.

I'd like to create a widget (setOutput) that would be appended to appropriate buildings in the building list table in CvMainInterface.py.

This widget would basically just be a clone of the + - buttons of the commerce controls, that would allow one to set the output of a building.

In cvinfos i already have getMaxBonusOutput and then And and Or Bonus Inputs that are entered per output.

So a factory has a max output of 4 steel. each steel takes 2 coal and 2 iron. but using the widget setoutput i can set it to produce 0 to 4 steel. so instead of having to always use 8 coal and 8 iron, i can set it to produce less and thus use less.

I've looked through CvDLLWidgetData and am pretty confused.

i guess my main question is, what are the steps i take to put a widget like this into play? first i add the widget in CvDLLWidgetData, then i add it to CyCityInterface? then to CvMainInterface.py? how do i get the information set in python back into CvCity for the actual calculations that i want to take place?

Thanks in advance for any help!
 
Start in CvEnums to make the Widget exist, add it to the list of other widgets.

Then go into CvDLLWidgetData and set rules for when there is a mouseover event and when there is a click event. In the first case you typically call a ::setHelp type of function which displays text, and in the latter you will call a function which runs your setOutput. Since you are using +/- buttons, look at how the Commerce Sliders operate to understand what values you will have to indicate which button was pressed.

CyCityInterface won't need anything, Widgets are Widgets. CyEnumsInterface will be your python accesspoint as I recall. Reasonably certain there isn't a CyDLLWidgetInterface.

In CvMainInterface, again look for the Widget which is used for the sliders to see how to place them, then look for where the list of constructed buildings is generated to place the +/- in the proper location.
 
Start in CvEnums to make the Widget exist, add it to the list of other widgets.

Then go into CvDLLWidgetData and set rules for when there is a mouseover event and when there is a click event. In the first case you typically call a ::setHelp type of function which displays text, and in the latter you will call a function which runs your setOutput. Since you are using +/- buttons, look at how the Commerce Sliders operate to understand what values you will have to indicate which button was pressed.

CyCityInterface won't need anything, Widgets are Widgets. CyEnumsInterface will be your python accesspoint as I recall. Reasonably certain there isn't a CyDLLWidgetInterface.

In CvMainInterface, again look for the Widget which is used for the sliders to see how to place them, then look for where the list of constructed buildings is generated to place the +/- in the proper location.

Thanks!

So i think i've found the function I want to copy from but I don't understand what it does.

Code:
void CvDLLWidgetData::doChangePercent(CvWidgetDataStruct &widgetDataStruct)
{
	CvMessageControl::getInstance().sendPercentChange(((CommerceTypes)widgetDataStruct.m_iData1), widgetDataStruct.m_iData2);
}

What does CvMessageControl do? Would I need to make use of this when making my widget?

this is the function in CvMessageControl

Code:
void CvMessageControl::sendPercentChange(CommerceTypes eCommerce, int iChange)
{
	if (NO_PLAYER != GC.getGameINLINE().getActivePlayer())
	{
		gDLL->sendMessageData(new CvNetPercentChange(GC.getGameINLINE().getActivePlayer(), eCommerce, iChange));
	}
}
 
That is for communication between computers in Multiplayer Simultaneous turn games. It informs the other players what decision you have made so that their computer shows the same change. It is something you will have to deal with, but cannot directly copy.

I still don't see how we can run messages from within the DLL, even after 3.19 exposed so much of the messaging capability. Problem is they only exposed one side of it (sending) and not the other (receiving). So it looks like you will have to resort to using python and sendModNetMessage + onModNetMessage functions. That means that you'll set up your widget to launch a sendModNetMessage (might be able to do that from within just the DLL, all the pieces seem to be there), and then set up your python to catch the onModNetMessage and control the changes in your actual code from there. It is a mild nuisance to handle, but till Firaxis makes a new patch which allows us to create our own message send/receive setups, it is the best you have available.
 
So I'm able to compile a DLL but for the life of me can't figure out how to set this thing up in python. I've looked at the API and it seems to be lacking information on the screen interface functions.

I am trying to change the building list table to do two things:

A) be scrollable. i can't remember if it already is scrollable (i haven't played in a very long time because of my modding project). i can't figure out how to tell from cvmaininterface.py if it is.

B) if the buildings in the list have a bonus output, i want to set buttons (the + - buttons from citizens\specialists)

I have all my widgets set up in the sdk, and everything compiled fine, i'm just really having trouble modding CvMainInterface.py. I already feel like (B) is pretty much not possible using the list table in place already.

here is the code i have that is failing me. it is basically Citizen buttons cloned and edited to fit my needs... also, my coordinates are pretty busted because i have no idea how to visualize what i'm actually doing.
Code:
# *********************************************************************************
		# FACTORY BUTTONS
		# *********************************************************************************

		
		# Increase Output...
		i = 0
		for i in range( gc.getNumBuildingInfos() ):
			if (pHeadSelectedCity.getNumBuilding(i) > 0):
				for j in range( gc.getNumBonusInfos() ):
					if (gc.getBuildingInfo(i).getMaxBonusOutput(j) > 0):						
						szName = "IncreaseOutput" + str(i)
						screen.setButtonGFC( szName, u"", "", 88, (yResolution - 541 - (26 * iCount)), 20, 20, WidgetTypes.WIDGET_CHANGE_OUTPUT, i, 1, ButtonStyles.BUTTON_STYLE_CITY_PLUS )
						screen.hide( szName )

						iCount = iCount + 1

		iCount = 0

		# Decrease Output
		i = 0
		for i in range( getNumBuildings() ):
			for j in range( gc.getNumBonusInfos() ):
				if (gc.getBuildingInfo(i).getMaxBonusOutput(j) > 0):
					szName = "DecreaseOutput" + str(i)
					screen.setButtonGFC( szName, u"", "", 110, (yResolution - 541 - (26 * iCount)), 20, 20, WidgetTypes.WIDGET_CHANGE_OUTPUT, i, -1, ButtonStyles.BUTTON_STYLE_CITY_MINUS )
					screen.hide( szName )

				iCount = iCount + 1

		iCount = 0

		# Factory Names
		i = 0
		for i in range( gc.getNumBuildingInfos() ):
			for j in range( gc.getNumBonusInfos() ):
				if (gc.getBuildingInfo(i).getMaxBonusOutput(j) > 0):	
				
					szName = gc.getBuildingInfo(i).getDescription() + str(i)
					screen.setText( szName, "", u"", CvUtil.FONT_LEFT_JUSTIFY, 66, (yResolution - 541 - (26 * iCount)), -0.3, FontTypes.SMALL_FONT, WidgetTypes.WIDGET_PEDIA_JUMP_TO_BUILDING, i, -1 )
					screen.hide( szName )
				iCount = iCount + 1

		iCount = 0
so widget_change_output essentially runs a task, change_output, which runs function ChangeBuildingOutput(BuildingTypes eIndex, iChange).


Any insight would be amazing.

Thanks!
 
also, for anyone following this thread, this is what i have going in the SDK. i can't really get to testing it out because i can't get the screen in python to work, but it doesn't compile with any errors.

in CvDllWidgetData under executeAction
Code:
case WIDGET_CHANGE_OUTPUT:
		doChangeOutput(widgetDataStruct);
		break;

then
Code:
void CvDLLWidgetData::doChangeOutput(CvWidgetDataStruct &widgetDataStruct)
{
	GC.getGameINLINE().selectedCitiesGameNetMessage(GAMEMESSAGE_DO_TASK, TASK_CHANGE_OUTPUT, widgetDataStruct.m_iData1, widgetDataStruct.m_iData2);
}

then in cvcity under dotask

Code:
case TASK_CHANGE_OUTPUT:
		changeBuildingOutputRate(((BuildingTypes)iData1), iData2);
		break;

then these functions, which will definitely need to be cleaned up (i've been toying with the sdk for a month so i'm sure some of my code is silly), but i haven't been able to test in game.... also haven't fleshed out the maxyieldoutput aspect yet because i just wanted to get this thing running for a test...
Code:
int CvCity::getBuildingOutputRate(BuildingTypes eIndex)
{
	return m_aiBuildingOutputRate != NULL ? m_aiBuildingOutputRate[eIndex] : 0;
}

void CvCity::changeBuildingOutputRate(BuildingTypes eIndex, int iChange)
{
	setBuildingOutputRate(eIndex, (getBuildingOutputRate(eIndex) + iChange));
}

void CvCity::setBuildingOutputRate(BuildingTypes eIndex, int iNewValue)
{
	int iI, iX, iZ, iY, iV, iRate;
	bool bLevelOkay = false;
	int OutputBonus;
	
	if (iNewValue > 0)
	{
		for (iY = 0; iY < GC.getNumBonusInfos(); iY++)
		{
			if (GC.getBuildingInfo(eIndex).getMaxBonusOutput(iY) > 0)
			{
				iRate = range(iNewValue, 0, GC.getBuildingInfo(eIndex).getMaxBonusOutput(iY));
				int OutputBonus = iY;
				break;
			}
		}
		for (iV = 0; iV < NUM_YIELD_TYPES; iV++)
		{					
			if (GC.getBuildingInfo(eIndex).getMaxYieldOutput(iV) > 0)
			{
				iRate = range(iNewValue, 0, GC.getBuildingInfo(eIndex).getMaxYieldOutput(iV));
			}
		}
	
		for (iI = 0; iI < GC.getNumBonusInfos(); iI++)
		{
			
			if (GC.getBuildingInfo(eIndex).getOrBonusInput(iI) > 0)
			{
				if (getOverallBonusStock((BonusTypes) iI) >= (iRate*(GC.getBuildingInfo(eIndex).getOrBonusInput(iI))))
				{
					bLevelOkay = true;
					break;
				}
			}
		}
		if (bLevelOkay == true)
		{
			for (iZ = 0; iZ < GC.getNumBonusInfos(); iZ++)
			{
				if (GC.getBuildingInfo(eIndex).getAndBonusInput(iZ) > 0)
				{
					if (getOverallBonusStock((BonusTypes) iZ) >= (iRate*(GC.getBuildingInfo(eIndex).getAndBonusInput(iZ))))
					{
						bLevelOkay = true;
					}
					else
					{
						bLevelOkay = false;
						break;
					}
				}
			}
		}
		if (bLevelOkay == true)
		{
			for (iX = 0; iX < NUM_YIELD_TYPES; iX++)
			{
				if (GC.getBuildingInfo(eIndex).getYieldInput(iX) > 0)
				{
					if (getYieldRate((YieldTypes) iX) >= (iRate*(GC.getBuildingInfo(eIndex).getYieldInput(iX))))
					{
						bLevelOkay = true;
					}
					else
					{
						bLevelOkay = false;
						break;
					}
				}
			}
		}
		if (bLevelOkay == true)
		{
			m_aiBuildingOutputRate[eIndex] = range(iNewValue, 0, GC.getBuildingInfo(eIndex).getMaxBonusOutput((BonusTypes) OutputBonus));
			if (isCitySelected())
			{
				gDLL->getInterfaceIFace()->setDirty(FactoryButtons_DIRTY_BIT, true);
			}

		}
		if (bLevelOkay == false)
		{
			setBuildingOutputRate(eIndex, (iNewValue - 1));
		}
	}
	else
	{
		m_aiBuildingOutputRate[eIndex] = 0;
		if (isCitySelected())
			{
				gDLL->getInterfaceIFace()->setDirty(FactoryButtons_DIRTY_BIT, true);
			}
	}				
}
 
I'll ignore most of the last posted code for the SDK since you said it needs cleaned up and on a cursory glance I agree, but it isn't the important part right now.

For your python: Buildings already scroll, so you are set there. You don't want to copy how Specialists are handled because all specialists ALWAYS show, but only the buildings which you current have show in the buildings list. Thus you need your +/- buttons to only show if the building exists in the city, rather than all the time. Thus the commerce sliders will be a better example for you, since you can have just Culture, or Culture + Espionage, or Culture + Gold, or Culture + Gold + Espionage... The fact that they do not always show, and you are not always assured of the exact same order is the important thing which you need to have happen for your final result.

These snippets are from MY version of CvMainInterface, which is highly modified. But these particular bits should be fairly close to the original code and ought to highlight what is important. They are not neccessarily all required, nor all inclusive of what IS required. Just what I see in a quick glance through and think will help you to look at.

Code:
		for iI in range( CommerceTypes.NUM_COMMERCE_TYPES ):
			szString = "IncreasePercent" + str(iI)
			screen.hide( szString )
			szString = "DecreasePercent" + str(iI)
			screen.hide( szString )

This hides the +/- buttons so you can choose which to reveal later.

Code:
				for iI in range( CommerceTypes.NUM_COMMERCE_TYPES ):
					# Intentional offset...
					eCommerce = (iI + 1) % CommerceTypes.NUM_COMMERCE_TYPES
										
					iShift = 60
					if (CyInterface().isCityScreenUp()):
						iShift = 0
					
					if (gc.getActivePlayer().isCommerceFlexible(eCommerce) or (CyInterface().isCityScreenUp() and (eCommerce == CommerceTypes.COMMERCE_GOLD))):
						szString1 = "IncreasePercent" + str(eCommerce)
						screen.setButtonGFC( szString1, u"", "", 70 + iShift, 50 + (19 * iCount), 20, 20, WidgetTypes.WIDGET_CHANGE_PERCENT, eCommerce, gc.getDefineINT("COMMERCE_PERCENT_CHANGE_INCREMENTS"), ButtonStyles.BUTTON_STYLE_CITY_PLUS )
						screen.show( szString1 )
						szString2 = "DecreasePercent" + str(eCommerce)
						screen.setButtonGFC( szString2, u"", "", 90 + iShift, 50 + (19 * iCount), 20, 20, WidgetTypes.WIDGET_CHANGE_PERCENT, eCommerce, -gc.getDefineINT("COMMERCE_PERCENT_CHANGE_INCREMENTS"), ButtonStyles.BUTTON_STYLE_CITY_MINUS )
						screen.show( szString2 )

						iCount = iCount + 1

						if (gc.getActivePlayer().isCommerceFlexible(eCommerce)):
							screen.enable( szString1, True )
							screen.enable( szString2, True )
						else:
							screen.enable( szString1, False )
							screen.enable( szString2, False )

This places each of the appropriate +/- buttons on the appropriate line based on which should be active for the player, makes them visible, and makes them clickable.

Code:
			# Percent of commerce
			if (gc.getPlayer(ePlayer).isAlive()):
				iCount = 0
				for iI in range( CommerceTypes.NUM_COMMERCE_TYPES ):
					eCommerce = (iI + 1) % CommerceTypes.NUM_COMMERCE_TYPES
					if (gc.getPlayer(ePlayer).isCommerceFlexible(eCommerce) or (CyInterface().isCityScreenUp() and (eCommerce == CommerceTypes.COMMERCE_GOLD))):
						iShift = 60
						if (CyInterface().isCityScreenUp()):
							iShift = 0
						szOutText = u"<font=2>%c:%d%%</font>" %(gc.getCommerceInfo(eCommerce).getChar(), gc.getPlayer(ePlayer).getCommercePercent(eCommerce))
						szString = "PercentText" + str(iI)
						screen.setLabel( szString, "Background", szOutText, CvUtil.FONT_LEFT_JUSTIFY, 14 + iShift, 50 + (iCount * 19), -0.1, FontTypes.SMALL_FONT, WidgetTypes.WIDGET_GENERAL, -1, -1 )
						screen.show( szString )

						if not CyInterface().isCityScreenUp():
							szOutText = u"<font=2>" + localText.getText("TXT_KEY_MISC_POS_GOLD_PER_TURN", (gc.getPlayer(ePlayer).getCommerceRate(CommerceTypes(eCommerce)), )) + u"</font>"
							szString = "RateText" + str(iI)
							screen.setLabel( szString, "Background", szOutText, CvUtil.FONT_LEFT_JUSTIFY, 112 + iShift, 50 + (iCount * 19), -0.1, FontTypes.SMALL_FONT, WidgetTypes.WIDGET_GENERAL, -1, -1 )
							screen.show( szString )

						iCount = iCount + 1;

This sets our labels

Code:
							for j in range(CommerceTypes.NUM_COMMERCE_TYPES):
								iCommerce = pHeadSelectedCity.getBuildingCommerceByBuilding(j, i) / pHeadSelectedCity.getNumBuilding(i)
	
								if (iCommerce != 0):
									if ( bFirst == False ):
										szRightBuffer = szRightBuffer + ", "
									else:
										bFirst = False
										
									if ( iCommerce > 0 ):
										szTempBuffer = u"%s%d%c" %( "+", iCommerce, gc.getCommerceInfo(j).getChar() )
										szRightBuffer = szRightBuffer + szTempBuffer
									else:
										szTempBuffer = u"%s%d%c" %( "", iCommerce, gc.getCommerceInfo(j).getChar() )
										szRightBuffer = szRightBuffer + szTempBuffer
	
							szBuffer = szLeftBuffer + "  " + szRightBuffer
							
							screen.appendTableRow( "BuildingListTable" )
							screen.setTableText( "BuildingListTable", 0, iNumBuildings, "<font=1>" + szLeftBuffer + "</font>", "", WidgetTypes.WIDGET_HELP_BUILDING, i, -1, CvUtil.FONT_LEFT_JUSTIFY )
							screen.setTableText( "BuildingListTable", 1, iNumBuildings, "<font=1>" + szRightBuffer + "</font>", "", WidgetTypes.WIDGET_HELP_BUILDING, i, -1, CvUtil.FONT_RIGHT_JUSTIFY )

This actually has nothing to do with the Percent Sliders, but it is roughly where you want to insert your code. This snippet is the code which shows any commerce modifiers which a building applies. It is indented under:

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

						for k in range(pHeadSelectedCity.getNumBuilding(i)):

Which you want to ensure you are also under.


Looks like that is most of the pieces. Might be enough for you to make some more headway. To make things easier on yourself, start out by trying to place a simple "Yes" string beside any building which should wind up with the +/- buttons eventually to know that you are placing things in the right location, THEN worry about actually getting the buttons in the spot and making them function properly when pressed.
 
Looks like that is most of the pieces. Might be enough for you to make some more headway. To make things easier on yourself, start out by trying to place a simple "Yes" string beside any building which should wind up with the +/- buttons eventually to know that you are placing things in the right location, THEN worry about actually getting the buttons in the spot and making them function properly when pressed.

Thanks for all your help xienwolf.

So i'm in the process of testing out just using a "yes" string if the building has maxBonusOutput > 0. I think i'll be able to get that working.

But i've noticed that the commerce slider buttons, and from what i've seen most buttons, require explicit x and y coordinates.

So how would i place these buttons within a container, in the case of the building table it would be "szRightBuffer"?
 
The X and Y coordinates in scrollable containers are typically referenced in terms of the container dimensions rather than screen dimensions IIRC. Unfortunately all of the sample code is for text only, and it is placed in a table by row and column, rather than utilizing an X/Y framework, so you won't get any handy examples.

Where the sliders use setButtonGFC, you probably will wind up using setButtonGFCAt, which attaches the button to another object. I am not certain what precisely you can attach to though, it'll be a matter of Guess & Check I am afraid. I would try attaching it to the "yes" string object and see if that flies, but most likely it wants a panel for what you attach to. In that case, you have to kind of guess at the proper conversion of Y values to maintain alignment with the buildingInfo entry (most likely 16*iNumBuildings). For starters, just choose a value like 20/20 so you know roughly where it is and ensure that it scrolls along with everything else (it ought to)


EDIT: This looks useful (from the API, haven't used it myself yet)

VOID addItemToTableGFC(STRING szAttachTo, STRING szText, WidgetType eWidgetType, INT iData1, INT iData2)
void ( string szAttachTo, wstring szText, WidgetTypes eWidgetType, int iData1, int iData2 )
 
I think your main challenge is that everything placed into tables AFAIK is based on row and column numbers. You place a single "thing" into each cell. You can see how to place a button inside a cell in the Domestic Advisor for the Zoom to City button, however it handles the click by detecting the click in column zero in Python rather than depending on the WidgetType. I think you can use the WidgetType though as that's how the Civilopedia list does it.
 
I think your main challenge is that everything placed into tables AFAIK is based on row and column numbers. You place a single "thing" into each cell. You can see how to place a button inside a cell in the Domestic Advisor for the Zoom to City button, however it handles the click by detecting the click in column zero in Python rather than depending on the WidgetType. I think you can use the WidgetType though as that's how the Civilopedia list does it.

I tried using the attachControlToTableCell and it was unsuccessful for the + - buttons. There is probably something I am missing because every time I tried to apply that to my buttons it was not matching the C++ signatures.


In the end I ended up with explicit buttons pegged to follow the table. My concern is that when the number of buildings surpasses the displayable amount, the buttons will become broken. Here is my code alsong with the remnants of my failed attempts to place the buttons inside the table.

Code:
for j in range(gc.getNumBonusInfos()):
								if (gc.getBuildingInfo(i).getMaxBonusOutput(j) > 0):
									szPlus = "IncreaseOutput" + str(i)
									szMinus = "DecreaseOutput" + str(i)
									screen.setButtonGFC("IncreaseOutput", "", "", 188, 317 +(iNumBuildings * 25), 20, 20, WidgetTypes.WIDGET_CHANGE_OUTPUT, i, 1, ButtonStyles.BUTTON_STYLE_CITY_PLUS)
										#screen.addItemToTableGFC(szButtonSite1,szPlus, WidgetTypes.WIDGET_CHANGE_OUTPUT, i, 1)
										#screen.attachControlToTableCell( szPlus, "BuildingListTable", iNumBuildings, 2)
									screen.setButtonGFC("DecreaseOutput", "", "", 204, 317 +(iNumBuildings * 25), 20, 20, WidgetTypes.WIDGET_CHANGE_OUTPUT, i, -1, ButtonStyles.BUTTON_STYLE_CITY_MINUS)
										#screen.addItemToTableGFC(szButtonSite2,szMinus, WidgetTypes.WIDGET_CHANGE_OUTPUT, i, -1)
										#screen.attachControlToTableCell( szMinus, "BuildingListTable", iNumBuildings, 3)						
									szOutput = u"%d" %( gc.getBuildingInfo(i).getMaxBonusOutput(j) )
									#screen.setTableText( "BuildingListTable", 4, iNumBuildings, "<font=1>" + szOutput + "</font>", "", WidgetTypes.WIDGET_GENERAL, -1, -1, CvUtil.FONT_LEFT_JUSTIFY )


Thanks for all the help!
 
Curious... one other downside of this would be that if a building has multiple maximum bonus outputs (say for Corn and for Rice) the buttons will be overlapping.

What did you define "szButtonSite1" as? That ought to be the name of the table you attach to as I am translating things in the API (it is alarmingly useless in this section I must admit). Though how I translate the rest of it is that this is for placing text which can also act as a Widget, so instead of the +/- buttons you might have to resort to just typing + and -. Though with the issue of the multiple possible resources that MIGHT work nicely for you as you could make it say +:commerce:-, replacing :commerce: with the character/symbol appropriate for the resource you are adjusting. You won't have as large of an area to click on, but at least it will work. (so you would add 3 items, "+" as your Widget with data2 as 1, then the character of the bonus j as Widget_General, then "-" as your Widger with data2 as -1. Or the other way around as it seems standard to have + on the right)
 
Curious... one other downside of this would be that if a building has multiple maximum bonus outputs (say for Corn and for Rice) the buttons will be overlapping.

What did you define "szButtonSite1" as? That ought to be the name of the table you attach to as I am translating things in the API (it is alarmingly useless in this section I must admit). Though how I translate the rest of it is that this is for placing text which can also act as a Widget, so instead of the +/- buttons you might have to resort to just typing + and -. Though with the issue of the multiple possible resources that MIGHT work nicely for you as you could make it say +-, replacing with the character/symbol appropriate for the resource you are adjusting. You won't have as large of an area to click on, but at least it will work. (so you would add 3 items, "+" as your Widget with data2 as 1, then the character of the bonus j as Widget_General, then "-" as your Widger with data2 as -1. Or the other way around as it seems standard to have + on the right)

Yah, that's what I figured for szText, however I did totally misread szAttachTo, as referring to a text or something. I have to admit I was getting a little frustrated with python, however once I get my change_output widget to actually work properly, I will have to give it another go anyway.

I actually initially wanted to have multiple bonus outputs, but decided to limit myself to one because of the interface. What you're suggesting solves that problem very elegantly, thank you!
 
hi,

so i'm having a bit of problem with my code for generating the bonuses\yields based on output. i'm getting a CTD when doIndustry is called. here it is:

in CvCity doTurn
Code:
for (iI = 0; iI < GC.getNumBuildingInfos(); iI++)
	{
		if (getNumBuilding( (BuildingTypes) iI ) > 0)
		{
		
			if (getBuildingOutputRate((BuildingTypes) iI) > 0)
			{
				doIndustry((BuildingTypes) iI);
			}
		}
	}

in CvCity

Code:
void CvCity::doIndustry(BuildingTypes eIndex)
{
	int iI;
	int iHighestOrSource = 0;
	int iContendingSource;
	int iRate;
	bool bLevelOkay = false;

	iRate = getBuildingOutputRate(eIndex);
	
	for (iRate; iRate >= 0; iI--)
	{
		for (iI = 0; iI < NUM_YIELD_TYPES; iI++)
		{
			if (getYieldRate((YieldTypes) iI) >= (iRate*(GC.getBuildingInfo(eIndex).getYieldInput(iI))))
			{
				bool bLevelOkay = true;
			}
			else
			{
				bool bLevelOkay = false;
				break;
			}
		}
		if (bLevelOkay == true)
		{
			for (iI = 0; iI <GC.getNumBonusInfos(); iI++)
			{
				if (getOverallBonusStock((BonusTypes) iI) >= (iRate*(GC.getBuildingInfo(eIndex).getOrBonusInput(iI))))
				{
					bool bLevelOkay = true;
					break;
				}
				else
				{
					bool bLevelOkay = false;
				}
			}
		}
		if (bLevelOkay == true)
		{
			for (iI = 0; iI < GC.getNumBonusInfos(); iI++)
			{
				if (getOverallBonusStock((BonusTypes) iI) >= (iRate*(GC.getBuildingInfo(eIndex).getAndBonusInput(iI))))
				{
					bool bLevelOkay = true;
				}
				else
				{
					bool bLevelOkay = false;
					break;
				}
			}
		}
		if (bLevelOkay == true)
		{
			break;
		}
	}

	setBuildingOutputRate(eIndex, iRate);
	
	if (iRate == 0)
	{
		return;
	}
	
	for (iI = 0; iI < NUM_YIELD_TYPES; iI++)
	{
		if (GC.getBuildingInfo(eIndex).getYieldOutput(iI) > 0)
		{
			changeBaseYieldRate( (YieldTypes) iI, (iRate*GC.getBuildingInfo(eIndex).getYieldOutput(iI) ));
			//changeBuildingYieldChange( (BuildingClassTypes) GC.getBuildingInfo(eIndex).getBuildingClassType(), (YieldTypes) iI, (iRate*GC.getBuildingInfo(eIndex).getYieldOutput(iI) ));
		}
		if (GC.getBuildingInfo(eIndex).getYieldInput(iI) > 0)
		{
			changeBaseYieldRate( (YieldTypes) iI, (iRate*GC.getBuildingInfo(eIndex).getYieldOutput(iI) ));
			//changeBuildingYieldChange( (BuildingClassTypes) GC.getBuildingInfo(eIndex).getBuildingClassType(), (YieldTypes) iI, -(iRate*GC.getBuildingInfo(eIndex).getYieldOutput(iI) ));
		}
	}
	
	for (iI = 0; iI < GC.getNumBonusInfos(); iI++)
	{
		if (GC.getBuildingInfo(eIndex).getBonusOutput(iI) > 0)
		{
			changeBonusStock( (BonusTypes) iI, iRate*GC.getBuildingInfo(eIndex).getBonusOutput(iI));
		}
		if (GC.getBuildingInfo(eIndex).getAndBonusInput(iI) > 0)
		{
			changeOverallBonusStock( (BonusTypes) iI, -(iRate*GC.getBuildingInfo(eIndex).getAndBonusInput(iI)));
		}
		if (GC.getBuildingInfo(eIndex).getOrBonusInput(iI) > 0)
		{
			iContendingSource = getOverallBonusStock((BonusTypes) iI) - (iRate*GC.getBuildingInfo(eIndex).getOrBonusInput(iI));
			if ( iContendingSource >= iHighestOrSource)
			{
				iContendingSource = iHighestOrSource;
			}
		}
	}
	for (iI = 0; iI < GC.getNumBonusInfos(); iI++)
	{
		if (getOverallBonusStock((BonusTypes) iI) - (iRate*GC.getBuildingInfo(eIndex).getOrBonusInput(iI)) == iHighestOrSource)
		{
			changeOverallBonusStock( (BonusTypes) iI, -(iRate*GC.getBuildingInfo(eIndex).getOrBonusInput(iI)));
			break;
		}
	}
}

EDIT: So I think I'm getting the CTD because I'm checking against NULL values in my arrays. Will fix and try tomorrow!

so after setting your ouput in the cityscreen, and ending your turn, the first half of doIndustry is just meant to check that you have what you need to produce at the rate you set, if you don't, the rate is decreased automatically until it reaches a level which is acceptable or goes to 0.

Thanks in advance for the help!
 
I don't see the source of the CTD, but they are typically caused by a null pointer exception--accessing an array before it has been allocated. Check that you allocate and initialize all arrays before using them. The culprit here is probably where you store the building inputs/outputs/rates.

You can compile a debug DLL so that Visual Studio will show you exactly where the CTD happens, and you can step through the code and examine variable values.

There are other problems in the code, and I've rewritten it to fix them and also make it a little easier to understand.

  1. You are a new variable called bLevelOkay each time you put "bool" (its type) in front. Declare it once at the top of the function (as you have) and then set a new value for it throughout.
  2. "if (<boolean-expression> == true)" is the same as "if (<boolean-expression>)"
  3. You probably only want to test the non-zero OR bonuses against the rate.
  4. You can look up the CvBuildingInfo once at the top and use it throughout. This is a tiny speed improvement, but I find it makes the code far more readable.
  5. Towards the bottom you add to the city's base yield rate for both the building's outputs and inputs. Is this intended?
  6. In the final loop that looks for the OR bonus with the highest leftover overall stock, I've set the bonus it found and then use it after instead of looping looking for one that matches the highest value. Slightly faster and easier to understand.
Code:
void CvCity::doIndustry(BuildingTypes eIndex)
{
	CvBuildingInfo& kBuilding = GC.getBuildingInfo(eIndex);
	int iI;
	int iHighestOrSource = 0;
	BonusTypes eHighestOrBonus = NO_BONUS;
	int iRate;
	bool bLevelOkay = true;

	for (iRate = getBuildingOutputRate(eIndex); iRate >= 0; iRate--)
	{
		for (iI = 0; iI < NUM_YIELD_TYPES; iI++)
		{
			if (getYieldRate((YieldTypes) iI) < (iRate*(kBuilding.getYieldInput(iI))))
			{
				bLevelOkay = false;
				break;
			}
		}
		if (bLevelOkay)
		{
			for (iI = 0; iI <GC.getNumBonusInfos(); iI++)
			{
				if (kBuilding.getOrBonusInput(iI) > 0)
				{
					if (getOverallBonusStock((BonusTypes) iI) >= (iRate*(kBuilding.getOrBonusInput(iI))))
					{
						bLevelOkay = true;
						break;
					}
					else
					{
						bLevelOkay = false;
					}
				}
			}
		}
		if (bLevelOkay)
		{
			for (iI = 0; iI < GC.getNumBonusInfos(); iI++)
			{
				if (getOverallBonusStock((BonusTypes) iI) < (iRate*(kBuilding.getAndBonusInput(iI))))
				{
					bLevelOkay = false;
					break;
				}
			}
		}
		if (bLevelOkay)
		{
			break;
		}
	}

	setBuildingOutputRate(eIndex, iRate);
	
	if (iRate == 0)
	{
		return;
	}
	
	for (iI = 0; iI < NUM_YIELD_TYPES; iI++)
	{
		if (kBuilding.getYieldOutput(iI) > 0)
		{
			changeBaseYieldRate( (YieldTypes) iI, (iRate*kBuilding.getYieldOutput(iI) ));
			//changeBuildingYieldChange( (BuildingClassTypes) kBuilding.getBuildingClassType(), (YieldTypes) iI, (iRate*kBuilding.getYieldOutput(iI) ));
		}
		if (kBuilding.getYieldInput(iI) > 0)
		{
			changeBaseYieldRate( (YieldTypes) iI, (iRate*kBuilding.getYieldOutput(iI) ));
			//changeBuildingYieldChange( (BuildingClassTypes) kBuilding.getBuildingClassType(), (YieldTypes) iI, -(iRate*kBuilding.getYieldOutput(iI) ));
		}
	}
	
	for (iI = 0; iI < GC.getNumBonusInfos(); iI++)
	{
		if (kBuilding.getBonusOutput(iI) > 0)
		{
			changeBonusStock( (BonusTypes) iI, iRate*kBuilding.getBonusOutput(iI));
		}
		if (kBuilding.getAndBonusInput(iI) > 0)
		{
			changeOverallBonusStock( (BonusTypes) iI, -(iRate*kBuilding.getAndBonusInput(iI)));
		}
		if (kBuilding.getOrBonusInput(iI) > 0)
		{
			int iContendingSource = getOverallBonusStock((BonusTypes) iI) - (iRate*kBuilding.getOrBonusInput(iI));
			if (iContendingSource >= iHighestOrSource)
			{
				iHighestOrSource = iContendingSource;
				eHighestOrBonus = (BonusTypes)iI;
			}
		}
	}
	if (eHighestOrBonus != NO_BONUS)
	{
		changeOverallBonusStock(eHighestOrBonus, -(iRate*kBuilding.getOrBonusInput(eHighestOrBonus)));
	}
}
 
Look closely at this part:
Code:
	iRate = getBuildingOutputRate(eIndex);
	
	for (iRate; iRate >= 0; iI--)

First, the initialization part just being "iRate" doesn't do anything for you.
Then you are looping until iRate drops to 0 or less, but are decrementing iI not iRate...
 
Good catch, God-Emperor, but I've never seen an infinite loop cause a crash before. Instead, the game would lock up indefinitely. I still think there's a missing array allocation, but the loop definitely needs fixing.
 
You are a new variable called bLevelOkay each time you put "bool" (its type) in front. Declare it once at the top of the function (as you have) and then set a new value for it throughout.
"if (<boolean-expression> == true)" is the same as "if (<boolean-expression>)"
You probably only want to test the non-zero OR bonuses against the rate.
You can look up the CvBuildingInfo once at the top and use it throughout. This is a tiny speed improvement, but I find it makes the code far more readable.
Towards the bottom you add to the city's base yield rate for both the building's outputs and inputs. Is this intended?
In the final loop that looks for the OR bonus with the highest leftover overall stock, I've set the bonus it found and then use it after instead of looping looking for one that matches the highest value. Slightly faster and easier to understand.

Thank you! You saved me a loop on that OrBonus as well.

Yah, that was a typo for the Yields, the input should be -iRate.

Look closely at this part:

Code:
iRate = getBuildingOutputRate(eIndex);

for (iRate; iRate >= 0; iI--)First, the initialization part just being "iRate" doesn't do anything for you.
Then you are looping until iRate drops to 0 or less, but are decrementing iI not iRate...

Yah I'm pretty sure this was it. Thank you!

Also, having an if (eIndex == NO_BUILDING) return; at the start of the function would be advisable.

Will add that in too. Thanks!

Thanks for all your help guys, will post the results of the changes after work!
 
Back
Top Bottom