Sample SDK Code (basic)

Uhm, I dont understand really what you did there. Why not just add the +40% or whatever to the promotion rather then in the battle code? Im confused.
 
Grey Fox said:
Uhm, I dont understand really what you did there. Why not just add the +40% or whatever to the promotion rather then in the battle code? Im confused.

If you added +40% to the promotion it would be +40% vs everything. Applying the modifier in this function lets a modmaker be more granular about when the bonus is gained. Want a unit to have a bonus when it is attacking into forests or when the defending unit is French/fatigued/republican/hindu/etc etc? Then you may want to modify the maxCombatStr code.
 
Kael said:
If you added +40% to the promotion it would be +40% vs everything. Applying the modifier in this function lets a modmaker be more granular about when the bonus is gained. Want a unit to have a bonus when it is attacking into forests or when the defending unit is French/fatigued/republican/hindu/etc etc? Then you may want to modify the maxCombatStr code.

Ok, im getting what you are saying now.

But cant you just make some units be classed Elf. And then the promotion give +40% vs Elf like the ones in the game does vs melee etc. Or do you want your elfs to still be melee and also elf? Isnt it easier to make a super class then that you give all units that defines their race? And a promotion can be against Unit Superclass, class and type (like the Grenadiers +vs Riflemen.).

Or am I still confused?
 
Grey Fox said:
Ok, im getting what you are saying now.

But cant you just make some units be classed Elf. And then the promotion give +40% vs Elf like the ones in the game does vs melee etc. Or do you want your elfs to still be melee and also elf? Isnt it easier to make a super class then that you give all units that defines their race? And a promotion can be against Unit Superclass, class and type (like the Grenadiers +vs Riflemen.).

Or am I still confused?

Nope, you are absolutly correct. In fact thats exactly how I did it in phase 1. But in phase 2 of my mod I wanted the units to be archers and elves, or melee and elves or whatever. Also the system is a lot more flexible than the unitcombat system is since promotions can be added and removed on the fly.
 
Lord Olleus said:
But why do this in C++ Kael?
One reason I can think of is it makes a good example for how to use the SDK in the absence of documentation from Firaxis. Good work Kael :goodjob:.
 
Lord Olleus said:
But why do this in C++ Kael?
Why not just create an event which is called at the same time as combat and do that in python? It would be a much neater way of doing it.

There is a time to use python and a time to use C++. You may be right, this may be a better time for a python routine. In all honesty I did it this way because it was very easy. I haven't added any python events called from the SDK yet. There are a few I would love to see but I don't know how to do them yet.

Most notably I would love to see a pre-combat python event, a cannotAttack python event and plot.doTurn() python event. Though that last one may be expensive.

I dont think I have ever seen a python event that passes data back to c++ as we would need to do if you wanted a python event that allow you to modify the odds.
 
Kael said:
I dont think I have ever seen a python event that passes data back to c++ as we would need to do if you wanted a python event that allow you to modify the odds.
What about CvGameUtils.py? Those functions return values to the c++ part don't they?

edit: from canTrain in CvCity.cpp
Code:
long lResult=0;
	gDLL->getPythonIFace()->callFunction(PYGameModule, "canTrain", argsList.makeFunctionArgs(), &lResult);
	delete pyCity;	// python fxn must not hold on to this pointer 
	if (lResult == 1)
	{
		return true;
	}
 
snarko said:
What about CvGameUtils.py? Those functions return values to the c++ part don't they?

edit: from canTrain in CvCity.cpp
Code:
long lResult=0;
	gDLL->getPythonIFace()->callFunction(PYGameModule, "canTrain", argsList.makeFunctionArgs(), &lResult);
	delete pyCity;	// python fxn must not hold on to this pointer 
	if (lResult == 1)
	{
		return true;
	}

Your absolutly right, good call!
 
Kael said:
...Want a unit to have a bonus when it is attacking into forests ...

This is the similar to the bonus I'm trying to implement - "Attackers with Woodsman2 (soon to be Woodsman3) gain a bonus when attacking into Forests equal to the current iDefense of the Forest feature. Also, the Defender does not receive the bonus."

