Case Study: Adding new XML attributes and using them in the SDK

Kael

Deity
Joined
May 6, 2002
Messages
17,401
Location
Ohio
My goal in making this was to add 4 new values to leaders that adjusts their attitude. Those values are:

1. An attitude modifier based on the other civilizations alignment compared to the deciding civilization.
2. An attitude modifier if the other civilization uses Death magic.
3. An attitude modifier if the other civilization uses Entropy magic.
4. An attitude modifier if the other civilization has its Compassion civic option set to a "good" value.
5. An attitude modifier if the other civilization has its Compassion civic option set to an "evil" value.

I realize these are FfH specific values but I tihnk the process is general enough that it can be used for other functions. Basically for anytime you want to create a new value in XML and reference it within the SDK.

Step 1: Add the values to the XML

First the CIV4CivilizationsSchema.xml file will need to be modified to allow the new attributes. The new attribute definitions were inserted into the following section. The new lines are in bold.

Code:
	<ElementType name="iSameReligionAttitudeChange" content="textOnly" dt:type="int"/>
	<ElementType name="iSameReligionAttitudeDivisor" content="textOnly" dt:type="int"/>
	<ElementType name="iSameReligionAttitudeChangeLimit" content="textOnly" dt:type="int"/>
	<ElementType name="iDifferentReligionAttitudeChange" content="textOnly" dt:type="int"/>
	<ElementType name="iDifferentReligionAttitudeDivisor" content="textOnly" dt:type="int"/>
	<ElementType name="iDifferentReligionAttitudeChangeLimit" content="textOnly" dt:type="int"/>
	[b]<ElementType name="Alignment" content="textOnly" dt:type="int"/>
	<ElementType name="iUseDeathAttitudeChange" content="textOnly" dt:type="int"/>
	<ElementType name="iUseEntropyAttitudeChange" content="textOnly" dt:type="int"/>
	<ElementType name="iHighCompassionAttitudeChange" content="textOnly" dt:type="int"/>
	<ElementType name="iLowCompassionAttitudeChange" content="textOnly" dt:type="int"/>[/b]
	<ElementType name="iBonusTradeAttitudeDivisor" content="textOnly" dt:type="int"/>
	<ElementType name="iBonusTradeAttitudeChangeLimit" content="textOnly" dt:type="int"/>

In the <ElementType name="LeaderHeadInfo" content="eltOnly"> section I added the definitions to make them valid attributes for the LeaderHeadInfo element (changes in bold):

Code:
		<element type="iSameReligionAttitudeChange"/>
		<element type="iSameReligionAttitudeDivisor"/>
		<element type="iSameReligionAttitudeChangeLimit"/>
		<element type="iDifferentReligionAttitudeChange"/>
		<element type="iDifferentReligionAttitudeDivisor"/>
		<element type="iDifferentReligionAttitudeChangeLimit"/>
		[b]<element type="Alignment"/>
		<element type="iUseDeathAttitudeChange"/>
		<element type="iUseEntropyAttitudeChange"/>
		<element type="iHighCompassionAttitudeChange"/>
		<element type="iLowCompassionAttitudeChange"/>[/b]
		<element type="iBonusTradeAttitudeDivisor"/>
		<element type="iBonusTradeAttitudeChangeLimit"/>

Then in the CIV4LeaderHeadInfos.xml file each leader must be modified to insert the new attributes and give each a value.

Code:
            <iSameReligionAttitudeChange>1</iSameReligionAttitudeChange>
            <iSameReligionAttitudeDivisor>10</iSameReligionAttitudeDivisor>
            <iSameReligionAttitudeChangeLimit>3</iSameReligionAttitudeChangeLimit>
            <iDifferentReligionAttitudeChange>-1</iDifferentReligionAttitudeChange>
            <iDifferentReligionAttitudeDivisor>-5</iDifferentReligionAttitudeDivisor>
            <iDifferentReligionAttitudeChangeLimit>-1</iDifferentReligionAttitudeChangeLimit>
            [b]<Alignment>1</Alignment>
	<iUseDeathAttitudeChange>-4</iUseDeathAttitudeChange>
            <iUseEntropyAttitudeChange>-4</iUseEntropyAttitudeChange>
            <iHighCompassionAttitudeChange>1</iHighCompassionAttitudeChange>
            <iLowCompassionAttitudeChange>-1</iLowCompassionAttitudeChange>[/b]
            <iBonusTradeAttitudeDivisor>50</iBonusTradeAttitudeDivisor>
            <iBonusTradeAttitudeChangeLimit>2</iBonusTradeAttitudeChangeLimit>

