SDK Mod Code samples

Seven05

Warmonger
Joined
Dec 5, 2005
Messages
2,056
Location
USA
In the spirit of the similar thread with Python code samples, here's a thread to post examples of C++ code for others to learn from/use. It will work best if everybody can refrain from posting broken code or requests in this thread.

(Although the python thread is over here, I thought this board was a better location, hopefully the moderators agree) :)

Anyway, I'll start it off with a relatively easy example:

In CvPlot.cpp, a replacement for the CvPlot::doImprovement() method:
Code:
void CvPlot::doImprovement()
{
	PROFILE_FUNC();

	CvCity* pCity;
	CvWString szBuffer;
	int iI;

	FAssert(isBeingWorked() && isOwned());

	if (getImprovementType() != NO_IMPROVEMENT)
	{
		if (getBonusType() == NO_BONUS)
		{
			FAssertMsg((0 < GC.getNumBonusInfos()), "GC.getNumBonusInfos() is not greater than zero but an array is being allocated in CvPlot::doImprovement");
			for (iI = 0; iI < GC.getNumBonusInfos(); ++iI)
			{
// ----- World Piece Begin -----
/*Seven05: Here we're going to make a slight change to the chance of discovering bonuses on
improved plots.  What we want is to limit food resources so they will only appear if the civ
already has that resource. And 'fix' it so resources can only be discovered on the appropriate
terrain.*/
				if (GET_TEAM(getTeam()).isHasTech((TechTypes)(GC.getBonusInfo((BonusTypes) iI).getTechReveal())) &&
					GC.getBonusInfo((BonusTypes) iI).isTerrain((unsigned int) getTerrainType()))
				{ // If this bonus type is allowed on this terrain and we have the tech needed
					if (GC.getImprovementInfo(getImprovementType()).getImprovementBonusDiscoverRand(iI) > 0)
					{ // If this bonus can be 'discovered'
						if (GC.getGameINLINE().getSorenRandNum(GC.getImprovementInfo(getImprovementType()).getImprovementBonusDiscoverRand(iI), "Bonus Discovery") == 0)
						{
							if(GC.getBonusInfo((BonusTypes) iI).getHealth() > 0)
							{ // This next check should only happen with food resources
								if(GET_PLAYER(getOwner()).hasBonus((BonusTypes) iI))
								{	// If we have this one already
									setBonusType((BonusTypes) iI);
								}
								else
								{ // We don't have this one yet, bail out
									break;
								}
							}
							else
							{ // Non-health bonuses can be discovered without having one already
								setBonusType((BonusTypes)iI);
							}
// ----- World Piece End -----

							pCity = GC.getMapINLINE().findCity(getX_INLINE(), getY_INLINE(), getOwnerINLINE(), NO_TEAM, false);

							if (pCity != NULL)
							{
								szBuffer = gDLL->getText("TXT_KEY_MISC_DISCOVERED_NEW_RESOURCE", GC.getBonusInfo((BonusTypes) iI).getTextKeyWide(), pCity->getNameKey());
								gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_DISCOVERBONUS", MESSAGE_TYPE_MINOR_EVENT, GC.getBonusInfo((BonusTypes) iI).getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_WHITE"), getX_INLINE(), getY_INLINE(), true, true);
							}

							break;
						}
					}
				}
			}
		}
	}

	doImprovementUpgrade();
}

What it does:
The biggest change is the this code limits resource discovery to terrain types allowed for that bonus. In other words, if Iron is set to only appear on deserts you will never discover iron in a mine that you build on a grassland hill. The second change is that it allows the discovery of health resources (if enabled in the XML) but limits them so that you can only discover a resource that you already have access to, including from trades. Neither change will alter the way the XML data is handled or allow players to discover a resource that isn't allowed in your XML files.

If you look in CIV4ImprovementInfos.xml at the mine improvement you will see several lines like:
Code:
				<BonusTypeStruct>
					<BonusType>BONUS_ALUMINUM</BonusType>
					<bBonusMakesValid>1</bBonusMakesValid>
					<bBonusTrade>1</bBonusTrade>
					<iDiscoverRand>10000</iDiscoverRand>
					<YieldChanges>
						<iYieldChange>0</iYieldChange>
						<iYieldChange>1</iYieldChange>
						<iYieldChange>1</iYieldChange>
					</YieldChanges>
				</BonusTypeStruct>
The iDiscoverRand value tells the game that a mine with no bonus has a chance (1 in iDiscoverRand) of discovering this bonus resource. The allowable terrain types are defined in CIV4BonusInfos.xml and used for resource placement during map generation.

Use:
In my mod I use this code to allow corn & wheat to spread to other farms in your civ, but only if you already have it. The only XML change required was altering the iDiscoverRand value for corn and wheat in the farm. It can easily be adapted (with no code changes) to do the same with animals provided you allow pastures to be built on a tile that doesn't already have the animal bonus.
 