The only part of this I've managed to get working is that I can reverse the terrain bonuses indiscriminantly, though the way I've done that seems messy from a coding point of view.

The problems I have are
  • identifying forests specifically (as they're Enumerated into FeatureTypes after loading the XML - the best I've got so far is along the lines of [ GC.getFeatureInfo(pPlot->getFeatureType()).getTextKey() == "FEATURE_FOREST" ]
  • Displaying a "[UNIT1] has been ambushed by [UNIT2]" message - struggling with the syntax for "gDLL->getInterfaceIFace()->addMessage"

If you were planning on adding any further functional example code to this thread - I'd request the above :D
 
This thread is only for samples of completed working code. Please start new threads for asking questions. That said I added a few recomendations.

Vehem said:
This is the similar to the bonus I'm trying to implement - "Attackers with Woodsman2 (soon to be Woodsman3) gain a bonus when attacking into Forests equal to the current iDefense of the Forest feature. Also, the Defender does not receive the bonus."

The only part of this I've managed to get working is that I can reverse the terrain bonuses indiscriminantly, though the way I've done that seems messy from a coding point of view.

The problems I have are
[*]identifying forests specifically (as they're Enumerated into FeatureTypes after loading the XML - the best I've got so far is along the lines of [ GC.getFeatureInfo(pPlot->getFeatureType()).getTextKey() == "FEATURE_FOREST" ]

You can search for enumerated data types by adding the (TYPE) to the getinfotype as in the following:

Code:
if (pPlot->getFeatureType() == (FeatureTypes)GC.getInfoTypeForString("FEATURE_FOREST"))

Sorry I havent messed with displaying text yet so I dont know about the 2nd question.
 
Adding a python cannotAttack check

This was actually pretty easy, I assumed it would be harder. In the CvUnit.cpp I added the following section to make the call:

void CvUnit::updateCombat(bool bQuick)

Code:
//FfH: Added by Kael 05/02/2006

	CyUnit* pyUnit = new CyUnit(this);
	CyUnit* pyUnit2 = new CyUnit(pDefender);
	CyArgsList argsList; // XXX
	long lResult=0;
	argsList.add(gDLL->getPythonIFace()->makePythonObject(pyUnit));	// pass in unit class
	argsList.add(gDLL->getPythonIFace()->makePythonObject(pyUnit2));	// pass in unit class
	gDLL->getPythonIFace()->callFunction(PYGameModule, "cannotAttack", argsList.makeFunctionArgs(), &lResult);
	delete pyUnit;	// python fxn must not hold on to this pointer
	delete pyUnit2;	// python fxn must not hold on to this pointer
	if (lResult == 1)
	{
		bFinish = true;
	}

//FfH: End Add

This passes the Attacker (CvUnit which is "this" in the function) and the Defender (pDefender) to the cannotAttack python function. And if the python fucntion returns a 1 (true) then it cancels combat.

To create the python function add the following function to the CvGameInterface.py file:

Code:
def cannotAttack(argsList):
	pAttacker = argsList[0]
	pDefender = argsList[1]

	if pAttacker.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CHARMED')):
		return True

	if pDefender.isHasPromotion(gc.getInfoTypeForString('PROMOTION_FEAR')):
		if pDefender.maxCombatStr(pDefender.plot(), pAttacker) > pAttacker.maxCombatStr(pDefender.plot(), pDefender):
			return True

	return False

And thats it. You can see that I check for a few things in the python routine. If the defender has the fear promotion and has higher strength than the attacker a false is returned (the attacker is unable to attack). Also if the attacker is charmed he is unable to attack.

I hope you guys find it handy. It allows you do make significant changes with very minor changes with the SDK and keeping your more complex programming in Python.
 
To add a python event, you have to do almost exactly the same thing described as above by Kael, but instead of using
Code:
gDLL->getPythonIFace()->callFunction(PYGameModule, "cannotAttack", argsList.makeFunctionArgs(), &lResult);
you should use:
Code:
gDLL->getEventReporterIFace()->genericEvent("yourevent", argsList.makeFunctionArgs());

And in your python event manager, just add your new event in the self.EventHandlerMap, let's say 'yourevent' : 'onYourEvent', create you onYourEvent function and that's it !
 
I wanted to allow more than one religion to spread to a city naturally. As is only one will spread and then you need to use a missionary or other method to get more religions to the city.

When I went to look at the code to do this I found a bit of a surprise, Firaxis had already programmed it to work this way and changed it later. Note that in the origional code there is a commented out line that divies the chance of the religion spreading by the amount of religions that already exist in the city. Also notice the if (!isHasReligion((ReligionTypes)iI)) line, which is completly unnessesary if you have already verified that the city has no religions as is done a few lines before.

So this is an extremly easy tweak, remove the if (getReligionCount() == 0) block and put the disor for multiple religions back in place and we are done.

I have attach the CvCoreGameDLL.dll for those that just want to use this change and haven't played with the SDK kit (I've had a few people request this change).

Game Effect: More than one religion can spread "naturally" (without missionarys) to a city. Although theoretically this could allow an unlimited amount of religions the chance of the religion spreading is divided by the amount of religions that already exist +1 so there is a practical limit.

CvCity.cpp

Original code:
Spoiler :
Code:
void CvCity::doReligion()
{
	CvCity* pLoopCity;
	int iRandThreshold;
	int iSpread;
	int iLoop;
	int iI, iJ;

	CyCity* pyCity = new CyCity(this);
	CyArgsList argsList;
	argsList.add(gDLL->getPythonIFace()->makePythonObject(pyCity));	// pass in city class
	long lResult=0;
	gDLL->getPythonIFace()->callFunction(PYGameModule, "doReligion", argsList.makeFunctionArgs(), &lResult);
	delete pyCity;	// python fxn must not hold on to this pointer 
	if (lResult == 1)
	{
		return;
	}

	if (getReligionCount() == 0)
	{
		for (iI = 0; iI < GC.getNumReligionInfos(); iI++)
		{
			if (!isHasReligion((ReligionTypes)iI))
			{
				if ((iI == GET_PLAYER(getOwnerINLINE()).getStateReligion()) || !(GET_PLAYER(getOwnerINLINE()).isNoNonStateReligionSpread()))
				{
					iRandThreshold = 0;

					for (iJ = 0; iJ < MAX_PLAYERS; iJ++)
					{
						if (GET_PLAYER((PlayerTypes)iJ).isAlive())
						{
							for (pLoopCity = GET_PLAYER((PlayerTypes)iJ).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER((PlayerTypes)iJ).nextCity(&iLoop))
							{
								if (pLoopCity->isConnectedTo(this))
								{
									iSpread = pLoopCity->getReligionInfluence((ReligionTypes)iI);

									iSpread *= GC.getReligionInfo((ReligionTypes)iI).getSpreadFactor();

									if (iSpread > 0)
									{
										iSpread /= max(1, (((GC.getDefineINT("RELIGION_SPREAD_DISTANCE_DIVISOR") * plotDistance(getX_INLINE(), getY_INLINE(), pLoopCity->getX_INLINE(), pLoopCity->getY_INLINE())) / GC.getMapINLINE().maxPlotDistance()) - 5));

										//iSpread /= (getReligionCount() + 1);

										iRandThreshold = max(iRandThreshold, iSpread);
									}
								}
							}
						}
					}

					if (GC.getGameINLINE().getSorenRandNum(GC.getDefineINT("RELIGION_SPREAD_RAND"), "Religion Spread") < iRandThreshold)
					{
						setHasReligion(((ReligionTypes)iI), true, true);
						break;
					}
				}
			}
		}
	}
}

Here is the changed code, the only changes made were to comment out 3 lines (basically the if statement that blocks if the city has more than 0 religions and its 2 { } lines).

Modified code:
Spoiler :
Code:
void CvCity::doReligion()
{
	CvCity* pLoopCity;
	int iRandThreshold;
	int iSpread;
	int iLoop;
	int iI, iJ;

	CyCity* pyCity = new CyCity(this);
	CyArgsList argsList;
	argsList.add(gDLL->getPythonIFace()->makePythonObject(pyCity));	// pass in city class
	long lResult=0;
	gDLL->getPythonIFace()->callFunction(PYGameModule, "doReligion", argsList.makeFunctionArgs(), &lResult);
	delete pyCity;	// python fxn must not hold on to this pointer
	if (lResult == 1)
	{
		return;
	}

//	if (getReligionCount() == 0)
//	{
		for (iI = 0; iI < GC.getNumReligionInfos(); iI++)
		{
			if (!isHasReligion((ReligionTypes)iI))
			{
				if ((iI == GET_PLAYER(getOwnerINLINE()).getStateReligion()) || !(GET_PLAYER(getOwnerINLINE()).isNoNonStateReligionSpread()))
				{
					iRandThreshold = 0;

					for (iJ = 0; iJ < MAX_PLAYERS; iJ++)
					{
						if (GET_PLAYER((PlayerTypes)iJ).isAlive())
						{
							for (pLoopCity = GET_PLAYER((PlayerTypes)iJ).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER((PlayerTypes)iJ).nextCity(&iLoop))
							{
								if (pLoopCity->isConnectedTo(this))
								{
									iSpread = pLoopCity->getReligionInfluence((ReligionTypes)iI);

									iSpread *= GC.getReligionInfo((ReligionTypes)iI).getSpreadFactor();

									if (iSpread > 0)
									{
										iSpread /= max(1, (((GC.getDefineINT("RELIGION_SPREAD_DISTANCE_DIVISOR") * plotDistance(getX_INLINE(), getY_INLINE(), pLoopCity->getX_INLINE(), pLoopCity->getY_INLINE())) / GC.getMapINLINE().maxPlotDistance()) - 5));

										iSpread /= (getReligionCount() + 1);

										iRandThreshold = max(iRandThreshold, iSpread);
									}
								}
							}
						}
					}

					if (GC.getGameINLINE().getSorenRandNum(GC.getDefineINT("RELIGION_SPREAD_RAND"), "Religion Spread") < iRandThreshold)
					{
						setHasReligion(((ReligionTypes)iI), true, true);
						break;
					}
				}
			}
		}
//	}
}

If you want this change without changing the SDK youself here is the modified CvGameCoreDLL with only this change: CvGameCoreDLL.dll
 
superb help Kael, thanks !
 
I want the Carrier unit to increase its strength by a certain percentage of the air unit strength whenever a fighter is on it and another percentage whenever a Jet is on it. I want these effects to be cumulative, but only for defense and only when they are on the intercept mission. Do I need the SDK to do this?
 
Kael, you might want to update post 35 to let people know that modification is done in CvCity.cpp :)

