• Civilization 7 has been announced. For more info please check the forum here .

Quick Modding Questions Thread

Not familiar with it. Terrain features are a bit of an odd thing to count per player. A text search of the DLL codebase shows that it's used only for AI purposes, namely for evaluating Environmentalism (Forest Preserves) and techs that unlock chopping. The code of the function itself can be located by just searching CvPlayer.cpp: CvPlayer.cpp#L3306
It counts the occurrences of eFeature on tiles around the player's cities. The city tiles themselves are included (although cities can't coexist with features anyway) and the maximal working radius (big fat cross) is checked, regardless of the current tile ownership. Imo should've been implemented explicitly as an AI-only function (i.e. a member of CvPlayerAI, not CvPlayer), not exposed to Python (most AI functions aren't) and should ideally skip tiles owned by a rival and unlikely to ever flip. (In contrast, e.g. countNumBuildings is only used for game rules - it matters for escalating Advanced Start costs.)
 
I would be weary of removing anything from python. You newer know when it comes in handy.
 
Right, I wouldn't want to mess with the DLL-Python interface at this point either. Never know which existing Python mod component that might break.
 
How can I make a +100% bonus to a player's commerce type on a trigger? For example, +100% to the culture of each city. i tried to use changeCommercePercent but it didn't work
 
That's Free Speech, let's see ... That XML tag is named CommerceModifiers, which corresponds to CvCivicInfo::getCommerceModifier, and that function gets called by CvPlayer:: processCivics, which feeds the result into CvPlayer::changeCommerceRateModifier. Which ... is not exposed to Python. So I guess one would have to use a building. There seems to be an unused tag GlobalCommerceModifiers that gets fed into CvPlayer::changeCommerceRateModifier. I don't think there's an equivalent tag for techs. changeCommercePercent only affects the positions of the commerce sliders.
 
If I wanted to hide one of the map sizes in the world picker screen (so basically stop people from picking it for random maps, while keeping it around to set it manually in scenarios), what would be the easiest way to do that? I don't think World Picker is a Python screen...
 
The game setup screens are in the EXE, apparently. My best bet would be to detect through the DLL when the Wolrd Size screen is opened and then to return a smaller number of WorldInfos to the EXE until that screen has been exited. Calls to CvGlobals::getNumWorldPickerInfos should indicate when "Select A Map" gets accessed. And there should be some way to tell when the player returns to the opening menu or proceeds to "Select A Civilization". The hiding should be possible through changes to getNumWorldInfos and getWorldInfo (also CvGlobals). To avoid slowing those functions down when used DLL-internally, the calls from the EXE can be redirected to differently named functions by way of a ".def" file. Though I doubt that the DLL iterates over all world sizes a lot, so this is probably quite unnecessary.
This hack should not require a lot of code, but it'll be laborious to figure out. Hopefully there is some simpler way. :undecide: I don't see anything in Civ4WorldInfo.xml that might cause the EXE to skip a world size.
 
Call it debug or something. That way they'll think it's something left over from development that does not work.
 
my mod said this:

Zrzut ekranu z 2024-06-01 11-11-16.png
 
I'm trying to get a new owner of the city after the capture in order to modify the gold received. This works for the player, but doesn't work for the AI. Maybe there is another way?

p.s. The new owner of the city and even the movement of the unit occurs AFTER the calculation of the gold received.

Code:
    def doCityCaptureGold(self, argsList):
        "controls the gold result of capturing a city"
        
        pOldCity = argsList[0]
        
        iCaptureGold = 0       
   
        iPlayer = gc.getGame().getActivePlayer()
        pPlayer = gc.getPlayer(iPlayer)
        if pPlayer.getBuildingClassCount(gc.getInfoTypeForString("BUILDINGCLASS_PETRA")) == 1:
            return 9999
 
@Darkator: Should be possible to disable that warning through HideMinSpecWarning = 1 in My Games\Beyond the Sword\CivilizationIV.ini. I've never had that problem myself, but I've read that Civ will automatically lower the player's graphical settings when displaying that warning; so you may want to also check the options menu and restore your settings if necessary.

@Mario W: The "active" player is always the human player in singleplayer games. In multiplayer, it's the human player whose civ is controlled by the user interface, i.e. it'll be a different player in network games depending on which machine the code gets executed on. Should therefore only be used in UI-related code and will lead to out-of-sync errors in multiplayer when used incorrectly. There is, to my knowledge, no function that says which player is currently taking their turn; perhaps because, with Simultaneous Turns, there would be multiple such players.

