Quick Modding Questions Thread

I little confussed, I don't know how to fix it, it's like the game says there can't be unique civilization units in the game
The proper civ needs to own the unique unit. Then there should be no problem. Though I can of course give any unique unit to any civ in WorldBuilder in BtS. Maybe that'll cause the assertion in CvTeam::canTrain to fail, but it's still not a problem. I suspect that it's also not a asign of a real problem in your mod. Though I don't know if the M41 should ever be owned by a civ other than America; perhaps it is a sign of something not working as intended. And, to be clear, without examing the call stack, i.e. where the canTrain call comes from, it's still unclear if the M41 already exists or if someone (other than America) is considering to produce one.

The assertions in CvPlot looked more worrisome – but aren't really helpful without the call stack.
 
Last edited:
regarding this cvplot error I found something
1742430896110.png
 
Hello all,
.
I have created art files for the leaderheads using historical still-pictures. These load up for me just fine.
.
I place the Leaderheads folder in Assets > Art > [paste entire leaderheads folder], and the art defines file in xml > art > [paste here] ... but when I send this file to my friend who I play against online and he pasted them in the same folders, the still-picture leaderheads do not show [the vanilla game leaderheads show]. Any idea why this occurs?
.
Edit: I even sent him the duplicate mod I'm using with identical folders as I'm using and it doesn't load the new leaderhead art ... any guesses as to why?
I'd have him check a few things:
Are the files still in the same format after he received them?
Are the files named the same?
Does CIV4ArtDefines_Leaderhead.xml point to the right file?
Does CIV4LeaderheadInfos.xml point to the right leaderhead art?
Were any other files in the leaderhead's art folder modified, and do they match your mod?
He can also try to view the files in NIFskope, Blender, and GIMP to verify them.
 
Hi Sturmgewher88mm,
.
Thanks for the reply. I'm not sure why, but the only way we can get it to work is to have him copy over the original files in BTS. And then also in KMod [the mod we are using to play multiplayer online]. I can't explain why he needs to also copy over the BTS files bc KMods should be the most recent called when playing thr mod [and mine are only in KMod]. Any thoughts regarding this?
 