Here's another, only slightly more complicated :)

In CvUnit.cpp insert this code just before the 'return true;' line at the end of the CvUnit::canMoveInto() method:
Code:
// ----- World Piece Begin -----
/* Tile stack size limit.  Base is 8 units per tile, more in cities and we don't count
air units, units loaded on ships or ships in cities.*/

	int iMaxUnits = 8;
	int iUnitCount = 0;
	int iPlotUnits = pPlot->getNumUnits();
	bool bBoardingShip = pPlot->isWater();

	DomainTypes eDomain = NO_DOMAIN;
	DomainTypes eUnitDomain = NO_DOMAIN;
	UnitCombatTypes eCombat = NO_UNITCOMBAT;
	UnitCombatTypes eUnitCombat = NO_UNITCOMBAT;

	eDomain = this->getDomainType();
	eCombat = this->getUnitCombatType();

	if( eDomain != DOMAIN_LAND && bBoardingShip )
		bBoardingShip = false;

	if( pPlot->isCity() )
		iMaxUnits += 4;

	if( (eDomain != DOMAIN_AIR && !bBoardingShip) || eCombat != NO_UNITCOMBAT )
	{ // Skip check for air units, special units and land units boarding ships
		for ( int iI = 0; iI < iPlotUnits; iI++ )
		{
			CvUnit* pUnit = pPlot->getUnitByIndex(iI);
			eUnitDomain = pUnit->getDomainType();
			eUnitCombat = pUnit->getUnitCombatType();
			if( !pUnit->isCargo() && eUnitDomain != DOMAIN_AIR &&
				eUnitCombat != NO_UNITCOMBAT && !pUnit->isEnemy(getTeam()) )
			{ // Don't count cargo, special, air or enemy units in the target plot
				if( pPlot->isCity() )
				{ // If the target plot is a city, ignore naval units
					if( eUnitDomain != DOMAIN_SEA && eDomain != DOMAIN_SEA )
						iUnitCount++;
				}
				else
					iUnitCount++;
			}
		}
	}
	if( iUnitCount >= iMaxUnits && eCombat !=NO_UNITCOMBAT )
		return false;

// ----- World Piece End -----

What it does:
This code enforces a basic limit to the number of units in a tile. I tried to keep the rules simple so it allows 8 units in non-city tiles, 16 in cities and 'special' units such as settlers, workers, missionaries, executives, spies, great people and missiles do not count towards the limit. Also, ships in cities don't count towards the limit nor do units loaded into the cargo of ships. Since this code is at the end of the method it is only exectued if a unit could normally enter the tile (and after any Python code as well) so it isn't neccessary to check for things like impassible terrain. The one exception is a land unit boarding a ship, for that we check if the current plot is water and the current unit is a land unit (DOMAIN_LAND). The 'special' units are any units whose unit combat is defined in the XML files as 'NONE' as this was the easiest way to encompass all of them. The AI has a limited understanding of the limits, it knows that it can't move a unit into the tile so it choses another tile to move to. The main reason for the cases of unlimited units, such as ships in cities, is to keep the AI from doing something stupid like filling a city with one defender and sixteen ships.

Use:
I use this in my mod to.. well, restrict the size of stacks :) It adds another layer to strategy when you're planning your attacks and holding choke-points.
 
My first submission for a code sample

In Globals.cpp add this function.:
Code:
// ----- Darque Code Begin -----
int CvGlobals::getActionIdFromString(const TCHAR* szType)
{
    for (int i=0; i < getNumActionInfos(); i++)
    {
        CvActionInfo &pAction = getActionInfo(i);
        TCHAR* curType = (TCHAR*)pAction.getType();
        if (strcmp(szType, curType)==0)
        {
            return i;
        }
    }
    //Log error message that the action is not found
    CvString szError;
    szError.Format("action type %s not found", szType);
    gDLL->logMsg("xml.log", szError);
    return -1;
}
// ----- Darque Code End -----

In Globals.h add this declaration:
Code:
	DllExport int getActionIdFromString(const TCHAR* szType); // Darque Code

In CyGlobalContextInterface4.cpp add this line somewhere in the CyGlobalContextPythonInterface4 function:
Code:
        .def("getActionIdFromString", &CyGlobalContext::getActionIdFromString, "int () - Returns action identifier") //Darque Code

What it does:
This function will allow the modder to enter a Type string (Such as "COMMAND_LOAD" or "MISSION_FORTIFY") and be returned the action identifier needed to properly create a button (Such as with CyGInterfaceScreen.appendMultiListButton() ) and using WidgetTypes.WIDGET_ACTION_ACTION

Use:
In my mod, I have updated the SDK so that I can add CommandInfo objects in CIV4CommandInfos.xml instead of the commands using a hard coded enumeration. I use this function to find the action identifier assigned to my new CommandInfo objects.
 
Top Bottom