Troubles with Arrays and Arrays of Arrays...

I'm still not seeing it. Anyway gotten everything else done. I just need to get this interface issue working correctly and then I can submit the code to be updated to RevDCM.
 
What about this?
Code:
		#phungus canTrain
		[b]bFirst = true[/b]
		for j in range(gc.getNumCivicInfos()):
			if (gc.getUnitInfo(self.iUnit).getPrereqOrCivics(j)):
				[b]if !bFirst:
					screen.attachLabel(panelName, "", localText.getText("TXT_KEY_OR", ()))[/b]
				eCivic = CivicTypes(j)
				screen.attachImageButton(panelName, "", gc.getCivicInfo(eCivic).getButton(), GenericButtonSizes.BUTTON_SIZE_CUSTOM, WidgetTypes.WIDGET_PEDIA_JUMP_TO_CIVIC, eCivic, -1, False)
				[b]bFirst = false[/b]
		#phungus -end
 
Troubles with Arrays and Arrays of Arrays...

Yo dawg I herd you like Arrays so I put an Array in your Array so you can have a table of data in your table of data.
 
Thanks Opera, that works :goodjob:

But now I just discoverd a major bug.... I thought I had it done. Sorry to continue to hijack the thread...

If I have 2 units that are blocked for trainability, and one is allowed and one is not, and I enter World Builder, World Builder breaks. It throws a python exception and makes the user stuck in World Builder, unable to do anything (all buttons in WB become unresponsive, including the leave WB button). I've also play tested about a 1000 turns on AI autoplay, and no other issues were found.... Just discovered this a couple minutes ago. The python exception is coming from here:

In CvWorldBuilderScreen.py (one of the few python interface files not rebuilt by the BUG team)
Code:
	def refreshPlayerTabCtrl(self):
		
		initWBToolPlayerControl()
		
		self.m_normalPlayerTabCtrl = getWBToolNormalPlayerTabCtrl()

		self.m_normalPlayerTabCtrl.setNumColumns((gc.getNumUnitInfos()/10)+2);
		self.m_normalPlayerTabCtrl.addTabSection(localText.getText("TXT_KEY_WB_UNITS",()));
		self.m_iUnitTabID = 0
		self.m_iNormalPlayerCurrentIndexes.append(0)

		self.m_normalPlayerTabCtrl.setNumColumns((gc.getNumBuildingInfos()/10)+1);
		self.m_normalPlayerTabCtrl.addTabSection(localText.getText("TXT_KEY_WB_BUILDINGS",()));
		self.m_iBuildingTabID = 1
		self.m_iNormalPlayerCurrentIndexes.append(0)

		self.m_normalPlayerTabCtrl.setNumColumns((gc.getNumTechInfos()/10)+1);
		self.m_normalPlayerTabCtrl.addTabSection(localText.getText("TXT_KEY_WB_TECHNOLOGIES",()));
		self.m_iTechnologyTabID = 2
		self.m_iNormalPlayerCurrentIndexes.append(0)
		
		addWBPlayerControlTabs()
		return
Can anyone think off the top of thier head what could be breaking in there?
 
The standard solution is to remember whether this is the first one printing. If not, print "or" first. Pseudocode:
Code:
bFirst = true
for <your loop>
   if <your test>
      if !bFirst: print " or "
      bFirst = false
      print <your data>
EDIT: sorry, note to self, read to the end of thread first.

It would be easier to make suggestions about your python error in WB if you posted the error itself and the related line numbers.
 
It would be easier to make suggestions about your python error in WB if you posted the error itself and the related line numbers.
There isn't much. The python exception just says unidefentified C++ exception caused by the last line: addWBPlayerControlTabs(), in the posted function above.
 
What do you think of this AI code? Will it work for CvCityAI?