Once we are done these values will give this leader a -4 attitude modifier if the other civ is unsing death magic, -4 if the other civ is using entropy magic, +1 if the other civ has a "good" option selected in their Compassion civic and -1 if the other civ has an "evil" option selected in their Compassion civic.

This is a good point to stop and load your mod. It should load without errors if everything to this point has been done correctly. But since we haven't changed any code to take advantage of the attributes even if it does load without errors the new attributes won't do anything. So off we go to the SDK.

Step 2: Allowing the SDK to read the new attributes

The next step is to expose our new XML attributes to the SDK. For that we will need to define some functions that will read in the new attributes. This is done in 2 modules. In the CvInfo.cpp we will need to make the following changes:

CvInfos.cpp

First we need to define the internal variables we will use:

Code:
m_iSameReligionAttitudeChange(0),
m_iSameReligionAttitudeDivisor(0),
m_iSameReligionAttitudeChangeLimit(0),
m_iDifferentReligionAttitudeChange(0),
m_iDifferentReligionAttitudeDivisor(0),
m_iDifferentReligionAttitudeChangeLimit(0),
[b]m_iAlignment(0),
m_iUseDeathAttitudeChange(0),
m_iUseEntropyAttitudeChange(0),
m_iHighCompassionAttitudeChange(0),
m_iLowCompassionAttitudeChange(0),[/b]
m_iBonusTradeAttitudeDivisor(0),
m_iBonusTradeAttitudeChangeLimit(0),

Then (also in CvInfos.cpp) we must add the functions that allow us to query this attribute.

Code:
int CvLeaderHeadInfo::getSameReligionAttitudeChange() const
{
	return m_iSameReligionAttitudeChange;
}

int CvLeaderHeadInfo::getSameReligionAttitudeDivisor() const
{
	return m_iSameReligionAttitudeDivisor;
}

int CvLeaderHeadInfo::getSameReligionAttitudeChangeLimit() const
{
	return m_iSameReligionAttitudeChangeLimit;
}

int CvLeaderHeadInfo::getDifferentReligionAttitudeChange() const
{
	return m_iDifferentReligionAttitudeChange;
}

int CvLeaderHeadInfo::getDifferentReligionAttitudeDivisor() const
{
	return m_iDifferentReligionAttitudeDivisor;
}

int CvLeaderHeadInfo::getDifferentReligionAttitudeChangeLimit() const
{
	return m_iDifferentReligionAttitudeChangeLimit;
}

[b]int CvLeaderHeadInfo::getAlignment() const
{
	return m_iAlignment;
}

void CvLeaderHeadInfo::setAlignment(int i)
{
	m_iAlignment = i;
}

int CvLeaderHeadInfo::getUseDeathAttitudeChange() const
{
	return m_iUseDeathAttitudeChange;
}

int CvLeaderHeadInfo::getUseEntropyAttitudeChange() const
{
	return m_iUseEntropyAttitudeChange;
}

int CvLeaderHeadInfo::getHighCompassionAttitudeChange() const
{
	return m_iHighCompassionAttitudeChange;
}

int CvLeaderHeadInfo::getLowCompassionAttitudeChange() const
{
	return m_iLowCompassionAttitudeChange;
}[/b]

int CvLeaderHeadInfo::getBonusTradeAttitudeDivisor() const
{
	return m_iBonusTradeAttitudeDivisor;
}

int CvLeaderHeadInfo::getBonusTradeAttitudeChangeLimit() const
{
	return m_iBonusTradeAttitudeChangeLimit;
}

Then in the CvLeaderHeadInfo::read(FDataStreamBase* stream) function we will add the ability to read the new attributes.

Code:
 	stream->Read(&m_iSameReligionAttitudeChange);
	stream->Read(&m_iSameReligionAttitudeDivisor);
	stream->Read(&m_iSameReligionAttitudeChangeLimit);
	stream->Read(&m_iDifferentReligionAttitudeChange);
	stream->Read(&m_iDifferentReligionAttitudeDivisor);
	stream->Read(&m_iDifferentReligionAttitudeChangeLimit);
	[b]stream->Read(&m_iAlignment);
	stream->Read(&m_iUseDeathAttitudeChange);
	stream->Read(&m_iUseEntropyAttitudeChange);
	stream->Read(&m_iHighCompassionAttitudeChange);
	stream->Read(&m_iLowCompassionAttitudeChange);[/b]
	stream->Read(&m_iBonusTradeAttitudeDivisor);
	stream->Read(&m_iBonusTradeAttitudeChangeLimit);

Likewise in the CvLeaderHeadInfo::write(FDataStreamBase* stream) function we will need to add the ability to write to the new attributes.