Kael said:
Just a simple example of a change that can be made in CvUnit::maxCombatStr(). This applies a combat modifier based on the existence of the "Elf" promotion on the defender and the "Elf Slaying" promotion on the attacker. maxCombatStr is called from the perspective of the defender and I have the 2 checks in so that the bonus is applied on attack and defense.

Changes are in bold.

CvUnit::maxCombatStr()

Code:
		if (getUnitCombatType() != NO_UNITCOMBAT)
		{
			iModifier -= pAttacker->unitCombatModifier(getUnitCombatType());
			if (pCombatDetails != NULL)
			{
				pCombatDetails->iCombatModifierT = -(pAttacker->unitCombatModifier(getUnitCombatType()));
			}
		}

[b]		if (isHasPromotion((PromotionTypes)GC.getInfoTypeForString("PROMOTION_ELF")))
		{
		    if (pAttacker->isHasPromotion((PromotionTypes)GC.getInfoTypeForString("PROMOTION_ELF_SLAYING")))
		    {
		        iModifier = iModifier - 40;
		    }
		}
		if (isHasPromotion((PromotionTypes)GC.getInfoTypeForString("PROMOTION_ELF_SLAYING")))
		{
		    if (pAttacker->isHasPromotion((PromotionTypes)GC.getInfoTypeForString("PROMOTION_ELF")))
		    {
		        iModifier = iModifier + 40;
		    }
		}[/b]

		iModifier += domainModifier(pAttacker->getDomainType());
		if (pCombatDetails != NULL)
		{
			pCombatDetails->iDomainModifierA = domainModifier(pAttacker->getDomainType());
		}