@Darkator: I've looked for call locations of isTradeNetworkConnected. Likely candidate:
Spoiler :
C++:
CvUnitAI::AI_airBombPlots()
{
	// ...
	for (iDX = -(iSearchRange); iDX <= iSearchRange; iDX++)
	{
		for (iDY = -(iSearchRange); iDY <= iSearchRange; iDY++)
		{
			pLoopPlot	= plotXY(getX_INLINE(), getY_INLINE(), iDX, iDY);
			// ...
/*************************************************************************************************/
/**AI route bombing value                     12/01/10                            NotSoGood      */
/**                                                                                              */
/**                                                                                              */
/*************************************************************************************************/
			// ...
							//Loops the nearby plots to calculate if you should bomb the plot
							for (int iI = 0; iI < NUM_DIRECTION_TYPES; iI++)
							{
								CvPlot* pCheckPlot = plotDirection(pLoopPlot->getX_INLINE(), pLoopPlot->getY_INLINE(), (DirectionTypes)iI);
								
								if (pCheckPlot != NULL && !pCheckPlot->isWater())
								{
									if(pCheckPlot->isTradeNetworkConnected(pCheckPlot, pCheckPlot->getTeam()))
You could verify through the Call Stack window (Alt+7) that the isTradeNetworkConnected call indeed comes from there. This sure looks incorrect because pCheckPlot is not guaranteed to be owned, but isTradeNetworkConnected works only for owned plots. So this needs a pCheckPlot->isOwned() check. Can do this already in the first conditional:
if (pCheckPlot != NULL && !pCheckPlot->isWater() && pCheckPlot->isOwned())
This will hopefully also address the other failed assertion you had, in CvPlot::isRevealed. isTradeNetworkConnected calls isRevealed, so that's probably where the invalid eTeam argument came from.
 
@<Nexus>: That addMessage uses variables iPlayerX, FeatureInfo and pPlot. Perhaps those aren't in place where you've copy-pasted it? You do have Python error popups enabled or check PythonErr.log? Because, otherwise, "ruining" placeWonderBuilding could break the whole module and you might not notice. I'd just put a print statement before the popup code and perhaps after, too, to make sure that your code is reached. Here's how CvWonderMovieScreen.py processes the natural wonders: Spoiler : ## Natural Wonders - Start elif self.iMovieType == 3: sType = gc.getFeatureInfo(iMovieItem).getType() szArtDef = CyArtFileMgr().getMovieArtInfo("ART_DEF_MOVIE_" + sType[sType.find("_PLATY_") +7:]) if szArtDef: self.szMovieFile = szArtDef.getPath() ## Natural Wonders - End So Data1 needs to be a feature ID and Data3 needs to be 3. I guess you're aiming at this bit of (original) code instead: elif self.iMovieType == MOVIE_SCREEN_WONDER: self.szMovieFile = gc.getBuildingInfo(iMovieItem).getMovie()MOVIE_SCREEN_WONDER is indeed 0, so I guess Data3=0 and a building ID as Data1 should work. But is the movie stored at the building too in XML? Well, what happens in CvWonderMovieScreen.py – if anything – also can be traced with print statements.
Okay, with python error on I get this:
Spoiler :

1742486068782.png


In Platy's mod thereis no CvEventInterface file, so... Can it be a BtS bug? I'm confused...

The next lines:


1742486428577.png
1742486484687.png




 

Attachments

  • 1742486428619.png
    1742486428619.png
    41.3 KB · Views: 6
  • 1742486947896.png
    1742486947896.png
    43.1 KB · Views: 5
Last edited:
The bottommost line number is where the exception occurs, placeWonderBuilding. The lines above that one show the call sequence leading to placeWonderBuilding. That's event handling code from BtS. But the error is in your code: iPlayerX was copied from a different context, it's not defined in placeWonderBuilding. You already had that part right in your earlier post with pCity.getOwner() as the player ID.
 
I'm not sure why, but the only way we can get it to work is to have him copy over the original files in BTS. And then also in KMod [the mod we are using to play multiplayer online]. I can't explain why he needs to also copy over the BTS files bc KMods should be the most recent called when playing thr mod [and mine are only in KMod]. Any thoughts regarding this?
The only thing I can think of is something in CustomAssets overriding the changes, but then, that would also supersede changes to vanilla directories. :confused:
 
I have a unit AI question. In my mod, the American unique unit is the Pioneer, a Settler replacement that can defend itself (but not attack). Its default unit AI is UNITAI_SETTLE.

I have encountered a bug where sometimes this type of unit would get "stuck" and the AI would never move it or do anything with it. Further investigation showed that CvUnitAI::AI_update() was never called for this unit, and that the selection group that its getGroupID() value points to actually contains zero units and is located at (-MAX_INT, -MAX_INT).

My guess is that some AI logic caused this unit to leave its original selection group without ever receiving a new one. Since unit moves occur by iterating selection groups, effectively that means that the AI "forgot" about this unit. I would assume that being both a settler unit (either by AI type or the ability to found cities) while also being a combat unit is some kind of special case that isn't properly handled in the unit AI code that can produce this state. However, I have no idea where to look for it. Does anyone have an idea or a direction to point me to? It's difficult to diagnose because usually I only notice the problem when the corrupt state has already occurred.
 
FAssert(getGroup() == NULL || getGroup()->getNumUnits() > 0);
at the end of CvUnit::joinGroup would be my quick attempt. I don't think CvUnit::m_iGroupID gets changed elsewhere, and the unit being associated with the group but not vice versa sounds like it should always be an error. The call then probably from CvUnitAI::AI_group. Though I guess it could also be that operations on CvSelectionGroup (add/remove/merge/split) somehow leave the group empty without reassigning the Pioneer. Perhaps enough to check for that once per turn (and then to reload with conditional breakpoints in CvSelectionGroup). Maybe just
C++:
#ifdef FASSERT_ENABLE
int iIter;
for (CvUnit const* u = firstUnit(&iIter); u != NULL; u = nextUnit(&iIter))
	FAssert(u->getGroup() == NULL || u->getGroup()->getNumUnits() > 0);
#endif
in CvPlayerAI::AI_doTurnUnitsPre/Post.
 
Thanks, I'll try. From my attempts to recreate the issue it is not too common, unfortunately. Guess I have some autoplay to run in the background.
 
The bottommost line number is where the exception occurs, placeWonderBuilding. The lines above that one show the call sequence leading to placeWonderBuilding. That's event handling code from BtS. But the error is in your code: iPlayerX was copied from a different context, it's not defined in placeWonderBuilding. You already had that part right in your earlier post with pCity.getOwner() as the player ID.
Okay, I try to follow your guidance but I'm a total noob...
Here's the code I tried:
Python:
    def placeWonderBuilding(self, pCity):
        for i in xrange(21):
            pPlot = pCity.getCityIndexPlot(i)
            iFeature = pPlot.getFeatureType()
            if iFeature == -1:
                continue
            
            sType = gc.getFeatureInfo(iFeature).getType()
            if sType.find("FEATURE_PLATY_") == -1:
                continue
            
            sNature = sType[sType.find("_PLATY_") + 7:]
            sBuildingType = "BUILDING_" + sNature
            iBuilding = gc.getInfoTypeForString(sBuildingType)
            
            if iBuilding == -1:
                continue
            if self.checkWonderBuilt(iBuilding):
                continue
            
            # Place the wonder in the city
            pCity.setNumRealBuilding(iBuilding, 1)
            
            # Trigger the wonder movie popup
            popupInfo = CyPopupInfo()
            popupInfo.setButtonPopupType(ButtonPopupTypes.BUTTONPOPUP_PYTHON_SCREEN)
                    popupInfo.setData1(iFeature)
                    popupInfo.setData3(3)
            popupInfo.setText(u"showWonderMovie")
            popupInfo.addPopup(pCity.getOwner())
            CyInterface().addMessage(iPlayerX,True,10,CyTranslator().getText("TXT_KEY_WONDERDISCOVERED_YOU",(FeatureInfo.getDescription(),)),'',0, FeatureInfo.getButton(),ColorTypes(11),pPlot.getX(),pPlot.getY(), True,True)
        return
This time the game started with a dozen or so python errors and building a city mot just didn't launch the movie but there wasn't even a building placed :confused:

I'm attaching the python folder.
My mistake may be obvious but I'm a noob after 3 long night-shifts, so I'm clueless. If you you can look into the files, I greatly appreciate it. If not that's okay as well :) I'll continue experimenting next week :sleep:
 

Attachments

Just looking at the code, there is still iPlayerX in the addMessage call. FeatureInfo also isn't defined here. Untested attempt at a correction:
Spoiler :
Python:
			# Trigger the wonder movie popup
			popupInfo = CyPopupInfo()
			popupInfo.setButtonPopupType(ButtonPopupTypes.BUTTONPOPUP_PYTHON_SCREEN)
			popupInfo.setData1(iFeature)
			popupInfo.setData3(3)
			popupInfo.setText(u"showWonderMovie")
			popupInfo.addPopup(pCity.getOwner())
			FeatureInfo = gc.getFeatureInfo(iFeature)
			CyInterface().addMessage(
					pCity.getOwner(), # recipient
					True, 10, # force immediate delivery, display duration
					CyTranslator().getText("TXT_KEY_WONDERDISCOVERED_YOU", (FeatureInfo.getDescription(),)),
					'', 0, FeatureInfo.getButton(), ColorTypes(11), # sound, indicator button and color
					pPlot.getX(), pPlot.getY(), # coordinates
					True, True) # offscreen/ onscreen arrows
Could still have other syntax errors (PythonErr.log would say where and what) or might have no apparent effect, in which case either placeWonderBuilding isn't called or the popup isn't processed as intended; either way, print statements (in placeWonderBuilding or in CvWonderBuildingScreen.py) should shed more light.
 
Something of a long shot, but hoping someone may have random insight.

I've a situation where a python event should change the Landmark on a tile, effectively doing:
Code:
CyEngine().removeLandmark(pPlot)
CyEngine().addLandmark(pPlot,szLandmarkText)
Unfortunately, it seems as if the second call fails if the prior has been called on that tile "recently", i.e. before the player has any input. If it's attached to the result of a player's action, the first time the player makes the selection it will only remove the landmark, and if they select it again the landmark will appear, despite the entire block executing both times.

Any thoughts on why or how to mitigate? And yes, simply doing add/remove/add/remove in the same block results in no change as if it were only one set of add/remove >.<
 
Perhaps a job for deferCall in BugUtil.py. Perplexity.ai believes that addLandmark could be passed to deferCall in this manner:
deferCall(lambda: CyEngine().addLandmark(pPlot, szLandmarkText), delay=0.0)
 
Or a workaround of defining iX and iY from pPlot before you use removeLandmark and then "reconstituting" pPlot from it after you run the command (so basically you save plot's X and Y coordinates as integers with pPlot.getX() and pPlot.getY(), run the command, and then redefine pPlot with GetPlotXY(iX,iY) ). In my experience, plot data sometimes gets inexplicably discarded.
 
Hi all, hoping for a little help with functions in python
For context this is in the thread 'Adding My Planet MOD to Final Frontier Plus'
I'm trying to program a quick getPlanetClass() in class CvPlanet to save me duplicating code in other functions. Its not critical to my coding but would be very useful, post is below:

So looking at Ai now
the code lists food production and commerce, i'll keep this structure but add a function at the start to get the planet class then add the appropriate buildings to each part. The code will be similar to what I've put in for restricting buildings to planet classes. Some buildings have more than one function and may be listed more than once.
I'm thinking about adding trait functions to the code - eg brotherhood builds the military complexes

I'm trying to create a function definition to quickly get the planet class, rather than duplicating the code each time I need it, but I'm having trouble adding my own def getPlanetClass(self): function in class CvPlanet: see below. The code doesn't like 'if self.bDisabled' any ideas where I'm going wrong?
Spoiler getPlanetClass(self) :

getPlanetClass(self) :
def getPlanetClass(self):
pPlanet = getPlanet(pSystem.getBuildingPlanetRing()
# Gray D is Class 1; Brown L is Class2; Ice P is Class 3; Lava G is Class 4; Toxic Y N is Class 5; Mars H K is Class 6
# Home M is Class 7; Gia Mg is Class 8; Swamp Ms is Class 9; JST are just the gas giant size variants

if self.bDisabled():
iPlanetClass = -1
else:
if (pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray1 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray2 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray3 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray4 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray5 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray6 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray7 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray8 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray9 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray10 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray11 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray12 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray13 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray14 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray15 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGray16 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeJovian1 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeJovian2 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeSaturn1 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeBlue or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeNeptune1):
iPlanetClass = 1
if (pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeBrown1 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeBrown2 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeBrown3 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeBrown4 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeJovian3 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeSaturn2 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeNeptune2 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeUranian2):
iPlanetClass = 2
if (pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeIce1 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeIce2 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeIce3 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeIce4 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeIce5 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeIce6 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeIce7 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeIce8 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeWhite or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeSaturn3 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeUranian3 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeUranian4 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeNeptune3):
iPlanetClass = 3
if (pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeRed or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeLava1 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeLava2 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeLava3 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeLava4 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeJovian4 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeNeptune4):
iPlanetClass = 4
if (pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeToxic1 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeToxic2 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeToxic3 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeToxic4 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeToxic5 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeToxic6 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeToxic7 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeToxic8 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeUranian1 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeYellow):
iPlanetClass = 5
if (pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeMars1 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeMars2 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeMars3 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeMars4 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeDesert1 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeDesert2 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeDesert3 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeDesert4 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeOrange or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeSaturn4):
iPlanetClass = 6
if (pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeHome1 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeHome2 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeHome3 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeHome4 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGreen):
iPlanetClass = 7
if (pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGia1 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGia2 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGia3 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeGia4):
iPlanetClass = 8
if (pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeSwamp1 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeSwamp2 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeSwamp3 or pPlanet.getPlanetType() == CvSolarSystem.iPlanetTypeSwamp4):
iPlanetClass = 9
return iPlanetClass
 
Thank y'all for the help!
In my experience, plot data sometimes gets inexplicably discarded.
A good idea and something I'll keep in mind for the future, but wasn't the issue here. The full path of the current code was actually two separate calls to the python from CvPlot, with the 'add landmark' second call also doing things after that on the plot that did function properly, which was a good part of my substantial confusion lol.
Perhaps a job for deferCall in BugUtil.py.
After a bit of work, this did end up doing the trick, thank you! Had to pull in that function from bugutil, and then figure out why onGameUpdate wasn't functioning; turns out Kael stopped the gameUpdate call from running in FfH singleplayer back in '08, and nobody's needed it since.

Incidentally, roughly how do you set up a LLM to work with this codebase? Sorting through labyrinthine ancient code is something that it seems it would actually be good at, but not something I've looked into at all.
The code doesn't like 'if self.bDisabled' any ideas where I'm going wrong
In this case ( if self.bDisabled(): iPlanetClass = -1 ) it's looking for if the class is disabled, for... some reason. Would you want to have this function disabled for some reason? If not, I'm guessing you can just remove that check.
https://docs.python.org/2.6/tutorial/classes.html may also be of use if you want more detail on how classes work in python 2ish.
 
Back
Top Bottom