• Our friends from AlphaCentauri2.info are in need of technical assistance. If you have experience with the LAMP stack and some hours to spare, please help them out and post here.

Array sometimes becomes corrupt

Red Key

Modder
Joined
Sep 24, 2011
Messages
424
Location
USA
I'm having a CTD issue after adding some tags and a new array in the SDK. Hopefully, someone out there has had a similar problem and/or knows the solution.

The array is supposed to hold terrain types but it sometimes returns values that are out of range of terrain types. Somehow it becomes corrupt but I haven't find any consistent way to reproduce the bug. Even if I reload a save from right before the bug happened I haven't seen it happen on the same turn again. Also I haven't seen the bug happen except after loading a saved game (at least it didn't occur when I let AIAutoPlay play for a few hundred turns).

More details: Below are some of the tags I am adding to the FFH2 spell schema. They are meant for terraforming spells. <TerrainType> is a terrain that the spell can be used on. <TerrainConversion> is what to change that terrain into. Many terraforming spells change more than one terrain type so this supports listing multiple <TerrainConversion>. I have done a similar thing for features as well.
Spoiler :
Code:
	<ElementType name="TerrainType" content="textOnly"/>
	<ElementType name="TerrainConvert" content="textOnly"/>
	<ElementType name="TerrainConversion" content="eltOnly">
		<element type="TerrainType"/>
		<element type="TerrainConvert"/>
	</ElementType>
	<ElementType name="TerrainConversions" content="eltOnly">
		<element type="TerrainConversion" minOccurs="0" maxOccurs="*"/>
	</ElementType>

In CvInfos.cpp here is some of the code I have added. Here are the default/initial values in the constructor and also the stuff I added to the destructor for the arrays:
Spoiler :
Code:
m_iCommandType(NO_COMMAND),
// MNAI begin
m_piTerrainConvert(NULL),
m_piFeatureConvert(NULL),
m_pbFeatureInvalid(NULL)
// MNAI end
{
}

//------------------------------------------------------------------------------------------------------
//
//  FUNCTION:   ~CvSpellInfo()
//
//  PURPOSE :   Default destructor
//
//------------------------------------------------------------------------------------------------------
CvSpellInfo::~CvSpellInfo()
{
	// MNAI begin
	SAFE_DELETE_ARRAY(m_piTerrainConvert);
	SAFE_DELETE_ARRAY(m_piFeatureConvert);
	SAFE_DELETE_ARRAY(m_pbFeatureInvalid);
	// MNAI end
}

Here is the new function I added for accessing the terrain convert array:
Spoiler :
Code:
int CvSpellInfo::getTerrainConvert(int i) const		
{
	FAssertMsg(i < GC.getNumTerrainInfos(), "Index out of bounds");
	FAssertMsg(i > -1, "Index out of bounds");
	return m_piTerrainConvert ? m_piTerrainConvert[i] : -1;
}

Code from the read() function:
Spoiler :
Code:
	stream->Read(&m_iCommandType);
	// MNAI begin
	SAFE_DELETE_ARRAY(m_piTerrainConvert);
	m_piTerrainConvert = new int[GC.getNumTerrainInfos()];
	stream->Read(GC.getNumTerrainInfos(), m_piTerrainConvert);

	SAFE_DELETE_ARRAY(m_piFeatureConvert);
	m_piFeatureConvert = new int[GC.getNumFeatureInfos()];
	stream->Read(GC.getNumFeatureInfos(), m_piFeatureConvert);

	SAFE_DELETE_ARRAY(m_pbFeatureInvalid);
	m_pbFeatureInvalid = new bool[GC.getNumFeatureInfos()];
	stream->Read(GC.getNumFeatureInfos(), m_pbFeatureInvalid);
	// MNAI end
}