Code:
	stream->Write(m_iSameReligionAttitudeChange);
	stream->Write(m_iSameReligionAttitudeDivisor);
	stream->Write(m_iSameReligionAttitudeChangeLimit);
	stream->Write(m_iDifferentReligionAttitudeChange);
	stream->Write(m_iDifferentReligionAttitudeDivisor);
	stream->Write(m_iDifferentReligionAttitudeChangeLimit);
	[b]stream->Write(m_iAlignment);
	stream->Write(m_iUseDeathAttitudeChange);
	stream->Write(m_iUseEntropyAttitudeChange);
	stream->Write(m_iHighCompassionAttitudeChange);
	stream->Write(m_iLowCompassionAttitudeChange);[/b]
	stream->Write(m_iBonusTradeAttitudeDivisor);
	stream->Write(m_iBonusTradeAttitudeChangeLimit);

Lastly (for CvInfos.cpp) we will need the ability to pull the attribute from XML. This is done in the CvLeaderHeadInfo::read(CvXMLLoadUtility* pXML) function.

Code:
	pXML->GetChildXmlValByName(&m_iSameReligionAttitudeChange, "iSameReligionAttitudeChange");
	pXML->GetChildXmlValByName(&m_iSameReligionAttitudeDivisor, "iSameReligionAttitudeDivisor");
	pXML->GetChildXmlValByName(&m_iSameReligionAttitudeChangeLimit, "iSameReligionAttitudeChangeLimit");
	pXML->GetChildXmlValByName(&m_iDifferentReligionAttitudeChange, "iDifferentReligionAttitudeChange");
	pXML->GetChildXmlValByName(&m_iDifferentReligionAttitudeDivisor, "iDifferentReligionAttitudeDivisor");
	pXML->GetChildXmlValByName(&m_iDifferentReligionAttitudeChangeLimit, "iDifferentReligionAttitudeChangeLimit");
	[b]pXML->GetChildXmlValByName(&m_iAlignment, "Alignment");
	pXML->GetChildXmlValByName(&m_iUseDeathAttitudeChange, "iUseDeathAttitudeChange");
	pXML->GetChildXmlValByName(&m_iUseEntropyAttitudeChange, "iUseEntropyAttitudeChange");
	pXML->GetChildXmlValByName(&m_iHighCompassionAttitudeChange, "iHighCompassionAttitudeChange");
	pXML->GetChildXmlValByName(&m_iLowCompassionAttitudeChange, "iLowCompassionAttitudeChange");[/b]
	pXML->GetChildXmlValByName(&m_iBonusTradeAttitudeDivisor, "iBonusTradeAttitudeDivisor");
	pXML->GetChildXmlValByName(&m_iBonusTradeAttitudeChangeLimit, "iBonusTradeAttitudeChangeLimit");

Then onto CvInfos.h to declare the new functions we added.

CvInfos.h

Code:
	DllExport int getSameReligionAttitudeChange() const;				// Exposed to Python
	DllExport int getSameReligionAttitudeDivisor() const;				// Exposed to Python
	DllExport int getSameReligionAttitudeChangeLimit() const;				// Exposed to Python
	DllExport int getDifferentReligionAttitudeChange() const;				// Exposed to Python
	DllExport int getDifferentReligionAttitudeDivisor() const;				// Exposed to Python
	DllExport int getDifferentReligionAttitudeChangeLimit() const;				// Exposed to Python
	[b]DllExport int getAlignment() const;				// Exposed to Python
	DllExport void setAlignment(int i);				// Exposed to Python
	DllExport int getUseDeathAttitudeChange() const;				// Exposed to Python
	DllExport int getUseEntropyAttitudeChange() const;				// Exposed to Python
	DllExport int getHighCompassionAttitudeChange() const;				// Exposed to Python
	DllExport int getLowCompassionAttitudeChange() const;				// Exposed to Python[/b]
	DllExport int getBonusTradeAttitudeDivisor() const;				// Exposed to Python
	DllExport int getBonusTradeAttitudeChangeLimit() const;				// Exposed to Python

And the new variables we added.

Code:
	int m_iSameReligionAttitudeChange;
	int m_iSameReligionAttitudeDivisor;
	int m_iSameReligionAttitudeChangeLimit;
	int m_iDifferentReligionAttitudeChange;
	int m_iDifferentReligionAttitudeDivisor;
	int m_iDifferentReligionAttitudeChangeLimit;
	[b]int m_iAlignment;
	int m_iUseDeathAttitudeChange;
	int m_iUseEntropyAttitudeChange;
	int m_iHighCompassionAttitudeChange;
	int m_iLowCompassionAttitudeChange;[/b]
	int m_iBonusTradeAttitudeDivisor;
	int m_iBonusTradeAttitudeChangeLimit;