p.s. The new owner of the city and even the movement of the unit occurs AFTER the calculation of the gold received.
Hmm, that's true. And you can't just return 0 and call CyPlayer.changeGold yourself later in cityAcquired because the DLL will show the amount returned by doCityCaptureGold in a popup. Also, by the time cityAcquired gets called, the population decrease from conquest will already have been applied, and the (BtS) capture gold formula is supposed to be based on the population prior to conquest. I guess the extra gold from the new owner's special ability could be treated as a separate source of income, i.e. you could add another e.g. 200 gold and tell the players about it in a separate on-screen message. If the extra gold is supposed to be based on the original capture gold, then you could store that amount in some global variable when doCityCaptureGold gets called and access it in cityAcquired.

Very awkward that the conquering player can't just be figured out in doCityCaptureGold. Here's part of the calling context of CvPlayer::acquireCity (which, in turn, calls doCityCaptureGold in Python), [edit:]in CvUnit::setXY:
Spoiler :
Code:
	pOldPlot = plot();
	if (pOldPlot != NULL)
	{
// Unit removed from old plot
		pOldPlot->removeUnit(this, bUpdate && !hasCargo());
[... a lot of cache updates concerning the old position of the unit ...]
// Unit's coordinates get set to the new plot
	if (pNewPlot != NULL)
	{
		m_iX = pNewPlot->getX_INLINE();
		m_iY = pNewPlot->getY_INLINE();
	}
// [...]
	if (pNewPlot != NULL)
	{
		pNewCity = pNewPlot->getPlotCity();
		if (pNewCity != NULL)
		{
			if (isEnemy(pNewCity->getTeam()) && !canCoexistWithEnemyUnit(pNewCity->getTeam()) && canFight())
			{
// Handle city conquest: effect on war weariness
				GET_TEAM(getTeam()).changeWarWeariness(pNewCity->getTeam(), *pNewPlot, GC.getDefineINT("WW_CAPTURED_CITY"));
				GET_TEAM(getTeam()).AI_changeWarSuccess(pNewCity->getTeam(), GC.getDefineINT("WAR_SUCCESS_CITY_CAPTURING"));
// Further complication: The owner of the unit does not always
// become the conquering player. If a vassal of the unit owner
// has previously owned the city, then that vassal may become
// the conquering player.
				PlayerTypes eNewOwner = GET_PLAYER(getOwnerINLINE()).pickConqueredCityOwner(*pNewCity);
				if (NO_PLAYER != eNewOwner)
				{
// acquireCity gets called, which will call doCityCaptureGold.
					GET_PLAYER(eNewOwner).acquireCity(pNewCity, true, false, true); // will delete the pointer
					pNewCity = NULL;
				}
			}
		}
// [...]
// Unit gets placed in its new plot
		pNewPlot->addUnit(this, bUpdate && !hasCargo());
[...]
// Python learns about the unit move
	CvEventReporter::getInstance().unitSetXY(pNewPlot, this);
It looks like the conquering unit's coordinates will already be updated before city acquisition gets handled. So it should be possible to identify that unit by looping through all units (CyPlayer.firstUnit, nextUnit) of all war enemies of the old city owner and checking through CyUnit.at whether they're at the city plot. The at function only checks the unit's coordinates:
Spoiler :
Code:
bool CvUnit::at(int iX, int iY) const
{
	return((getX_INLINE() == iX) && (getY_INLINE() == iY));
}
Spies would have to be excluded (maybe best through CyUnit.canCoexistWithEnemyUnit). And CvPlayer:: pickConqueredCityOwner would pretty much have to be re-implemented in Python to deal with the originally-vassal-owned special case:
Spoiler :
Code:
PlayerTypes CvPlayer::pickConqueredCityOwner(const CvCity& kCity) const
{
	PlayerTypes eBestPlayer = kCity.getLiberationPlayer(true);

	if (NO_PLAYER != eBestPlayer)
	{
		if (GET_TEAM(getTeam()).isVassal(GET_PLAYER(eBestPlayer).getTeam()))
		{
			return eBestPlayer;
		}
	}

	return getID();
}
Maybe someone can think of a simpler alternative. If multiplayer isn't relevant, then there might be at least some easier way to determine which player is currently taking their turn(?).
 
Last edited:
How can I create and use a global variable?
Are they saved after saving/loading the game?
 
Such a variable would not persist unless explicitly sent to the DLL as "script data". Here's a recent tutorial. Though, in this case, I think the cityAcquired event will necessarily follow doCityCaptureGold without any intervening creation of savegames. So I hope it would suffice to just reset the variable to some "none" value each time that onCityAcquired gets called. With the BUG event handling system, it is possible to have handlers for CvGameUtils (doCityCaptureGold) and CvEventManager (onCityAcquired) in the same module, in which case the shared variable could just be defined somewhere at the start of that module. But I assume that you're modifying CvGameUtils and CvEventManager directly or are, in any case, dealing with two separate modules. If neither of them is supposed to import the other, then I guess the variable ought to be placed in a third module that both of them will import. I have little experience with Python myself, so I'm not sure how to do this nicely. For a test, I see that both CvGameUtils and CvEventManager import CvUtil, so maybe you could just put something like iMostRecentCaptureGold = -1 near the top of that file and then refer to that variable as CvUtil.iMostRecentCaptureGold in the event handler functions. To write to the variable, it may have to be declared as "global" beforehand, e.g.
Code:
global CvUtil.iMostRecentCaptureGold
CvUtil.iMostRecentCaptureGold = iCaptureGold
Not the case in this small example that Google has found for me; though that's for Python 3, while we're dealing with 2.4, so I'm not sure.
 
my civ crashing in my mod when when civilization have contacct
Traceback (most recent call last):

File "CvDiplomacyInterface", line 23, in beginDiplomacy

File "CvDiplomacy", line 319, in setAIComment

File "CvDiplomacy", line 394, in getDiplomacyComment

RuntimeError: Access violation - no RTTI data!
ERR: Python function beginDiplomacy failed, module CvDiplomacyInterface
 
The error seems to be caused here (Python\CvDiplomacy.py):
Spoiler :
Code:
	def getDiplomacyComment (self, eComment):
		"Function to get the user String"
		debugString = "CvDiplomacy.getDiplomacyComment: %s" %(eComment,)
		eComment = int(eComment)
		if DebugLogging:
			print debugString, eComment
		
		szString = ""
		szFailString = "Error***: No string found for eComment: %s"
		
		if ( gc.getDiplomacyInfo(eComment) ):
			DiplomacyTextInfo = gc.getDiplomacyInfo(eComment)
			if ( not DiplomacyTextInfo ):
				print "%s IS AN INVALID DIPLOCOMMENT" %(eComment,)
				CvUtil.pyAssert(True, "CvDiplomacy.getDiplomacyComment: %s does not have a DiplomacyTextInfo" %(eComment,))
				return szFailString %(eComment,)
			
			szString = self.filterUserResponse(DiplomacyTextInfo)
Specifically by the line if ( gc.getDiplomacyInfo(eComment) ):
That does not look like the proper way of checking whether the diplo comment ID eComment is valid. So that would seem like an issue with the BtS error handling. But the bigger problem, likely caused by your mod, is that eComment does apparently not correspond to a CvDiplomacyInfo object in the DLL. Maybe an addition to XML\GameInfo\Civ4DiplomacyInfos.xml is needed. To narrow the problem down, I would suggest temporarily setting DebugLogging = True at the start of CvDiplomacy.py and consulting PythonDbg.log (with LoggingEnabled=1 in CivilizationIV.ini) for the last CvDiplomacy.getDiplomacyComment message before the crash. Replacing if ( gc.getDiplomacyInfo(eComment) ): with
Code:
if eComment >= gc.getNumDiplomacyInfos() or eComment < 0:
might avoid the crash, but you'll probably still see incomplete diplo text. Edit: No, if anything, needs to be the logical inverse of the above, i.e. if not (eComment...)
 
Last edited:
I admit it honestly @f1rpo
I configured my dll to my mod from dcm and another attachment
 

Attachments

  • 24549-dcm319_v2_6.7z
    1.7 MB · Views: 6
  • beyond-the-sword-sdk-develop.zip
    21.2 MB · Views: 4
  • SDK.7z
    1.6 KB · Views: 3
Last edited:
Top Bottom