Why use this? It provides the ability to specify anti-unit combat bonuses without having to make new unit combats or declare specific unitclasses. Also since its easy to add and remove promotions as you play it can allow you to create pretty dynamic bonuses.

Outside of that just being familiar with the effect of changing maxCombatStr can be very helpful to anyone who wants to effect the combat odds. Changes made here are reflect both in the actual combat odds as well as the display.
How difficult is it to add a new tag in CIV4PromotionInfos.xml that is coded with a more generic code so as to allow modders to add their own anti-promotion promotions without setting the specifics for it all in the SDK?
 
Shqype said:
Kael, you might want to update post 35 to let people know that modification is done in CvCity.cpp :)


How difficult is it to add a new tag in CIV4PromotionInfos.xml that is coded with a more generic code so as to allow modders to add their own anti-promotion promotions without setting the specifics for it all in the SDK?

More difficult than what I have done but not impossible (and a better solution overall). Chalid is working on something along those lines and hopefully we will be able to get him to share the results if it is relativly consise.
 
Sounds good to me, hopefully he is successful!
 
Kael said:
Just a simple example of a change that can be made in CvUnit::maxCombatStr(). This applies a combat modifier based on the existence of the "Elf" promotion on the defender and the "Elf Slaying" promotion on the attacker. maxCombatStr is called from the perspective of the defender and I have the 2 checks in so that the bonus is applied on attack and defense.