At this point we have new attributes in XML and the ability to read them in the game engine. But we still don't actually do anything with them. go ahead and compile your DLL with just these changes and make sure it loads without errors before going onto the next section.

Step 3: Making the new attributes do something

Now the fun part, we get to do something with our new attributes! This section will probably be less specifically applicable to your mod because it details exactly how I will be using these attributes, you may use the attributes you created entirely differently. Still it serves as a good example of what can be done.

The attitude modifiers are handled in the CvPlayerAI.cpp file so we will create 4 new functions in that file. As you can tell from the code this is where we really define what it means to "UseDeath" or "HighCompassion". Basically a civilization applies its iUseDeathAttitudeChange modifier if the civilization it is judging has the death mana resource in its capital. If we wanted to change this definition (to look through all the cities, see if "death" units have been produced, etc) these are the functions we would do it in.

CvPlayerAI.cpp

Code:
[b]int CvPlayerAI::AI_getAlignmentAttitude(PlayerTypes ePlayer)
{
	int iAttitude;

	iAttitude = 0;

    if (GC.getLeaderHeadInfo(getPersonalityType()).getAlignment() == 1)
    {
        if (GC.getLeaderHeadInfo(GET_PLAYER(ePlayer).getPersonalityType()).getAlignment() == 1)
        {
            iAttitude = 2;
        }
        if (GC.getLeaderHeadInfo(GET_PLAYER(ePlayer).getPersonalityType()).getAlignment() == 0)
        {
            iAttitude = 0;
        }
        if (GC.getLeaderHeadInfo(GET_PLAYER(ePlayer).getPersonalityType()).getAlignment() == -1)
        {
            iAttitude = -6;
        }
    }
    if (GC.getLeaderHeadInfo(getPersonalityType()).getAlignment() == 0)
    {
        if (GC.getLeaderHeadInfo(GET_PLAYER(ePlayer).getPersonalityType()).getAlignment() == 1)
        {
            iAttitude = -2;
        }
        if (GC.getLeaderHeadInfo(GET_PLAYER(ePlayer).getPersonalityType()).getAlignment() == 0)
        {
            iAttitude = 0;
        }
        if (GC.getLeaderHeadInfo(GET_PLAYER(ePlayer).getPersonalityType()).getAlignment() == -1)
        {
            iAttitude = -2;
        }
    }
    if (GC.getLeaderHeadInfo(getPersonalityType()).getAlignment() == -1)
    {
        if (GC.getLeaderHeadInfo(GET_PLAYER(ePlayer).getPersonalityType()).getAlignment() == 1)
        {
            iAttitude = -4;
        }
        if (GC.getLeaderHeadInfo(GET_PLAYER(ePlayer).getPersonalityType()).getAlignment() == 0)
        {
            iAttitude = -2;
        }
        if (GC.getLeaderHeadInfo(GET_PLAYER(ePlayer).getPersonalityType()).getAlignment() == -1)
        {
            iAttitude = 2;
        }
    }

	return iAttitude;
}

int CvPlayerAI::AI_getUseDeathAttitude(PlayerTypes ePlayer)
{
	int iAttitude;

	iAttitude = 0;

	if (GET_PLAYER(ePlayer).getCapitalCity() != NULL)
	{
		if (GET_PLAYER(ePlayer).getCapitalCity()->hasBonus((BonusTypes)GC.getInfoTypeForString("BONUS_MANA_DEATH")))
		{
			iAttitude += GC.getLeaderHeadInfo(getPersonalityType()).getUseDeathAttitudeChange();
		}
	}

	return iAttitude;
}

int CvPlayerAI::AI_getUseEntropyAttitude(PlayerTypes ePlayer)
{
	int iAttitude;

	iAttitude = 0;

	if (GET_PLAYER(ePlayer).getCapitalCity() != NULL)
	{
		if (GET_PLAYER(ePlayer).getCapitalCity()->hasBonus((BonusTypes)GC.getInfoTypeForString("BONUS_MANA_ENTROPY")))
		{
			iAttitude += GC.getLeaderHeadInfo(getPersonalityType()).getUseEntropyAttitudeChange();
		}
	}

	return iAttitude;
}

int CvPlayerAI::AI_getHighCompassionAttitude(PlayerTypes ePlayer)
{
	int iAttitude;

	iAttitude = 0;

	if (GET_PLAYER(ePlayer).getCivics((CivicOptionTypes)GC.getInfoTypeForString("CIVICOPTION_COMPASSION")) == (CivicOptionTypes)GC.getInfoTypeForString("CIVIC_PUBLIC_HEALERS") ||
        GET_PLAYER(ePlayer).getCivics((CivicOptionTypes)GC.getInfoTypeForString("CIVICOPTION_COMPASSION")) == (CivicOptionTypes)GC.getInfoTypeForString("CIVIC_PROTECT_THE_MEET"))
	{
		iAttitude += GC.getLeaderHeadInfo(getPersonalityType()).getHighCompassionAttitudeChange();
	}

	return iAttitude;
}

