Creating new unit after a unit is killed in combat

ls612

Deity
Moderator
Joined
Mar 10, 2008
Messages
8,288
Location
America
I'm working on a slavery mechanic for my mod, and part of the way it will work is that when a unit is killed by a civ which has the prerequisites for taking slaves there is a chance that that civ will receive a Slave unit in the tile the old unit was in. How would I go about implementing this in the DLL? I assume that it would involve adding code for creating a new unit to ResolveMeleeCombat() and ResolveCityMeleeCombat(), but am not quite familiar enough with the DLL to know exactly what to do.
 
I'm working on a slavery mechanic for my mod, and part of the way it will work is that when a unit is killed by a civ which has the prerequisites for taking slaves there is a chance that that civ will receive a Slave unit in the tile the old unit was in. How would I go about implementing this in the DLL? I assume that it would involve adding code for creating a new unit to ResolveMeleeCombat() and ResolveCityMeleeCombat(), but am not quite familiar enough with the DLL to know exactly what to do.

Yay DLL question! :goodjob:

May I recommend the CvUnit::kill(bool bDelay, PlayerTypes ePlayer) function in CvUnit.cpp?

I would use the createCaptureUnit() function as a guide, follow their instructions carefully (you have to create the new unit in the old unit's plot without referencing the unit, because it's deleted at that point).

edit: essentially what they do is create a struct CvUnitCaptureDefinition, which stores all the information necessary to create the new unit at the plot...you'll probably want to just modify CvUnit::getCaptureDefinition() as it makes little sense to create your own.
 
Thanks. I have another question about this though. How would I best go about setting the new capture unit to be the Slave unit. The only way I can think of is to create a function to find the first unit that has IsSlave as true in the database and #defining that as a slave, but that is horribly hardcoded and reduces expandability in the future (if I want for instance multiple slave types). Is there a better way to do it?
 
Thanks. I have another question about this though. How would I best go about setting the new capture unit to be the Slave unit. The only way I can think of is to create a function to find the first unit that has IsSlave as true in the database and #defining that as a slave, but that is horribly hardcoded and reduces expandability in the future (if I want for instance multiple slave types). Is there a better way to do it?

Yes! Create a new XML column in the Units table using SQL. Observe how I achieve this with the Era table and duplicate with the Units table.

Code:
ALTER TABLE Eras			ADD		VassalageEnabled			boolean;

---------------------------------------------------------------------------------------------
-- Eras
UPDATE	Eras	SET	'VassalageEnabled' = 1	WHERE	Type = 'ERA_MEDIEVAL';

Now, you must locate where they read from the XML files in the DLL. For Units that is in UnitClasses.h/.cpp.

For this example I use the Range tag.

1. Create a new CvUnitEntry function: int GetRange() const;
2. Create a new CvUnitEntry member variable: int m_iRange;
3. Define GetRange() to return m_iRange
4. Be sure to include m_iRange in the constructor
5. Finally, in CvUnitEntry::CacheResults you include the line m_iRange = kResults.GetInt("Range"); which reads the data from the Range tag.

Yeah, I used two different examples. But this was just to show you how this is done.

Now I will do real code examples:

Code:
-- SQL File
ALTER TABLE Units			ADD		Slave			boolean;
UPDATE	Units	SET	'Slave' = 1	WHERE	Type = 'UNIT_SLAVE';

//------------------------------
// UnitClasses.h
bool IsSlave() const;

bool m_bSlave;

// UnitClasses.cpp
bool CvUnitEntry::IsSlave() const
{
	return m_bSlave;
}

// CvUnitEntry::CacheResults
m_bSlave = kResults.GetBool("m_bSlave");

// ------------------------------------------

The most complicated part is understanding that CvUnit.h and CvUnitClasses.h are two different things. Unit definition information is loaded in CvUnitClasses, specific instances of units are used in CvUnit.

Now I will return to the Range() example for completeness.

In CvUnit.cpp they create another GetRange() function, which return the unit's class get range like follows:

Code:
//	--------------------------------------------------------------------------------
int CvUnit::GetRange() const
{
	VALIDATE_OBJECT
	return (m_pUnitInfo->GetRange() + m_iExtraRange);
}

Hopefully this was helpful and you could take it from there.
 