Code from the write() function:
Spoiler :
Code:
	stream->Write(m_iCommandType);
	// MNAI begin
	stream->Write(GC.getNumTerrainInfos(), m_piTerrainConvert);
	stream->Write(GC.getNumFeatureInfos(), m_piFeatureConvert);
	stream->Write(GC.getNumFeatureInfos(), m_pbFeatureInvalid);
	// MNAI end
}

Code from the other read() function for the XML. I am copying the way TerrainPassableTech and FeaturePassableTech did it:
Spoiler :
Code:
pXML->GetChildXmlValByName(m_szSound, "Sound");

	// MNAI begin
	CvString* pszTemp = NULL;
	pXML->SetVariableListTagPair(&pszTemp, "TerrainConversions", sizeof(GC.getTerrainInfo((TerrainTypes)0)), GC.getNumTerrainInfos());
	m_piTerrainConvert = new int[GC.getNumTerrainInfos()];
	for (int i = 0; i < GC.getNumTerrainInfos(); ++i)
	{
		m_piTerrainConvert[i] = pszTemp[i].IsEmpty() ? NO_TERRAIN : pXML->FindInInfoClass(pszTemp[i]);
	}
	SAFE_DELETE_ARRAY(pszTemp);

	pXML->SetVariableListTagPair(&pszTemp, "FeatureConversions", sizeof(GC.getFeatureInfo((FeatureTypes)0)), GC.getNumFeatureInfos());
	m_piFeatureConvert = new int[GC.getNumFeatureInfos()];
	for (int i = 0; i < GC.getNumFeatureInfos(); ++i)
	{
		m_piFeatureConvert[i] = pszTemp[i].IsEmpty() ? i : pXML->FindInInfoClass(pszTemp[i], true);
	}
	SAFE_DELETE_ARRAY(pszTemp);

	pXML->SetVariableListTagPair(&m_pbFeatureInvalid, "FeatureInvalids", sizeof(GC.getFeatureInfo((FeatureTypes)0)), GC.getNumFeatureInfos());
	// MNAI end

	return true;
}

Here is the code where the bug occurs. The setTerrainType() function is sometimes given values that are out of range of all terrain types by getTerrainConvert(). I have never seen getFeatureConvert() give setFeatureType() invalid values even though it comes first, so the problem seems to only be with the terrain conversions.
Spoiler :
Code:
void CvUnit::castTerraform(int spell)
{
	CvSpellInfo& kSpellInfo = GC.getSpellInfo((SpellTypes) spell);
	CvPlot* pPlot = plot();
	int iRange = kSpellInfo.getRange();

	for (int iDX = -iRange; iDX <= iRange; ++iDX)
	{
		for (int iDY = -iRange; iDY <= iRange; ++iDY)
		{
			CvPlot* pLoopPlot = plotXY(pPlot->getX_INLINE(), pPlot->getY_INLINE(), iDX, iDY);
			if (pLoopPlot != NULL)
			{
				FeatureTypes eFeature = pLoopPlot->getFeatureType();
				if (eFeature != NO_FEATURE)
				{
					if (kSpellInfo.isFeatureInvalid(eFeature))
					{
						continue;
					}
					if (kSpellInfo.getFeatureConvert(eFeature) != eFeature)
					{
						pLoopPlot->setFeatureType((FeatureTypes) kSpellInfo.getFeatureConvert(eFeature));
					}
				}
				if (kSpellInfo.getTerrainConvert(pLoopPlot->getTerrainType()) != NO_TERRAIN)
				{
					[U][B]pLoopPlot->setTerrainType((TerrainTypes) kSpellInfo.getTerrainConvert(pLoopPlot->getTerrainType()));[/B][/U]
					if (kSpellInfo.isRemoveInvalidFeature() && pLoopPlot->getFeatureType() != NO_FEATURE)
					{
						if (!GC.getFeatureInfo(pLoopPlot->getFeatureType()).isTerrain(pLoopPlot->getTerrainType()))
						{
							pLoopPlot->setFeatureType(NO_FEATURE);
						}
					}
				}
			}
		}
	}
 
Back
Top Bottom