int CvPlayerAI::AI_getLowCompassionAttitude(PlayerTypes ePlayer)
{
	int iAttitude;

	iAttitude = 0;

	if (GET_PLAYER(ePlayer).getCivics((CivicOptionTypes)GC.getInfoTypeForString("CIVICOPTION_COMPASSION")) == (CivicOptionTypes)GC.getInfoTypeForString("CIVIC_FEND_FOR_THEMSELVES") ||
        GET_PLAYER(ePlayer).getCivics((CivicOptionTypes)GC.getInfoTypeForString("CIVICOPTION_COMPASSION")) == (CivicOptionTypes)GC.getInfoTypeForString("CIVIC_SACRIFICE_THE_WEAK"))
	{
		iAttitude += GC.getLeaderHeadInfo(getPersonalityType()).getLowCompassionAttitudeChange();
	}

	return iAttitude;
}[/b]

Then we have to adjust the function that calculates the attitude to ask for the attitude modifier from these functions. This is done in the CvPlayerAI::AI_getAttitudeVal(PlayerTypes ePlayer) function.

Code:
 	iAttitude += AI_getSameReligionAttitude(ePlayer);
	iAttitude += AI_getDifferentReligionAttitude(ePlayer);
	[b]iAttitude += AI_getAlignmentAttitude(ePlayer);
	iAttitude += AI_getUseDeathAttitude(ePlayer);
	iAttitude += AI_getUseEntropyAttitude(ePlayer);
	iAttitude += AI_getHighCompassionAttitude(ePlayer);
	iAttitude += AI_getLowCompassionAttitude(ePlayer);[/b]
	iAttitude += AI_getBonusTradeAttitude(ePlayer);

Finally we have to define the 4 new function we created in the CvPlayerAI.h file.

CvPlayerAI.h

Code:
	int AI_getSameReligionAttitude(PlayerTypes ePlayer);
	int AI_getDifferentReligionAttitude(PlayerTypes ePlayer);
	[b]int AI_getAlignmentAttitude(PlayerTypes ePlayer);
	int AI_getUseDeathAttitude(PlayerTypes ePlayer);
	int AI_getUseEntropyAttitude(PlayerTypes ePlayer);
	int AI_getHighCompassionAttitude(PlayerTypes ePlayer);
	int AI_getLowCompassionAttitude(PlayerTypes ePlayer);[/b]
	int AI_getBonusTradeAttitude(PlayerTypes ePlayer);


Thats it. It does seem like a lot of work but now that we have the attributes defined we can adjust them for each leader in the xml, which makes life easy. Also now that we have done the hard part of allowing the game engine to read the attributes from XML and associated them to the leader instances we can easily call them in any new functions we build.

Step 4 (optional): Making your adjustments show up on the diplo screen:

Thanks to Chalid for showing me where to do this. CvGameTextMgr.cpp controls the game text. Making the following addition to CvGameTextMgr::getAttitudeString will allow your attitude adjustments on the diplomacy screen.

CvGameTextMgr.cpp