I know how to add columns to the SQL and make the DLL backend for them (I've already done that for technologies). The problem is that the CaptureDef struct wants a UnitType and not a UnitClass object for the unit that you get afterwards, and I don't remember how to convert between the two (and it isn't done like the Civ 4 DLL, I tried that).

Here is the code I have so far in CvUnit::getCaptureDefinition()

Code:
	/*kCaptureDef.eCaptureUnitType = NO_UNIT;*///Old Code from BNW	
// New Horizons: New Code Begin
	if (GET_TEAM(GET_PLAYER(kCaptureDef.eCapturingPlayer).getTeam()).HasTechForSlavery() && !isBarbarian())
	{
		//if we can do slavery then we might get a slave
		//This chance is constant for now
		if (GC.getGame().getJonRandNum(100, "Slave Seed") <= 40)
		{
			[B]kCaptureDef.eCaptureUnitType = [/B];
			SetCapturedAsIs(false);
		}
	}
	else
	{
		kCaptureDef.eCaptureUnitType = NO_UNIT;
	}
	//New Horizons: End

The bold part is causing me problems (I don't know what to put there).
 
I know how to add columns to the SQL and make the DLL backend for them (I've already done that for technologies). The problem is that the CaptureDef struct wants a UnitType and not a UnitClass object for the unit that you get afterwards, and I don't remember how to convert between the two (and it isn't done like the Civ 4 DLL, I tried that).

Here is the code I have so far in CvUnit::getCaptureDefinition()

Code:
	/*kCaptureDef.eCaptureUnitType = NO_UNIT;*///Old Code from BNW	
// New Horizons: New Code Begin
	if (GET_TEAM(GET_PLAYER(kCaptureDef.eCapturingPlayer).getTeam()).HasTechForSlavery() && !isBarbarian())
	{
		//if we can do slavery then we might get a slave
		//This chance is constant for now
		if (GC.getGame().getJonRandNum(100, "Slave Seed") <= 40)
		{
			[B]kCaptureDef.eCaptureUnitType = [/B];
			SetCapturedAsIs(false);
		}
	}
	else
	{
		kCaptureDef.eCaptureUnitType = NO_UNIT;
	}
	//New Horizons: End

The bold part is causing me problems (I don't know what to put there).

Try this as an example to help you out?
Code:
// slewis
	// If we're Venice
	if (GetPlayerTraits()->IsNoAnnexing())
	{
		// if we're trying to drop a settler
		if((eUnitAI == UNITAI_SETTLE) || (pkUnitInfo->GetDefaultUnitAIType() == UNITAI_SETTLE))
		{
			// if we already have a settler
			if(GetNumUnitsWithUnitAI(UNITAI_SETTLE) >= 1)
			{
				// drop a merchant of venice instead
				// find the eUnit replacement that's the merchant of venice
				for(int iI = 0; iI < GC.getNumUnitClassInfos(); iI++)
				{
					const UnitClassTypes eUnitClass = static_cast<UnitClassTypes>(iI);
					CvUnitClassInfo* pkUnitClassInfo = GC.getUnitClassInfo(eUnitClass);
					if(pkUnitClassInfo)
					{
						[B]const UnitTypes eLocalUnit = (UnitTypes) getCivilizationInfo().getCivilizationUnits(eUnitClass);[/B]
						if (eLocalUnit != NO_UNIT)
						{
							CvUnitEntry* pUnitEntry = GC.getUnitInfo(eLocalUnit);
							if (pUnitEntry->IsCanBuyCityState())
							{
								// replacing the parameters
								eUnit = eLocalUnit;
								eUnitAI = (UnitAITypes)pkUnitInfo->GetDefaultUnitAIType();
								break;
							}
						}
					}
				}
			}
		}	
	}

edit: the part I bolded is the one you'd have to really change. You have to find a function to return the correct unit for that class...that may help you out though. You should seek other examples out.
 
Try this as an example to help you out?
Code:
// slewis
	// If we're Venice
	if (GetPlayerTraits()->IsNoAnnexing())
	{
		// if we're trying to drop a settler
		if((eUnitAI == UNITAI_SETTLE) || (pkUnitInfo->GetDefaultUnitAIType() == UNITAI_SETTLE))
		{
			// if we already have a settler
			if(GetNumUnitsWithUnitAI(UNITAI_SETTLE) >= 1)
			{
				// drop a merchant of venice instead
				// find the eUnit replacement that's the merchant of venice
				for(int iI = 0; iI < GC.getNumUnitClassInfos(); iI++)
				{
					const UnitClassTypes eUnitClass = static_cast<UnitClassTypes>(iI);
					CvUnitClassInfo* pkUnitClassInfo = GC.getUnitClassInfo(eUnitClass);
					if(pkUnitClassInfo)
					{
						[B]const UnitTypes eLocalUnit = (UnitTypes) getCivilizationInfo().getCivilizationUnits(eUnitClass);[/B]
						if (eLocalUnit != NO_UNIT)
						{
							CvUnitEntry* pUnitEntry = GC.getUnitInfo(eLocalUnit);
							if (pUnitEntry->IsCanBuyCityState())
							{
								// replacing the parameters
								eUnit = eLocalUnit;
								eUnitAI = (UnitAITypes)pkUnitInfo->GetDefaultUnitAIType();
								break;
							}
						}
					}
				}
			}
		}	
	}

edit: the part I bolded is the one you'd have to really change. You have to find a function to return the correct unit for that class...that may help you out though. You should seek other examples out.

Thanks a lot, that helped me (the unit info structure has changed considerably since civ 4).
 
Back
Top Bottom