Changes are in bold.

CvUnit::maxCombatStr()

Code:
		if (getUnitCombatType() != NO_UNITCOMBAT)
		{
			iModifier -= pAttacker->unitCombatModifier(getUnitCombatType());
			if (pCombatDetails != NULL)
			{
				pCombatDetails->iCombatModifierT = -(pAttacker->unitCombatModifier(getUnitCombatType()));
			}
		}

[b]		if (isHasPromotion((PromotionTypes)GC.getInfoTypeForString("PROMOTION_ELF")))
		{
		    if (pAttacker->isHasPromotion((PromotionTypes)GC.getInfoTypeForString("PROMOTION_ELF_SLAYING")))
		    {
		        iModifier = iModifier - 40;
		    }
		}
		if (isHasPromotion((PromotionTypes)GC.getInfoTypeForString("PROMOTION_ELF_SLAYING")))
		{
		    if (pAttacker->isHasPromotion((PromotionTypes)GC.getInfoTypeForString("PROMOTION_ELF")))
		    {
		        iModifier = iModifier + 40;
		    }
		}[/b]

		iModifier += domainModifier(pAttacker->getDomainType());
		if (pCombatDetails != NULL)
		{
			pCombatDetails->iDomainModifierA = domainModifier(pAttacker->getDomainType());
		}

Why use this? It provides the ability to specify anti-unit combat bonuses without having to make new unit combats or declare specific unitclasses. Also since its easy to add and remove promotions as you play it can allow you to create pretty dynamic bonuses.

Outside of that just being familiar with the effect of changing maxCombatStr can be very helpful to anyone who wants to effect the combat odds. Changes made here are reflect both in the actual combat odds as well as the display.

I know these will seem like very basic questions, but i only started with XML this week and im in at the deep end :confused:

1 - Im not sure where the file is that this set of instructions needs to be added to, anyone? (i did a search for .cpp files on my HD but couldnt find a single one, and browsing around the vanilla files as well as a few mods i can only see .py files)

2 - Anyone know where i can download a FREE c++ editor/compiler to load and edit the .cpp file? (when i find it) :)

3 - Can I just copy the code into the place shown in the example, with my own promotions replacing PROMOTION_ELF (to PROMOTION_BORG) and PROMOTION_ELFSLAYING (PROMOTION_RUSE1), or do i need to declare the variables somewhere else?
 
Top Bottom