Code:
[b] 		iAttitudeChange = GET_PLAYER(ePlayer).AI_getAlignmentAttitude(eTargetPlayer);
		if ((iPass == 0) ? (iAttitudeChange > 0) : (iAttitudeChange < 0))
		{
		    if (GC.getLeaderHeadInfo(GET_PLAYER(eTargetPlayer).getPersonalityType()).getAlignment() == 1)
		    {
                szTempBuffer.Format(SETCOLR L"%s" ENDCOLR, TEXT_COLOR((iAttitudeChange > 0) ? "COLOR_POSITIVE_TEXT" : "COLOR_NEGATIVE_TEXT"), gDLL->getText("TXT_KEY_MISC_ATTITUDE_GOOD_ALIGNMENT", iAttitudeChange).GetCString());
                szBuffer += NEWLINE + szTempBuffer;
		    }
		    if (GC.getLeaderHeadInfo(GET_PLAYER(eTargetPlayer).getPersonalityType()).getAlignment() == 0)
		    {
                szTempBuffer.Format(SETCOLR L"%s" ENDCOLR, TEXT_COLOR((iAttitudeChange > 0) ? "COLOR_POSITIVE_TEXT" : "COLOR_NEGATIVE_TEXT"), gDLL->getText("TXT_KEY_MISC_ATTITUDE_NEUTRAL_ALIGNMENT", iAttitudeChange).GetCString());
                szBuffer += NEWLINE + szTempBuffer;
		    }
		    if (GC.getLeaderHeadInfo(GET_PLAYER(eTargetPlayer).getPersonalityType()).getAlignment() == -1)
		    {
                szTempBuffer.Format(SETCOLR L"%s" ENDCOLR, TEXT_COLOR((iAttitudeChange > 0) ? "COLOR_POSITIVE_TEXT" : "COLOR_NEGATIVE_TEXT"), gDLL->getText("TXT_KEY_MISC_ATTITUDE_EVIL_ALIGNMENT", iAttitudeChange).GetCString());
                szBuffer += NEWLINE + szTempBuffer;
		    }
		}		iAttitudeChange = GET_PLAYER(ePlayer).AI_getUseDeathAttitude(eTargetPlayer);
		if ((iPass == 0) ? (iAttitudeChange > 0) : (iAttitudeChange < 0))
		{
			szTempBuffer.Format(SETCOLR L"%s" ENDCOLR, TEXT_COLOR((iAttitudeChange > 0) ? "COLOR_POSITIVE_TEXT" : "COLOR_NEGATIVE_TEXT"), gDLL->getText("TXT_KEY_MISC_ATTITUDE_DEATH_USE", iAttitudeChange).GetCString());
			szBuffer += NEWLINE + szTempBuffer;
		}
		iAttitudeChange = GET_PLAYER(ePlayer).AI_getUseEntropyAttitude(eTargetPlayer);
		if ((iPass == 0) ? (iAttitudeChange > 0) : (iAttitudeChange < 0))
		{
			szTempBuffer.Format(SETCOLR L"%s" ENDCOLR, TEXT_COLOR((iAttitudeChange > 0) ? "COLOR_POSITIVE_TEXT" : "COLOR_NEGATIVE_TEXT"), gDLL->getText("TXT_KEY_MISC_ATTITUDE_ENTROPY_USE", iAttitudeChange).GetCString());
			szBuffer += NEWLINE + szTempBuffer;
		}
		iAttitudeChange = GET_PLAYER(ePlayer).AI_getHighCompassionAttitude(eTargetPlayer);
		if ((iPass == 0) ? (iAttitudeChange > 0) : (iAttitudeChange < 0))
		{
			szTempBuffer.Format(SETCOLR L"%s" ENDCOLR, TEXT_COLOR((iAttitudeChange > 0) ? "COLOR_POSITIVE_TEXT" : "COLOR_NEGATIVE_TEXT"), gDLL->getText("TXT_KEY_MISC_ATTITUDE_HIGH_COMPASSION", iAttitudeChange).GetCString());
			szBuffer += NEWLINE + szTempBuffer;
		}
		iAttitudeChange = GET_PLAYER(ePlayer).AI_getLowCompassionAttitude(eTargetPlayer);
		if ((iPass == 0) ? (iAttitudeChange > 0) : (iAttitudeChange < 0))
		{
			szTempBuffer.Format(SETCOLR L"%s" ENDCOLR, TEXT_COLOR((iAttitudeChange > 0) ? "COLOR_POSITIVE_TEXT" : "COLOR_NEGATIVE_TEXT"), gDLL->getText("TXT_KEY_MISC_ATTITUDE_LOW_COMPASSION", iAttitudeChange).GetCString());
			szBuffer += NEWLINE + szTempBuffer;
		}[/b]

And of course the text strings I used in the above code need to be put in one of the files in your &#8216;/Assets/xml/text/&#8217; directory. The following are the defines I use but you can translate them to whatever fits your mod:

CIV4GameText_FFH2.xml (or any file in the &#8216;/Assets/xml/text&#8217; directory)

Code:
    <TEXT>
        <Tag>TXT_KEY_MISC_ATTITUDE_EVIL_ALIGNMENT</Tag>
        <English>%D1: "You are Evil."</English>
        <French>%D1: "You are Evil."</French>
        <German>%D1: "You are Evil."</German>
        <Italian>%D1: "You are Evil."</Italian>
        <Spanish>%D1: "You are Evil."</Spanish>
    </TEXT>
    <TEXT>
        <Tag>TXT_KEY_MISC_ATTITUDE_GOOD_ALIGNMENT</Tag>
        <English>%D1: "You are Good."</English>
        <French>%D1: "You are Good."</French>
        <German>%D1: "You are Good."</German>
        <Italian>%D1: "You are Good."</Italian>
        <Spanish>%D1: "You are Good."</Spanish>
    </TEXT>
    <TEXT>
        <Tag>TXT_KEY_MISC_ATTITUDE_NEUTRAL_ALIGNMENT</Tag>
        <English>%D1: "You are Neutral."</English>
        <French>%D1: "You are Neutral."</French>
        <German>%D1: "You are Neutral."</German>
        <Italian>%D1: "You are Neutral."</Italian>
        <Spanish>%D1: "You are Neutral."</Spanish>
    </TEXT>
    <TEXT>
        <Tag>TXT_KEY_MISC_ATTITUDE_DEATH_USE</Tag>
        <English>%D1: "You use Death magic."</English>
        <French>%D1: "You use Death magic."</French>
        <German>%D1: "You use Death magic."</German>
        <Italian>%D1: "You use Death magic."</Italian>
        <Spanish>%D1: "You use Death magic."</Spanish>
    </TEXT>
    <TEXT>
        <Tag>TXT_KEY_MISC_ATTITUDE_ENTROPY_USE</Tag>
        <English>%D1: "You use Entropy magic."</English>
        <French>%D1: "You use Entropy magic."</French>
        <German>%D1: "You use Entropy magic."</German>
        <Italian>%D1: "You use Entropy magic."</Italian>
        <Spanish>%D1: "You use Entropy magic."</Spanish>
    </TEXT>
    <TEXT>
        <Tag>TXT_KEY_MISC_ATTITUDE_HIGH_COMPASSION</Tag>
        <English>%D1: "You treat your people well."</English>
        <French>%D1: "You treat your people well."</French>
        <German>%D1: "You treat your people well."</German>
        <Italian>%D1: "You treat your people well."</Italian>
        <Spanish>%D1: "You treat your people well."</Spanish>
    </TEXT>
    <TEXT>
        <Tag>TXT_KEY_MISC_ATTITUDE_LOW_COMPASSION</Tag>
        <English>%D1: "You treat your people poorly."</English>
        <French>%D1: "You treat your people poorly."</French>
        <German>%D1: "You treat your people poorly."</German>
        <Italian>%D1: "You treat your people poorly."</Italian>
        <Spanish>%D1: "You treat your people poorly."</Spanish>
    </TEXT>
 

Attachments

  • ffh2diplo.JPG
    ffh2diplo.JPG
    210.4 KB · Views: 12,574
Very cool Kael, thanks!!!
 
TheLopez said:
Very cool Kael, thanks!!!

Yeah, I want to use this to be able to set variables on leaders for their preference for hiring/selling mercenaries.
 
It looked really easy right up to the last step ;). I am curious, have you created a 'modification only' version of the game so that you can play around with all the .dlls without fear of ruining your actual game? Or is the system more robust than that? Just wondering before I take the plunge myself.

Aussie_Lurker.
 
The SDK is only one dll (CvGameCoreDLL.dll), and you don't need to overwrite the original to use it. Just stick it under "My Games\Sid Meier's Civilization 4\CustomAssets" to use it for a normal game, or it can go inside a mod. I.e. In "My Games\Sid Meier's Civilization 4\MODS\<modname>\Assets".
 
This is a very usefull tutorial (which I could never have figured out on my own) I would like to point out a what I think would have been a better way to implement the same effect.

Rather then create XML tags for specific Bonuses a generalized <BonusDiplomacyModifers> grouping would hold any number of individual Modifiers, each modifier would speficy a Resorce an Int and a Boolean.

The C code would then be something like this psudocode

if ( not (OtherPlayer.hasResorce("InputResorce") xor inputBoolean) )
DiplomaticStatus += InputInt

The effect is that the Boolean controls if the diplomatic modifier is aplied when they have it (true) or dont have it (false). So if you wanted to have an empire hate other empires that use Death Magic (or any other resorce) you XML would be ("Death_Magic", -3, true). Or you could have them love anyone who isn't using Entropy ("Entropy_Magic", +2, false). This should be much more flexible system and it can be used by other mod makers in the future.
 