Code:
				//These functions value health and happiness the same as regular health 
				//and happiness, but only if the team has the prereq tech researched
				for (iI = 0; iI < GC.getNumTechInfos(); iI++)//Afforess
				{
					if (kBuilding.getTechHappinessChanges(iI) != 0) 
					{
						if (GET_TEAM(getTeam()).isHasTech((TechTypes)iI))
						{
						iValue += (std::min(kBuilding.getTechHappinessChanges(iI), iAngryPopulation) * 10) + (std::max(0, kBuilding.getTechHappinessChanges(iI) - iAngryPopulation) * iHappyModifier);
						}
					}
					if (kBuilding.getTechHealthChanges(iI) != 0) 
					{
						if (GET_TEAM(getTeam()).isHasTech((TechTypes)iI))
						{
							iValue += (std::min(kBuilding.getTechHappinessChanges(iI), iBadHealth) * 12) + (std::max(0, kBuilding.getTechHappinessChanges(iI) - iBadHealth) * iHealthModifier);
						}
					}
				}

Edit:

I just finished up the code for the AI_BestTech, see if you like it or not.
Code:
	for (iI = 0; iI < GC.getNumTechInfos(); iI++)
	{
		if ((eIgnoreTech == NO_TECH) || (iI != eIgnoreTech))
		{
			if ((eIgnoreAdvisor == NO_ADVISOR) || (GC.getTechInfo((TechTypes)iI).getAdvisorType() != eIgnoreAdvisor))
			{
				if (canEverResearch((TechTypes)iI))
				{
					if (!(kTeam.isHasTech((TechTypes)iI)))
					{
						if (GC.getTechInfo((TechTypes)iI).getEra() <= (getCurrentEra() + 1))
						{
						...
							for (iJ = 0; iJ < GC.getNumBuildingClassInfos(); iJ++)
								{
									eLoopBuilding = ((BuildingTypes)(GC.getCivilizationInfo(getCivilizationType()).getCivilizationBuildings(iJ)));

									if (eLoopBuilding != NO_BUILDING)
									{
										CvBuildingInfo& kLoopBuilding = GC.getBuildingInfo(eLoopBuilding);
										
										if (kLoopBuilding.getTechHealthChanges((TechTypes)iI) != 0) //Afforess
										{
											iValue += (kLoopBuilding.getTechHealthChanges((TechTypes)iI) * 50);
										}
										
										if (kLoopBuilding.getTechHappinessChanges((TechTypes)iI) != 0)
										{
											iValue += (kLoopBuilding.getTechHappinessChanges((TechTypes)iI) * 100);
										}
									...
 
The python exception just says unidefentified C++ exception caused by the last line: addWBPlayerControlTabs(), in the posted function above.

That is odd. This function is inside the game engine, not inside the sdk. If you remove the OrPrereq from your xml only, does WB work then? I guess the function is building the pickbox of all the unit icons. I can't imagine that this step would actually use any of your new code, so it is a mystery why it would crash.
 
For the techs I would consider having it multiply the iValue modification by getNumBuildingClass query from the the players in the team. But as written works fairly well, since even if they don't have buildings yet, they soon will due to the other code making the building now worthwhile for them.
 
The fun never ends! So, I finished this and was looking back at the original project I had designed this for. Guess what. It called for some Tech Global Health and Happiness changes!

I figure I might as well throw the Area changes in as well, for the heck of it. Might as well be thorough. I've added the new functions to CvInfos, and I looked at how they were implemented in Civ4. It looks like this is going to be a bit easier, no real major changes, as they seem to utilize the same functions for building happiness and health as the regular health. So, I probably can use the TechExtraHealthChanges and HappinessChanges again.

But I have a specific question. I'm adding these change functions to CvTeam, but their counterparts where in CvPlayer. There is no "pArea" parameter in ProcessTech() and I'm not sure what to do. Should I keep these in CvTeam, and add a new variable pArea or move these four over to CvPlayer?


Edit:

After a second glance, those functions are too difficult to do, and I would hardly ever use them. I think I'll be happy with the things I already have.
 
That is odd. This function is inside the game engine, not inside the sdk. If you remove the OrPrereq from your xml only, does WB work then? I guess the function is building the pickbox of all the unit icons. I can't imagine that this step would actually use any of your new code, so it is a mystery why it would crash.
Thanks davidallen, I have tracked the cause to the state religion check, and not the array. Will investigate further and start a new thread on this bug if I can't figure it out, as my current problem doesn't seem to be related to arrays..
 
I'm a bit stumped by a recent code change I would like to implement. I have a boolean array created by afforess in BuildingInfos: PrereqBuildingClasses. I pass in a value (corresponding to a buildingclass), it tells me true or false. Works great. I'd like to add in a second paramater: TechOverride, where for a specific building, I can also ask what tech is stored there as well (if there is a tech at all). The overally design goal is to be able to override a building requirement to train when the player knows a specified tech; though that will all be handled in CvPlot::canTrain; for now I need to figure out how to simply load this into CvInfos.

This is the function currently in UnitInfos:

bool getPrereqBuildingClass(int i)

No I want to be able to add a tech line with the building class line the PreqBuildingClasses array. I also have a fairly large concern about the code I implement, this is for RevDCM, which is used as a modding base for multiple mods. If I add in a second argument to this function other mods are going to suddenly have their code break because the'll get a "function takes two arguments, one given" error. While I can break save game, I am not "allowed" to break calls to arguments like this (though I suppose I could simply overload the function).

I'm not sure if this is the right way, but I followed YieldTechChanges as a template in ImprovementInfos. Following this setup I left well enough alone with the original getPrereqBuildingClass function and created a new one, along with a multidimensional array, where the m_ppbPrereqBuildingAndTechs was the number of buildinclasses in the part and as big as TechInfos in m_ppbPrereqBuildingClass[j]; or in other words:

I_size = GC.getNumBuildingInfos();
J_size = GC.getNumTechInfos();

bool m_ppbPrereqBuildingAndTechs[I_size][J_size];

I've followed the TechYieldChanges tag in ImprovementsInfos as a template. Now everything was going smoothly, and I figured I'd have a nice multidimesional array to call weather a unit has a PrereqBuildingClass with a corresponding override tech or not, but then I got to where CvInfos actually reads things from the XML. The problem is the tag I am following has an extra level of depth, as it looks inside the YieldTypes array, in the XML it looks like this:

Code:
<TechYieldChanges>
	<TechYieldChange>
		<PrereqTech>TECH_AQUACULTURE</PrereqTech>
		<TechYields>
			<iYield>0</iYield>
			<iYield>0</iYield>
			<iYield>1</iYield>
		</TechYields>
	</TechYieldChange>
</TechYieldChanges>

What I've got, or rather what makes the most sense to me is this:
Code:
<PrereqBuildingClasses>
	<PrereqBuildingClass>
		<BuildingClassType>NONE</BuildingClassType>
		<bPrereq>1</bPrereq>
		<TechOverride>NONE</TechOverride>
	</PrereqBuildingClass>
</PrereqBuildingClasses>

It's different, and the method CvInfos reads TechYieldChanges doesn't correspond correctly. Here is the code it uses:
Code:
	// initialize the boolean list to the correct size and all the booleans to false
	FAssertMsg((GC.getNumTechInfos() > 0) && (NUM_YIELD_TYPES) > 0,"either the number of tech infos is zero or less or the number of yield types is zero or less");
	pXML->Init2DIntList(&m_ppiTechYieldChanges, GC.getNumTechInfos(), NUM_YIELD_TYPES);
	if (gDLL->getXMLIFace()->SetToChildByTagName(pXML->GetXML(),"TechYieldChanges"))
	{
		if (pXML->SkipToNextVal())
		{
			iNumSibs = gDLL->getXMLIFace()->GetNumChildren(pXML->GetXML());
			if (gDLL->getXMLIFace()->SetToChild(pXML->GetXML()))
			{
				if (0 < iNumSibs)
				{
					for (j=0;j<iNumSibs;j++)
					{
						pXML->GetChildXmlValByName(szTextVal, "PrereqTech");
						iIndex = pXML->FindInInfoClass(szTextVal);

						if (iIndex > -1)
						{
							// delete the array since it will be reallocated
							SAFE_DELETE_ARRAY(m_ppiTechYieldChanges[iIndex]);
							// if we can set the current xml node to it's next sibling
							if (gDLL->getXMLIFace()->SetToChildByTagName(pXML->GetXML(),"TechYields"))
							{
								// call the function that sets the yield change variable
								pXML->SetYields(&m_ppiTechYieldChanges[iIndex]);
								gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
							}
							else
							{
								pXML->InitList(&m_ppiTechYieldChanges[iIndex], NUM_YIELD_TYPES);
							}
						}

						if (!gDLL->getXMLIFace()->NextSibling(pXML->GetXML()))
						{
							break;
						}
					}
				}

				gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
			}
		}

		gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
	}
I am not familiar with how the XML is read by, and I have always simply copied this stuff before. Right now though I cannot see how to get the XML properly read, and also I have a feeling I am not setting this up optimally. Any advice would be apreciated.
 
I always recommend starting with a concrete example that you want to be able to support. I suspect your multi-dimensional array isn't what you really wanted. Let me try an example.

Swordsman

Requires Forge unless you have discovered Machinery
Requires Barracks unless you have discovered Conscription

So in the early game a city must have a Forge and Barracks to train a Swordsman. Once you acquire Machinery, the Forge is no longer required, but the Barracks still is. if you acquire Conscription first, the Barracks is no longer required, but the Forge is. Only after you have acquired both techs are both buildings no longer required.

If this is what you want you need to pair each tech to a building class. Have two parallel arrays both of length GC.getNumBuildingClassInfos(). Read them together pairwise from the XML. First read the building class and use it for the index in both arrays. The first array is a boolean which you set just as it's being set now. Next read the tech and write its TechTypes value into the second array at the slot numbered for the building class. The second array is a TechTypes array (or int, whatever CvInfos uses).

Next your API should just need to add a second function to what's there already:

  • bool getPrereqBuildingClass(int /* BuildingClassTypes */ i)
  • /* TechTypes */ int getPrereqBuildingClassObsoletingTech(int /* BuildingClassTypes */ i)
If that's not what you're after, give some examples.
 
Currently for the private member I have:

bool m_bpPrereqBuldingClass;

Where i is the number of buildingclasses, examples of this are all through the SDK (actually I think it's *bool m_bpPrereqBuildingClass, with no array brackets, not sure how this works, but that seems to be how arrays are set up in Civ4, and functionally it's equivalent to what I have above). And you're saying I should keep this. Then I should add a second array:

int m_ipPrereqBuildingClassTech;

where the array is the same as above, but instead of a boolean value, the int value corresponding to the tech is returned by the function that access this array? This makes much more sense to me. I'm still confused though about how to get the XML read correctly; the XML parser isn't easy to understand at all. But at least now I'm no longer looking at an array of an array, but rather two arrays, one that holds a boolean and the other an int. I suppose the problem I'm having is that the XML reads these from the same parent, and I don't understand the whole parent/child/node thing with the XML.
 
I just can't figure out how to set up the XML reading part.

Here is the way the boolean array reads it:

Code:
	pXML->SetVariableListTagPair(&m_pbPrereqBuildingClass, "PrereqBuildingClasses", sizeof(GC.getBuildingClassInfo((BuildingClassTypes)0)), GC.getNumBuildingClassInfos());

Here is the XML (could always change the schema as well, but this makes the most sense to me):
Code:
			<PrereqBuildingClasses>
				<PrereqBuildingClass>
					<BuildingClassType>NONE</BuildingClassType>
					<bPrereq>1</bPrereq>
					<TechOverride>NONE</TechOverride>
				</PrereqBuildingClass>
			</PrereqBuildingClasses>

So how do I get the Dll to read the BuildingClassType tag to set the proper position in the array, and then read the TechOverride tag to set it's integer value?
 
That is an interesting question. Reading the code for SetVariableListTagPair, it is not passed the name of any child element. The code has a section:
Code:
	iIndexVal = FindInInfoClass(szTextVal);

	if (iIndexVal != -1)
	{
		GetNextXmlVal(&piList[iIndexVal]);
	}
I believe that assumes there are exactly two children, the first one is an index, and the second one is the desired list value. There is no way to control that. So, there is no way to process an element with three children. Is there any xml anywhere in vanilla or a mod, which has three children?

I agree that this xml is clean, and relatively easy to understand, but it doesn't seem that civ could parse it. Since you have agreed that your design fits well into two arrays, perhaps you could create two different xml entries instead of combining them:
Code:
<PrereqBuildingClasses>
	<PrereqBuildingClass>
		<BuildingClassType>BUILDINGCLASS_FORGE</BuildingClassType>
		<bPrereq>1</bPrereq>
	</PrereqBuildingClass>
<PrereqBuildingClasses>
<PrereqBuildingTechOverrides>
	<PrereqBuildingTechOverride>
		<BuildingClassType>BUILDINGCLASS_FORGE</BuildingClassType>
		<TechOverride>TECH_MACHINERY</TechOverride>
	</PrereqBuildingTechOverride>
</PrereqBuildingTechOverrides>
That seems to fit what the parser is trying to do; the second child is the value inserted into the list at the index of the first child.
 
Yeah, I searched all through the XML, and couldn't find any examples. Would it be possible to code the parser to read 3 children, or is this in the Exe / is too difficult to do? I can of curse set up two tags, but it just doesn't look as clean, and it also runs into logic coding problems, if someone sets a building in one tag, but forgets to in another, etc. Ultimately I'll probably settle on that aproach though, but figure I should ask if this is at least feasible.
 
It is software, I am sure it is *possible* to add a third child and pass along the field names. I tend to go "around" obstacles like this by using a different xml coding style, rather than going "through" them. YMMV.

Another possibility to consider is to define your own object in cvinfos.h, and then refer to that object. For example, CvSpecialistInfo is a small object, you could clone that. I have done this recently on another project which isn't released yet. For something which has only two data fields, this may be too much complexity.
 
This is absolutely possible in the SDK. It contains all of the XML parsing code. Afforess and xienwolf could easily point you in the right direction here. You could copy the code for SetVariableListTagPair() and have it read a third value into the second array. If you make it specific to your single case here it should be pretty easy.
 
If I'm going to bother coding it, I'll make it a useful function, either allow the programmer to specify the tag to read by name, or by position. Unfortunately I'm not familiure with parsing XML data, so I hope I can guess correctly, as compiling is a pain on my computer (takes 2 hours to do a complete new build). Fortunately setting up the function should only require 1 full build as I should be able to specify the function and it's arguments in a pretty straighforward way for the header, but implementing the function is another thing altogether.

Hopefully afforess or Xienwolf can point me toward the right direction, otherwise I'll just go looking blindly in the SDK once I get home.

It is software, I am sure it is *possible* to add a third child and pass along the field names. I tend to go "around" obstacles like this by using a different xml coding style, rather than going "through" them.
While that's often sensible, in this case it's not. If I don't deal with it now, there are going to be constant logic issues that creep up from having two arrays virtually doing the same thing, especially if the modder enters conflicting info in them. This is one of those places where it's better to just do the work up front and make sure it's working correctly, in my oppinion.
 
Back
Top Bottom