Kael said:
One last bit of housekeeping before we start the real programming. We need to assign the new attributes to the LeaderHead object so that they can be queried for specific instances of leaders. This is done in the CyInfoInterface3.cpp file.
Not quite true - CyInfoInterface3.cpp (and any Cy*Interface#.cpp file) exposes stuff to python, so you only need to worry about it if you want what you added exposed to python.
 
talchas said:
Not quite true - CyInfoInterface3.cpp (and any Cy*Interface#.cpp file) exposes stuff to python, so you only need to worry about it if you want what you added exposed to python.

Good to know, I'll update the writeup.
 
I updated the first post to add another attitude modifer, alignment. Depending on the alignment of the target civlization and the deciding civilization a different attitude modifier is applied.

I also added a section that allows your modifiers to display on the diplomacy screen (thanks to Chalid for pointing out where this is done) and a screen shot of the finished result in action.

Alignment is especially interesting as you could use it in your mods to represent any sort of informal relationship between civs. If, for example, you wanted certain civs to get along well and others not to.

Enjoy!
 
Hiya Kael.

First up, I have to say that I THINK I am beginning to understand this, but I do have a couple of questions:

1) Can this be used to add ANY new XML attribute? For instance, I want certain civics options to be able to increase Specialist Yields (Hammers, Food, Gold) and not simply commerces (culture, research and gold). Could I do this using the SDK? I know it can technically be done in Python, but I don't know how to then make players aware of the effects.

2) The file in question consists of a HUGE number of .cpp files, and I confess that it is somewhat bewildering!!! Can you point me towards a comprehensive list of the .cpp's, and what they primarily effect?

Thanks in advance.

Aussie_Lurker.
 
Aussie_Lurker said:
Hiya Kael.

First up, I have to say that I THINK I am beginning to understand this, but I do have a couple of questions:

1) Can this be used to add ANY new XML attribute? For instance, I want certain civics options to be able to increase Specialist Yields (Hammers, Food, Gold) and not simply commerces (culture, research and gold). Could I do this using the SDK? I know it can technically be done in Python, but I don't know how to then make players aware of the effects.

Yes, and adding the attributes is pretty easy. Getting the attributes to do something is the hard part. Adding schema attributes and modifing the CvCity.cpp::doTurn() function and the text output (probably somewhere around CvGameTextMgr.cpp::parseCivicInfo() ) is the "best" way to do what you are describing. The simpliest like you say would be to modify python and just update the strategy tag to tell the player what it is doing.

2) The file in question consists of a HUGE number of .cpp files, and I confess that it is somewhat bewildering!!! Can you point me towards a comprehensive list of the .cpp's, and what they primarily effect?

Thanks in advance.

Aussie_Lurker.

Nope, I don't have any list. The names are pretty descriptive. The Cv... files are the real files. The Cy... version are for export to Python. The .cpp files are the code and the .h files are the headers which define the variables. I spend a lot of time in CvUnit.cpp, CvCity.cpp, CvGame.cpp, CvPlayer.cpp and CvPlot.cpp.
 
Kael, first of all, I want to thank you for ALL of your assistance up until this point. I wouldn't even be able to ask you this question without your help.

Now, as you know, I am a bit of a dunce when it comes to Python :mischief:, but I feel your tutorial above has given me the best chance yet of avoiding the need to do Python in many cases-simply by adding new XML 'hooks' via the SDK.
Now I have followed all of your instruction to this point, and feel fairly confident that they should work, and am now up to your 'optional' section.
What I have basically done is added a getStateReligionCommerceModifier and getStateReligionYieldModifier for civics options.
I have added them to both the cvInfos file-in both places-as well as in the cvInfos.h file.
The question is, do I REALLY need to do anything more at this point or, as I believe, can I go ahead and compile? If you can let me know, I would VERY much appreciate it :).

Aussie_Lurker.
 
Aussie_Lurker said:
Kael, first of all, I want to thank you for ALL of your assistance up until this point. I wouldn't even be able to ask you this question without your help.

Now, as you know, I am a bit of a dunce when it comes to Python :mischief:, but I feel your tutorial above has given me the best chance yet of avoiding the need to do Python in many cases-simply by adding new XML 'hooks' via the SDK.
Now I have followed all of your instruction to this point, and feel fairly confident that they should work, and am now up to your 'optional' section.
What I have basically done is added a getStateReligionCommerceModifier and getStateReligionYieldModifier for civics options.
I have added them to both the cvInfos file-in both places-as well as in the cvInfos.h file.
The question is, do I REALLY need to do anything more at this point or, as I believe, can I go ahead and compile? If you can let me know, I would VERY much appreciate it :).

Aussie_Lurker.

Assuming they are being called by whereever the turn upkeeps are generated it sounds like you've got it.
 
Kael, I am afraid my first attempt failed, but I feel certain this can be rectified. You mentioned that as long as the functions are being called-but how do I make certain of this before I go ahead and compile?

Aussie_Lurker.
 
Aussie_Lurker said:
Kael, I am afraid my first attempt failed, but I feel certain this can be rectified. You mentioned that as long as the functions are being called-but how do I make certain of this before I go ahead and compile?

Aussie_Lurker.

By "called" I just mean that there is some functon that is using your new attributes. I would highly recommend compiling as you go and making sure the dll works. There is nothing worse than doing a ton of work, then compiling and figuring out it doesn't work. I recommended a few compiling points in the first post.
 
Top Bottom