Bug Thread

Danger, Will Robinson! Danger!

Do not use any of the code I posted above for the meltdown issue!
(EDIT: I have now removed the bad code from those posts.)

Things I had not noticed because I was testing the code only with new games where nobody occupied more than their home star system (which never has a planetary resource) or had more than the starting unit:

1) Systems with resources have a bug when the meltdown planet has the resource: the pPlot variable it is trying to use is not defined.

2) It is applying nuke damage to every unit the player owns, not just those in the target system. (In my new test, using a save from a game that is well under way, it literally wiped out every unit I owned since I got 3 meltdowns on the first turn due to the 50% chance of meltdown from a nutrition facility that I gave it, so every one of my units took triple nuke damage.)

I've also noticed that you can have the "disabled" planet as the current build planet and build anything you want there - although it doesn't make any sense to build some things since they won't do anything for you.

Obviously, this needs more work. Back on Thursday I though I'd just bang out a quick fix for this...
 
OK, as the saying goes, perhaps the 3rd time's the charm.

The onNukeExplosion code from before is unchanged. EDIT: that was overly optimistic. Too many versions of things. Fortunately I actually checked. So there is a new version.

The doMeltdown code is new again, to fix the bad variable and the nuking of all units instead of just the ones on that plot.

Also changed is cannotConstruct which has been adjusted to block construction of buildings that are not set to be nuke immune on planets that are disabled. (Before, for example, if you wanted more happiness you could build a sports arena on the nice glowing planet so your civilization's sports fans could get a nice deep tan while watching the games.)

So, some re-revised doMeltdown code (old style, as per the last time):
Code:
	def doMeltdown(self,argsList):
		pCity = argsList[0]
		pSystem = CvSolarSystem.getSystemAt(pCity.getX(), pCity.getY())
		iMeltPlanet = -1
		
		# check to see if there is a meltdown
		for iBuilding in range(gc.getNumBuildingInfos()):
			if pCity.getNumBuilding(iBuilding) > 0 :
				iBuildingNukeExRand = gc.getBuildingInfo(iBuilding).getNukeExplosionRand()
				if iBuildingNukeExRand != 0 :
					if CyGame().getSorenRandNum(iBuildingNukeExRand, "FFP: meltdown check in doMeltdown callback") == 0 :
						# a building has had a meltdown, check planets for buildings of this type and pick one
						lPlanets = []
						for iPlanet in range(pSystem.getNumPlanets()):
							pPlanet = pSystem.getPlanetByIndex(iPlanet)
							if pPlanet.isHasBuilding(iBuilding) :
								lPlanets.append(pPlanet)
						
						iMeltPlanet = CyGame().getSorenRandNum(len(lPlanets), "FPP: meltdown planet")
						pMeltPlanet = lPlanets[iMeltPlanet]
						
						# for the sake of simplicity, have it so that no star system will get more than one meltdown in a turn
						break # break out of building loop
		
		if iMeltPlanet == -1 :
			# no meltdown so exit now (always return True to avoid DLL's meltdown processing)
			return True
		
		pPlayer = gc.getPlayer(pCity.getOwner()) # defined now since we don't need it above
		pCityPlot = pCity.plot()
		
		# Meltdown effects (same as nuke effects, but applied to meltdown planet instead of best planet) 
		# 1) set the planet to be disabled
		# 2) remove all buildings from the now dead planet, dealing with capitol if on this planet
		# 3) if thre was a bonus on the planet, remove it from planet and plot
		# 4) chance of specified feature being distributed around system
		# 5) unit damage (possibly killed)
		# 6) population reduction
		
		# 1) disable
		pMeltPlanet.setDisabled(true)
		pMeltPlanet.setPopulation(0)
		
		# 2) remove buildings
		for iBuilding in range(gc.getNumBuildingInfos()):
			if pMeltPlanet.isHasBuilding(iBuilding) and not gc.getBuildingInfo(iBuilding).isNukeImmune():
				bRemove = True
				if (gc.getBuildingInfo(iBuilding).isCapital()):
					if (pPlayer.getNumCities () > 1):
						# The following call moves the capitol building, removing it from this city's data
						# in the DLL (which is why there is no manual setNumRealBuilding in here)
						# and adding it to the new capital city's data in the DLL plus adding it to that system's
						# current build planet to get it into the Python planet data.
						printd("Meltdown: finding new capial system")
						pPlayer.findNewCapital()
					else:
						# This is this civ's only system so we can't move the capitol building to a different one.
						# Try to move it to a different planet instead.
						printd("Meltdown: moving capitol to different planet in same system")
						# Select the planet that is the largest population limit planet
						#  (using production as tie breaker) that is not the planet being wiped out
						bRemove = False
						aiPlanetList = pSystem.getSizeYieldPlanetIndexList(1) # 1 is production, arbitrarily selected
						for iLoopPlanet in range( len(aiPlanetList)):
							pLoopPlanet = pSystem.getPlanetByIndex(aiPlanetList[iLoopPlanet][2])
							if (pLoopPlanet.getOrbitRing() != pMeltPlanet.getOrbitRing()):
								pLoopPlanet.setHasBuilding(iBuilding, true)
								printd("Meltdown: moved Capitol to planet at ring %d" % pLoopPlanet.getOrbitRing())
								bRemove = True
								break

				else:
					pCity.setNumRealBuilding(iBuilding, pCity.getNumRealBuilding(iBuilding)-1)

				if bRemove :
					# The only time this is not the case is when it is the capitol and there is no other
					# planet it can be moved to. You always need a Capitol, so it stays on the dead planet
					pMeltPlanet.setHasBuilding(iBuilding, false)

		# 2.5) unlisted above, but here anyway
		if (pSystem.getBuildingPlanetRing() == pMeltPlanet.getOrbitRing()):
			# This system's current build planet is the planet being wiped out,
			# change it to some other planet, like the new "best" planet.
			# There is an issue if every planet in the current infuence range is dead -
			# in such a case there is no planet that should be having things built on it.
			# With any luck, this will never come up.
			pSystem.setBuildingPlanetRing(pSystem.getPlanetByIndex(CvSolarSystem.getBestPlanetInSystem(pSystem)).getOrbitRing())
					
		# 3) remove bonus
		if (pMeltPlanet.isBonus()): # planet being melted has a bonus, remove it from the planet and the plot
			pMeltPlanet.setBonusType(-1)
			pCityPlot.setBonusType(-1)			
		
		# 4) feature spread
		for iDX in range(-1, 2) :
			for iDY in range (-1, 2) :
				if (iDX != 0) and (iDY != 0) :
					pPlot = plotXY( pCity.getX(), pCity.getY(), iDX, iDY)
					if pPlot and not pPlot.isNone():
						if not pPlot.isImpassable() and (FeatureTypes.NO_FEATURE == pPlot.getFeatureType()) :
							if (CyGame().getSorenRandNum(100, "Meltdown Fallout") < gc.getDefineINT("NUKE_FALLOUT_PROB")) :
								pPlot.setImprovementType(ImprovementTypes.NO_IMPROVEMENT)
								pPlot.setFeatureType(gc.getDefineINT("NUKE_FEATURE"), 0)
							
		# 5) unit damage - only check units on this plot!
		lUnit = []
		for i in range(pCityPlot.getNumUnits()):
			pLoopUnit = pCityPlot.getUnit(i)
			if ( not pLoopUnit.isDead()): #is the unit alive?
				lUnit.append(pLoopUnit) #add unit instance to list

		for pUnit in lUnit :
			if not pUnit.isDead() and not pUnit.isNukeImmune() :
				iNukeDamage = gc.getDefineINT("NUKE_UNIT_DAMAGE_BASE") + CyGame().getSorenRandNum(gc.getDefineINT("NUKE_UNIT_DAMAGE_RAND_1"), "Nuke Damage 1") + CyGame().getSorenRandNum(gc.getDefineINT("NUKE_UNIT_DAMAGE_RAND_2"), "Nuke Damage 2")
				iNukeDamage *= max(0, (pCity.getNukeModifier() + 100))
				iNukeDamage /= 100
				if pUnit.canFight() or (pUnit.airBaseCombatStr() > 0) :
					printd("Meltdown: unit %s damaged for %d" % (pUnit.getName().encode('unicode_escape'), iNukeDamage))
					pUnit.changeDamage(iNukeDamage, PlayerTypes.NO_PLAYER)
				elif iNukeDamage >= gc.getDefineINT("NUKE_NON_COMBAT_DEATH_THRESHOLD") :
					printd("Meltdown: non-combat unit %s killed from damage over threshold" % (pUnit.getName().encode('unicode_escape'),))
					pUnit.kill(false, PlayerTypes.NO_PLAYER)
					
		# 6) population reduction
		iNukedPopulation = (pCity.getPopulation() * (gc.getDefineINT("NUKE_POPULATION_DEATH_BASE") + CyGame().getSorenRandNum(gc.getDefineINT("NUKE_POPULATION_DEATH_RAND_1"), "Population Nuked 1") + CyGame().getSorenRandNum(gc.getDefineINT("NUKE_POPULATION_DEATH_RAND_2"), "Population Nuked 2"))) / 100

		iNukedPopulation *= max(0, (pCity.getNukeModifier() + 100))
		iNukedPopulation /= 100

		pCity.changePopulation(-(min((pCity.getPopulation() - 1), iNukedPopulation)))
				
		# Now update the city and display
		FinalFrontier = CvEventInterface.getEventManager()
		FinalFrontier.getAI().doCityAIUpdate(pCity)
			
		pSystem.updateDisplay()
		
		# give message that this has happened
		szBuffer = localText.getText("TXT_KEY_MISC_MELTDOWN_CITY", (pCity.getName(),))
		CyInterface().addMessage( pCity.getOwner(), False, gc.getDefineINT("EVENT_MESSAGE_TIME"), szBuffer, 
				"AS2D_MELTDOWN", InterfaceMessageTypes.MESSAGE_TYPE_MINOR_EVENT,
				CyArtFileMgr().getInterfaceArtInfo("INTERFACE_UNHEALTHY_PERSON").getPath(), gc.getInfoTypeForString("COLOR_RED"),
				pCity.getX(), pCity.getY(), True, True)

		return True

Note: there might be an issue with the printd() function calls. I'm not sure when the particular adjustment for them was made, so... In the old version these might need to be "CvSolarSystem.printd(...)" - or you could add specific import of that function into the local namespace like the current version has by adding a line that says "from CvSolarSystem import printd" right above the existing "import CvSolarSystem" near the beginning of the file. Well, you could also remove them since they don't do anything in the game (but assist with debugging).

And a new cannotConstruct:
Code:
	def cannotConstruct(self,argsList):
		pCity = argsList[0]
		eBuilding = argsList[1]
		bContinue = argsList[2]
		bTestVisible = argsList[3]
		bIgnoreCost = argsList[4]

		# CP - validate eBuilding (I don't know why this is needed - if it is calling this with an 
		#      invalid building code then something in the caller is broken)
		if (eBuilding < 0):
			CvSolarSystem.printd("CvGameUtils.cannotConstruct, passed eBuilding < 0 (%d)" % (eBuilding) )
			return True
		
		FinalFrontier = CvEventInterface.getEventManager()
		
		pSystem = FinalFrontier.getSystemAt(pCity.getX(), pCity.getY()) 
		pPlanet = pSystem.getPlanet(pSystem.getBuildingPlanetRing())
		pCivilization = gc.getCivilizationInfo( gc.getPlayer(pCity.getOwner()).getCivilizationType())
		bNotHere = False
		pBuildingInfo = gc.getBuildingInfo(eBuilding)
		
		# Cannot build a building if it already exists on the currently "Building" Planet
		if (pPlanet):
			if (pPlanet.isHasBuilding(eBuilding)):
				bNotHere = True
		else:
			return True # this should never happen
		
		#Moon and Rings restrictors moved to XML tags: TC01
		if (pBuildingInfo.isMoon()) and (not pPlanet.isMoon()):
			bNotHere = true
			
		if (pBuildingInfo.isRings()) and (not pPlanet.isRings()):
			bNotHere = true

		if (eBuilding in self.dBuildingBonuses):
			if not pPlanet.isBonus():
				bNotHere = True
			elif (pPlanet.getBonusType() != self.dBuildingBonuses[eBuilding]):
				# the planet has a bonus, but it is the wrong one - since there can be only one bonus in a system
				# and we know it has the wrong one, theis building can not be built anywhere in this system
				return True
					
		# On a disabled planet we can only build nuke immune buildings
		if (not pBuildingInfo.isNukeImmune()) and pPlanet.isDisabled():
			bNotHere = True
		
		# Prereqs are required for the Planet
		for iNeededBuildingClassLoop in range(gc.getNumBuildingClassInfos()):
			if (pBuildingInfo.isBuildingClassNeededOnPlanet(iNeededBuildingClassLoop)):
				iNeededBuildingLoop = pCivilization.getCivilizationBuildings(iNeededBuildingClassLoop )
				if (not pPlanet.isHasBuilding(iNeededBuildingLoop)):
					bNotHere = True
					break
		
		# CP - now for the "if we can't build it here, try it on some other planet in the system" upgrade.
		#      This is the core functionality of the improvement, and is all new.
		#      Skip if this if this is for a player, not an AI.
		#
		#      Note: I'm not positive that this is working properly. If the AI ever calls this function when
		#      the build queue is not empty, then it can change the current build planet out from under whatever
		#      is currently being built, possibly continuing to build the thing currently in the queue on an
		#      invalid planet (such as one that already has the building, or that does not have a prereq building
		#      on it). So far I have not seen any mysterious bad behavior, but I can't be certain.
		if bNotHere and (not gc.getPlayer(pCity.getOwner()).isHuman()):
			for iPlanetLoop in range(pSystem.getNumPlanets()):
				pLoopPlanet = pSystem.getPlanetByIndex(iPlanetLoop)
				if (pLoopPlanet != pPlanet) and (pLoopPlanet.isPlanetWithinCulturalRange()) : 
					# skip the planet we are already using and planets not in the cultural range
					
					if (pBuildingInfo.isMoon()) and (not pLoopPlanet.isMoon()):
						continue
						
					if (pBuildingInfo.isRings()) and (not pLoopPlanet.isRings()):
						continue
						
					if (pLoopPlanet.isHasBuilding(eBuilding)):
						continue # already has one here, skip rest of loop
					
					if (eBuilding in self.dBuildingBonuses):
						if not pPlanet.isBonus():
							# planet does not have bonus but one is required by this building
							continue
						elif (pPlanet.getBonusType() != self.dBuildingBonuses[eBuilding]):
							# the planet has a bonus, but it is the wrong one - since there can be only one bonus in a system
							# and we know it has the wrong one, this building can not be built anywhere in this system
							return True
					
					if (not pBuildingInfo.isNukeImmune()) and pLoopPlanet.isDisabled():
						continue
							
					hasPrereq = True
					for iNeededBuildingClassLoop in range(gc.getNumBuildingClassInfos()):
						if (pBuildingInfo.isBuildingClassNeededInCity(iNeededBuildingClassLoop)):
							iNeededBuildingLoop = pCivilization.getCivilizationBuildings(iNeededBuildingClassLoop ) # get this civilization's building for this buildingclass
							if (not pLoopPlanet.isHasBuilding(iNeededBuildingLoop)):
								hasPrereq = False
								break # one missing prereq is all that has to happen so skip checking for more
					# If we get here with hasPrereq == True then we are at a planet that is 
					# not the one we started with, is within the cultural/influence range, 
					# does not already have a building of this type, and does have all prereq buildings.
					# At this point we may, in the furture, want to add some additional checks, in particular
					# we may want to check if the planet has any population assigned to it and skip if if
					# it doesn't but the building type is one of the ones that modifies the planet's yield
					# (which gains you nothing if the planet has no people on it). At the moment I'm not doing
					# this because it may work fine without it - when the system's population assignments are
					# recalculated it may shift people to the planet after the improvement is done.
					# Also, we may want to add the planet to a list and then do some sort of selection
					# process to determine which of the possible planets to use - for now, this is just
					# using the first planet that it finds.
					if (hasPrereq == True) :
						bNotHere = False # don't block the build
						if (not bTestVisible) : # only actually change the ring if not just testing "visibility"
							pSystem.setBuildingPlanetRing(pLoopPlanet.getOrbitRing()) # switch to this new planet
							CvSolarSystem.printd("CvGameUtils.cannotConstruct, %s active planet ring set to %d (turn = %d, owner = %s, building = %s, bContinue = %d, bTestVisible = %d)" % (pCity.getName(), pLoopPlanet.getOrbitRing(), CyGame().getElapsedGameTurns(), gc.getPlayer(pCity.getOwner()).getName(), pBuildingInfo.getType(), bContinue, bTestVisible) )
						else:
							CvSolarSystem.printd("CvGameUtils.cannotConstruct, %s active planet ring not set, bTestVisible = true (turn = %d, owner = %s, building = %s, bContinue = %d, bTestVisible = %d)" % (pCity.getName(), CyGame().getElapsedGameTurns(), gc.getPlayer(pCity.getOwner()).getName(), pBuildingInfo.getType(), bContinue, bTestVisible) )
						break # we have done what we needed to do, bail out of the loop entirely
	
		return bNotHere

Edit: new onNukeExplosion, only slightly changed:
Code:
	def onNukeExplosion(self, argsList):
		'Nuke Explosion'
		pPlot, pNukeUnit = argsList
		
		if (pPlot.isCity()):

			pCity = pPlot.getPlotCity()
			pSystem = getSystemAt(pPlot.getX(), pPlot.getY()) #FFPBUG
			pBestPlanet = pSystem.getPlanetByIndex(getBestPlanetInSystem(pSystem))
			
			pBestPlanet.setDisabled(true)
			pBestPlanet.setPopulation(0)			

			for iBuilding in range(gc.getNumBuildingInfos()):
				if pBestPlanet.isHasBuilding(iBuilding) and not gc.getBuildingInfo(iBuilding).isNukeImmune():
					bRemove = True
					if (gc.getBuildingInfo(iBuilding).isCapital()):
						pPlayer = gc.getPlayer(pCity.getOwner())
						if (pPlayer.getNumCities () > 1):
							# The following call moves the capitol building, removing it from this city's data
							# in the DLL (which is why there is no manual setNumRealBuilding in here)
							# and adding it to the new capital city's data in the DLL plus adding it to that system's
							# current build planet to get it into the Python planet data.
							printd("Nuked: finding new capial system")
							pPlayer.findNewCapital()
						else:
							# This is this civ's only system so we can't move the capitol building to a different one.
							# Try to move it to a different planet instead.
							printd("Nuked: moving capitol to different planet in same system")
							# Select the planet that is the largest population limit planet
							#  (using production as tie breaker) that is not the planet being wiped out
							bRemove = False
							aiPlanetList = pSystem.getSizeYieldPlanetIndexList(1) # 1 is production, arbitrarily selected
							for iLoopPlanet in range( len(aiPlanetList)):
								pLoopPlanet = pSystem.getPlanetByIndex(aiPlanetList[iLoopPlanet][2])
								if (pLoopPlanet.getOrbitRing() != pBestPlanet.getOrbitRing()):
									pLoopPlanet.setHasBuilding(iBuilding, true)
									printd("Nuked: moved Capitol to planet at ring %d" % pLoopPlanet.getOrbitRing())
									bRemove = True
									break
					else:
						pCity.setNumRealBuilding(iBuilding, pCity.getNumRealBuilding(iBuilding)-1)
					
					if bRemove :
						# The only time this is not the case is when it is the capitol and there is no other
						# planet it can be moved to. You always need a Capitol, so it stays on the dead planet
						pBestPlanet.setHasBuilding(iBuilding, false)
						
			if (pSystem.getBuildingPlanetRing() == pBestPlanet.getOrbitRing()):
				# This system's current build planet is the planet being wiped out,
				# change it to some other planet, like the new "best" planet.
				# There is an issue if every planet in the current infuence range is dead -
				# in such a case there is no planet that should be having things built on it.
				# With any luck, this will never come up.
				pSystem.setBuildingPlanetRing(pSystem.getPlanetByIndex(getBestPlanetInSystem(pSystem)).getOrbitRing())			
				
			if (pBestPlanet.isBonus()): # planet being nuked has a bonus, remove it from the planet and the plot
				pBestPlanet.setBonusType(-1)
				pPlot.setBonusType(-1)			
			
			self.getAI().doCityAIUpdate(pPlot.getPlotCity())
			
			pSystem.updateDisplay()

I have tested these by running a few turns with a save of a somewhat-late mid-game game with the nutrition facility set to a meltdown chance of 50% (iNukeExplosionRand set to 2) which cases a lot of them since every star system colonized gets one for free.

So now I really really hope :please: that this is actually fixed without introducing any new bugs, leaving anything out, or dredging up some previously unnoticed issue.
 
Hi God-Emperor/TC01 I've looked at the Debug files over and over every time the game CtD's it's for the same reason - details below.

Possible cause of the CtD.

in doBeginTurnAI, get system for pCity at x=128, y=67

getSystemAt 128,67

Updating happiness for city Abbai HW SsumsshAa

Max: 2, Used: 2; PopOver for this city is 0

getSystemAt 128,67




Wheeeee: Checking City System at 128, 67

Max Pop is 2

Population which should be assigned on cultural expansion: 0

14:26:59 DEBUG: BugEventManager - event techSelected: (52, 1)
getSystemAt 128,67

14:26:59 DEBUG: BugEventManager - event cityDoTurn: (<CvPythonExtensions.CyCity object at 0x288711F0>, 1)
14:26:59 DEBUG: BugEventManager - event EndPlayerTurn: (49, 1)
14:26:59 DEBUG: BugEventManager - event unitPromoted: (<CvPythonExtensions.CyUnit object at 0x288712D0>, 31)
14:26:59 DEBUG: BugEventManager - event unitMove: (<CvPythonExtensions.CyPlot object at 0x288712D0>, <CvPythonExtensions.CyUnit object at 0x288714C8>, <CvPythonExtensions.CyPlot object at 0x28871340>)
14:26:59 DEBUG: BugEventManager - event BeginPlayerTurn: (49, 2)
!!! set self.theCurrentPlayer = 2

in doBeginTurnAI, get system for pCity at x=3, y=90

getSystemAt 3,90

Updating happiness for city Brakos

Max: 2, Used: 2; PopOver for this city is 0

getSystemAt 3,90




Wheeeee: Checking City System at 3, 90

Max Pop is 2

Population which should be assigned on cultural expansion: 0

14:26:59 DEBUG: BugEventManager - event windowActivation: (0,)

When the Python does it's DEBUG checks for Abbai HW SsumsshAa everything seems to be OK but when it checks Brakos it doesn't do what it did for the Abbai


14:26:59 DEBUG: BugEventManager - event techSelected: (52, 1)
getSystemAt 128,67

Instead it does this.


14:26:59 DEBUG: BugEventManager - event windowActivation: (0,)

Which tells me that it trying to open an Event window rather than follow the same process it has just done for the Abbai. I've also attached the three Debug files from the last CtD I had. Besides the problem above I also have a small problem with the SevoPedia Promotions tree. I've tried looking at the lines indicated by the error message but cannot find a Unit Upgrades Graph that is referred to in the PythonErr Log. This log also contains the other error message I get at game start that refers to the unique units. With the latest version of the Mod uploaded you should be able to see everything in action for yourself rather than rely on me giving you slightly inaccurate information. Really hoping you can help out.
 
The "DEBUG: BugEventManager - event windowActivation: (0,)" is not about an event window being activated (like the "DEBUG: BugEventManager - event techSelected: (52, 1)" is not about selecting an "event tech"), rather it is notifying you of the event that the game's window was activated.

Do you have a savegame from shortly before a crash happens? I installed the latest version of B5 a few days ago but haven't looked at it yet.
 
Here is the last autosave I had.

Just so you know, I have been looking at this some.

I fixed 3 issues that are not related to the crash:

Python error popups at game start: fix UnitUtil.py as specified in post 35 of the Final Frontier Plus BUG thread - UU of NONE issue. (This apparently didn't make it into the version 2.5 Plus.)

Did a fix Python\Contrib\CvModName.py to have the correct mod name, display name, and version number. I just set them to this:
Code:
modName = "Babylon 5"
displayName = "Babylon 5 Mod"
modVersion = "2.5 Plus"
(FFP needs to update the version number in there too, it still has the "BUG" part of the name in there and still says version 1.651.)

And to make PythonDbg.log less crowded (and a lot smaller), I set the Update event to be excluded from the events that BUG logs by adding it to the noLogEvents list which starts at line 75 of Config\BUG Core.xml.

None of these 3 things had any impact on the crash.

Which brings us to the funky magic part of the thing...

In order to narrow down where the crash is happening, I added a printd() call in the doGold callback (which is always called, so it doesn't need to be enabled) to see if it was crashing before that gets called or after. Just adding that one line to that one semi-randomly selected callback stopped the crashing. I have run your savegame for over 20 turns past the point where it was crashing (after hitting end turn the second time) and it seems to be working fine.

So the ridiculous magical fix of the crash problem (which completely ignores the actual underlying cause) is to replace the doGold callback with this slightly modified version that doesn't actually do anything but print a line to the PythonDbg.log file:
Code:
	def doGold(self,argsList):
		ePlayer = argsList[0]
		printd("in doGold for player %d" % (ePlayer,)) # crash debug
		return False
I have not checked to see if this actually makes it work when the line near the beginning of CvSolarSystem.py that sets the g_bPrintDebugText value is setting it to False (which would make all the printd function calls do nothing instead of printing to the log file), but it is making it not crash for me when it is set to True. In theory, that is supposed to be set to False for a release version so as to avoid putting megabytes of stuff into that log file that the regular player is never going to look at, but I digress.

This "fix" does not actually fix anything. The fact that it makes it work is, therefore, somewhat troubling because it is currently preventing me from possibly actually locating the problem. The bad news is that frequently if just adding a print statement somewhere "fixes" a problem it is either a timing issue (which is unlikely in a single threaded application, although the graphics processing is somewhat asynchronous so it can happen) or some bad memory access issue that is "fixed" by shifting what memory it is randomly looking at slightly, which happens to make it use some value that is valid instead of the invalid value it was using - neither of these types of issue is good news, so hopefully the underlying issue is not one of those. Definitely some bad magic going on.

This sort of "fix" also has a high probability of breaking again if anything else is changed.

I might have better luck if I was set up to build and use an actual debug DLL (assuming that the crash still happens when using it) and could look at what it is really doing, but I'm not.

So that is where things currently stand. I'll be looking at it more later.
 
It turns out that the above voodoo-fix doesn't actually work (which is actually a good thing). I had not realized that the save game had the "New Random Seed on Reload" option activated, and that was apparently the culprit, in conjunction with the typical RNG shenanigans.

It turns out I hit the perfect random chance sequence of events:

run save - crash
run save - crash
add printd to callback - no crash
remove printd from callback - crash
put it back - no crash
post about it here

then

try running it again later with callback still modified - crash
try again - crash
make this post

So it looks like the odds of a crash are high, but not 100%. I have not yet examined the debug log to see if teh crash happens before or after the doGold callback.

The interesting thing is that once it gets past the crash, whatever is causing it, the game I ran kept going without difficulty. It is currently on turn 222.


BTW, From this game I can report that B5 develops excruciatingly slowly - most of the time there is pretty much nothing to do. Part of that is that this save is on Marathon, but a lot of it is the tech tree - there was only 1 earlyish ship and that came after researching 2 additional techs. I kept the already entered research queue, which was probably part of the problem (there are 20-something techs already queued up, adn the sequence is no beelining anything). As it is, that game is still some dozen or so techs away from being able to colonize another star system, unless I change the research path, since the tech is in something like the 4th or 5th column of techs and there are various prereqs to meet meaning the shortest path to get to it would be something like 8 techs at the start of the game. A lot of ships need resources to build but those resources are not even revealed until a 3rd or 4th column tech - I only just revealed crystals and have not yet revealed the other asteroid based resources. Also, no ship is available until later that can pop the wreckage without risking raiders, and the EA Artemis type ship can not reliably win against more than 2 even with pretty much every available promotion. Recently I became able to build the EA Sagittarius which is 50% stronger, so I may to a bit better exploring with those. Just a few turns ago I finally met someone else, the Drazi (they are also shown as still having only 1 star system).

I also ran into a few other issues, like the system preview screen's code is still looking for BUILDING_PBS (a UB version of the Commercial Satellites in regular FFP) and the Sevopedia's promotion tree page dies while trying to build the tree.

Anyhoo, I'll be looking into the crash situation more later.
 
Many thanks for the updates God-Emperor.

As it is, that game is still some dozen or so techs away from being able to colonize another star system
I've been toying with the idea of having either a randomly generated number of colonization ships available at game start or a fixed number of them. These would represent a civ's early attempts at colonizing other planetary sysems and also allowing a little early exploration of the map. You won't want to pop a goody wreck for fear of the raiders lurking inside unless you're feeling really brave.

A lot of ships need resources to build but those resources are not even revealed until a 3rd or 4th column tech
I've also been thinking about early versions of the Jump capable ships that could be available earlier in the tree and become obsolete when the Jump capable version becomes available.

Until I get the crashing problem resolved I don't want to make any more additions or changes to what I have done. So for the time being things will have to stay as they are.

the system preview screen's code is still looking for BUILDING_PBS (a UB version of the Commercial Satellites in regular FFP)
I hadn't noticed there was a UB version of this when I was doing the merge, I'll have to find it and then either remove it, comment it out or have a civ able to build it as a UB)
 
I hadn't noticed there was a UB version of this when I was doing the merge, I'll have to find it and then either remove it, comment it out or have a civ able to build it as a UB)

It appears in a couple of other places in the Python for displaying planets - it checks for it to see if it should display the orbiting satellites. All 3 of these should be removed when there is no such building (and B5 currently has no such building).

The other two instances don't cause a Python exception pop-up, but the one in the system preview screen does. Why? Well, when adding it I matched the style used in CvPlanetInfoScreen.py. IN specific, in that one place it uses "CvUtil.findInfoTypeNum(gc.getBuildingInfo,gc.getNumBuildingInfos(),'BUILDING_PBS')" but in the other two it uses "gc.getInfoTypeForString('BUILDING_PBS')". The difference is the CvUtil function checks to see if the value it gets is -1 and if so it throws an error via an assert statement. Simply changing that to use the same "gc.getInfoTypeForString('BUILDING_PBS')" removes the error, mostly - it is pretty much purely accidental that this works.

So here is the interesting part: In Python if an index into an array is negative, then it is treated as being the index relative to the end of the list so when getInfoTyeForString doesn't find a definition for 'BUILDING_PBS' and returns -1, the use of this -1 as an index into the array of buildings on the planet causes Python to return the last element of the array (coming in from the end, -1 is the first element since -0 is the same as 0 which is the first element at the front of the array) which is actually the entry for BUILDING_LEAGUE_COLONIES, so there should be a display error such that any planet with BUILDING_LEAGUE_COLONIES on it is going to show the orbiting satellites whether or not it has build the commercial satellites building built on that planet.
 
OK, I've done some more fiddling and looking at code in the DLL. The news is not so good.

The fact that is sometimes crashes and sometimes doesn't would seem to indicate that there is a problem in some area that has a random chance applied to it. This is probably only indirectly the case (down in the section labeled in Part II is a more specific probable cause).

Based on adding a print statement to the doGold callback, I can say that it seems to always crash before it gets to that but after the end of the onBeginPlayerTurn Python function's processing of the beginPlayerTurn event. This narrows it down some (after line 2568 and before line 2597 of CvPlayer.cpp), but quite a bit happens in that interval.

So first some stuff about things I fixed (Part I) then some bad news (Part II).

Part I:

Via XML adjustments I have the crash rate is down to something like 20% of the time for the posted save - most of the time you can click on the second end turn and it does not crash. (So there are issues with the XML that may cause crashes, but it is possible that the XMl adjustments just shifted things around in memory a little which had an indirect accidental effect on the specific crash cause.)

Much later in a game, continued from the save, I have seen a couple of crashes. Mostly right after loading from a save. I also got a weird occurrence: it appeared to lock up, so it went to the task manager to kill it and noticed that it was doing a fair amount of I/O. I found that it was creating an ever increasing in size autosave file - it was up to about 19.5MB when I killed it.

Here is a list of stuff I fixed in the XML:
Code:
CIV4ArtDefines_Unit.xml:
- ART_DEF_UNIT_THORUSHKA: the NIF and SHADERNIF sepcify the wrong folder
	NIF says Art/Units/Dilgar_Imperium_Ships/Dilgar_Thorun/Dilgar_AdvFighter.nif
		this should be Art/Units/Dilgar_Imperium_Ships/Dilgar_Thorushka/Dilgar_AdvFighter.nif
	SHADERNIF says Art/Units/Dilgar_Imperium_Ships/Dilgar_Thorun/Dilgar_AdvFighter_FX.nif
		there is no such .nif file anywhere, so it should probably match the corrected regular NIF
- ART_DEF_UNIT_SEKHMET: the NIF entry has a typo
	NIF says ">Art/Units/Dilgar_Imperium_Ships/Dilgar_Sekhmet/Dilgar_Sekhmet.nif", note the leading extraneous ">"
- the defense units (ODS, PDS, ODP) had bad sound defines for SelectionSound and ActionSound
	all set to AS3D_UN_FIGHTER_COMMAND_PATROL changed to AS3D_UN_FF_FIGHTER_COMMAND_PATROL
	and thsoe with PatrolSound set to AS3D_UN_FIGHTER_PATROL changed to AS3D_UN_FF_FIGHTER_PATROL
- Also, just in case:
	changed Narn_ToReth to Narn_Toreth to match the actual case of the folder
	changed Narn_RonGoth.nif to Narn_Rongoth.nif to match the actual file name

In CIV4TechInfos.xml TECH_B5_ADV_CYBERNETICS has a prereq of B5_TECH_CYBERNETICS, which should be TECH_B5_CYBERNETICS

AudioDefines.xml:
- SND_TECH_B5_SMALL_ATTACK_CRAFT is missing, added entry for it (sound file did exist)

CIV4BuildingInfos.xml
- BUILDING_MANUFACTURING_PLANT had an extra iCostModIncrease entry at the end

CIV4LeaderHeadInfos.xml
- LEADER_BARBARIAN had a specifically set favorite civic of NONE, like so:
			<FavoriteCivics>
				<FavoriteCivic>
					<CivicType>NONE</CivicType>
					<bFavoriteCivic>1</bFavoriteCivic>
				</FavoriteCivic>
			</FavoriteCivics>
	which seems like a bad plan, changed to "<FavoriteCivics/>"

CIV4UnitInfos.xml - has some bad XML:
- 2 units with UnitClassTargets (UNIT_ALIEN_INVASION_SHIP and UNIT_ALIEN_SCOUT)
  and 3 with UnitClassDefenders (UNIT_PLANETARY_DEFENSE_I, _II, and  _III) that are are specified wrong
	they just give a unit class instead of having the required sub-tags, i.e.
		UNIT_ALIEN_INVASION_SHIP has <UnitClassTargets>UNITCLASS_STARBASE_I</UnitClassTargets>
		when it should have
			<UnitClassTargets>
				<UnitClassTarget>
					<UnitClassTargetType>UNITCLASS_STARBASE_I</UnitCombatTargetType>
					<bUnitClassTarget>1</bUnitCombatTarget>
				</UnitClassTarget>
			</UnitClassTargets>
THe first few art define issues where identified by running CivChecker on the mod. The rest were found by identifying the causes of various messages in all the log files and such.

I also applied some Python fixes, at least some of which were mentioned earlier in this thread (that I though had already been applied). Also, some of these were just to reduce the clutter in the PythonDbg.log file.

Also, Config\Unit Naming.xml is missing from the mod.

The promotions seem to be a bit of a mess, but I didn't try to sort it out. Their relationships are so convoluted that the function that maps the upgrade paths you can follow can't do it.

After all that messing around, it is not fixed.

Anyhow, now we get to some pretty bad news...

Part II:

I found something in the DLL that leads me to beleive that having units classes with a default unit type of NONE (or the various non-existant ship times like BATTLESHIP_I, which are effectively identical to NONE) won't work.

In fact, I'm surprised it doesn't crash more. Like the instant an AI spots somebody else's ship.

The issue is in the CvPlayerAI::AI_doEnemyUnitData() function.
Lines 16720 and 16721 in CvPlayerAI.cpp:
Code:
			UnitTypes eUnit = (UnitTypes)GC.getUnitClassInfo((UnitClassTypes)iI).getDefaultUnitIndex();
			m_aiUnitCombatWeights[GC.getUnitInfo(eUnit).getUnitCombatType()] += m_aiUnitClassWeights[iI];
The first line there gets the default unit type for a unit class. Most unit classes in B5 do not have a valid default unit type. They are all effectively "NONE" which, in this case, translates to a value of -1 being assigned to eUnit (which is the NO_UNIT value in the UnitTypes enumeration).

The second line tries get a unit info for that unit type of -1. This is not valid. If a debug DLL was being used the getUnitInfo would fire off a failed assert, but asserts do nothing when not using a debug DLL. Therefore, it will return random junk (whatever is pointed to by the value in memory right before the start of the array) interpreted as a unit info type object. It then gets the unit combat type of the junk (which I'm a little surprised actually works) and uses that junk number as an index into the m_aiUnitCombatWeights array (it could easily be way outside the bounds of the actual array, negative or past the end - but since it frequently doesn't crash, I'm thinking it is probably lucking out most of the time and getting a 0 since a lot of memory is set to 0 a lot of the time).

That is assuming it gets this far before the crash (I suspect it is, but I'm not sure).

A debug version of the 1.72 DLL would probably locate the problem more directly, but I haven't got one. (You'd have to click through a couple hundred failed assertions when loading the XML relating to non-existant buildings and units - I know because I clicked past them using a 1.73 debug DLL which works up until it loads the options from the save game, since 1.73 has an additional option causing it to fail then.)

The actual crash could also be after this, but this code all by itself leads to one conclusion:

You can not actually get away with using unit classes that do not have valid default units. Even it it doesn't crash at this location it can not possibly be correctly evaluating the other civs' units (that's what those two lines of code are part of).

As unfortunate as it seems, I think you have to go though and make the ship classes into proper unique units. That would be the "option A" I mentioned over in post #35 in the Final Frontier Plus BUG thread, and mentioned again at the end of post #43 over there. Evidently it is not just BUG that wants this, it is the DLL (I knew there were issues with either buildings or units with invalid default types, but can never remember which - it looks like it is units, although I suppose it could be both).
 
It may not be good news but you have done an awful lot of work here for which I am eternally grateful. I've implemented all of the changes you located in part 1 and will be looking at what I can do to sort out part 2 which is probably going to take a lot longer to sort out.

Incidentally I played a game last night and played over 300 turns with no crash. The only change I had made at that point was the one to stop the python pop ups at game start (which I thought I had already done but oh so obviously hadn't:blush:).

I've been looking into what you said in part 2 and have some things I want to clarify.

Code:
UnitTypes eUnit = (UnitTypes)GC.getUnitClassInfo((UnitClassTypes)iI).getDefaultUnitIndex();
			m_aiUnitCombatWeights[GC.getUnitInfo(eUnit).getUnitCombatType()] += m_aiUnitClassWeights[iI];


The first line there gets the default unit type for a unit class. Most unit classes in B5 do not have a valid default unit type. They are all effectively "NONE" which, in this case, translates to a value of -1 being assigned to eUnit (which is the NO_UNIT value in the UnitTypes enumeration).
In here you point out that it is looking for a default unit type for a unit class. At the moment we have the following for the B5 mod (One Unit picked as an example.)
In CivilizationInfos.
Code:
				<Unit>
					<UnitClassType>UNITCLASS_TRAGA</UnitClassType>
					<UnitType>UNIT_TRAGA</UnitType>
				</Unit>
In UnitClasInfos
Code:
		<UnitClassInfo><!-- Traga Starfighter -->
			<Type>UNITCLASS_TRAGA</Type>
			<Description>TXT_KEY_UNIT_TRAGA</Description>
			<iMaxGlobalInstances>-1</iMaxGlobalInstances>
			<iMaxTeamInstances>-1</iMaxTeamInstances>
			<iMaxPlayerInstances>-1</iMaxPlayerInstances>
			<iInstanceCostModifier>0</iInstanceCostModifier>
			<DefaultUnit>UNIT_SPACE_FIGHTER_I</DefaultUnit>
		</UnitClassInfo>
In UnitInfos
Code:
		<UnitInfo><!-- Traga Starfighter -->
			<Class>UNITCLASS_TRAGA</Class>
			<Type>UNIT_TRAGA</Type>
			<UniqueNames/>
			<Special>SPECIALUNIT_FIGHTER</Special>
			<Capture>NONE</Capture>
			<Combat>UNITCOMBAT_SQUADRON</Combat>
			<Domain>DOMAIN_AIR</Domain>
			<DefaultUnitAI>UNITAI_DEFENSE_AIR</DefaultUnitAI>
			<Invisible>NONE</Invisible>
			<SeeInvisible>NONE</SeeInvisible>
			<Description>TXT_KEY_UNIT_TRAGA</Description>
			<Civilopedia>TXT_KEY_UNIT_TRAGA_PEDIA</Civilopedia>
			<Strategy>TXT_KEY_UNIT_TRAGA_STRATEGY</Strategy>
			<Help/>
			<Advisor>ADVISOR_MILITARY</Advisor>
			<bAnimal>0</bAnimal>
			<bFood>0</bFood>
			<bNoBadGoodies>0</bNoBadGoodies>
			<bOnlyDefensive>0</bOnlyDefensive>
			<bNoCapture>0</bNoCapture>
			<bQuickCombat>0</bQuickCombat>
			<bRivalTerritory>0</bRivalTerritory>
			<bMilitaryHappiness>0</bMilitaryHappiness>
			<bMilitarySupport>1</bMilitarySupport>
			<bMilitaryProduction>1</bMilitaryProduction>
			<bPillage>0</bPillage>
			<bSpy>0</bSpy>
			<bSabotage>0</bSabotage>
			<bDestroy>0</bDestroy>
			<bStealPlans>0</bStealPlans>
			<bInvestigate>0</bInvestigate>
			<bCounterSpy>0</bCounterSpy>
			<bFound>0</bFound>
			<bGoldenAge>0</bGoldenAge>
			<bInvisible>0</bInvisible>
			<bFirstStrikeImmune>0</bFirstStrikeImmune>
			<bNoDefensiveBonus>0</bNoDefensiveBonus>
			<bIgnoreBuildingDefense>0</bIgnoreBuildingDefense>
			<bCanMoveImpassable>0</bCanMoveImpassable>
			<bCanMoveAllTerrain>0</bCanMoveAllTerrain>
			<bFlatMovementCost>0</bFlatMovementCost>
			<bIgnoreTerrainCost>0</bIgnoreTerrainCost>
			<bNukeImmune>0</bNukeImmune>
			<bPrereqBonuses>0</bPrereqBonuses>
			<bPrereqReligion>0</bPrereqReligion>
			<bMechanized>1</bMechanized>
			<bSuicide>0</bSuicide>
			<bHiddenNationality>0</bHiddenNationality>
			<bAlwaysHostile>0</bAlwaysHostile>
			<UnitClassUpgrades/>
			<UnitClassTargets/>
			<UnitCombatTargets/>
			<UnitClassDefenders/>
			<UnitCombatDefenders/>
			<FlankingStrikes/>
			<UnitAIs>
				<UnitAI>
					<UnitAIType>UNITAI_DEFENSE_AIR</UnitAIType>
					<bUnitAI>1</bUnitAI>
				</UnitAI>
				<UnitAI>
					<UnitAIType>UNITAI_CARRIER_AIR</UnitAIType>
					<bUnitAI>1</bUnitAI>
				</UnitAI>
			</UnitAIs>
			<NotUnitAIs/>
			<Builds/>
			<ReligionSpreads/>
			<CorporationSpreads/>
			<GreatPeoples/>
			<Buildings/>
			<ForceBuildings/>
			<HolyCity>NONE</HolyCity>
			<ReligionType>NONE</ReligionType>
			<StateReligion>NONE</StateReligion>
			<PrereqReligion>NONE</PrereqReligion>
			<PrereqCorporation>NONE</PrereqCorporation>
			<PrereqBuilding>BUILDING_FLIGHT_SCHOOL</PrereqBuilding>
			<PrereqTech>TECH_B5_SMALL_ATTACK_CRAFT</PrereqTech>
			<TechTypes/>
			<BonusType>NONE</BonusType>
			<PrereqBonuses/>
			<ProductionTraits/>
			<Flavors/>
			<iAIWeight>0</iAIWeight>
			<iCost>18</iCost>
			<iHurryCostModifier>0</iHurryCostModifier>
			<iAdvancedStartCost>30</iAdvancedStartCost>
			<iAdvancedStartCostIncrease>0</iAdvancedStartCostIncrease>
			<iMinAreaSize>-1</iMinAreaSize>
			<iMoves>1</iMoves>
			<bNoRevealMap>0</bNoRevealMap>
			<iAirRange>2</iAirRange>
			<iAirUnitCap>0</iAirUnitCap>
			<iDropRange>0</iDropRange>
			<iNukeRange>-1</iNukeRange>
			<iWorkRate>0</iWorkRate>
			<iBaseDiscover>0</iBaseDiscover>
			<iDiscoverMultiplier>0</iDiscoverMultiplier>
			<iBaseHurry>0</iBaseHurry>
			<iHurryMultiplier>0</iHurryMultiplier>
			<iBaseTrade>0</iBaseTrade>
			<iTradeMultiplier>0</iTradeMultiplier>
			<iGreatWorkCulture>0</iGreatWorkCulture>
			<iEspionagePoints>0</iEspionagePoints>
			<TerrainImpassables/>
			<FeatureImpassables/>
			<TerrainPassableTechs/>
			<FeaturePassableTechs/>
			<iCombat>0</iCombat>
			<iCombatLimit>0</iCombatLimit>
			<iAirCombat>8</iAirCombat>
			<iAirCombatLimit>100</iAirCombatLimit>
			<iXPValueAttack>4</iXPValueAttack>
			<iXPValueDefense>4</iXPValueDefense>
			<iFirstStrikes>0</iFirstStrikes>
			<iChanceFirstStrikes>0</iChanceFirstStrikes>
			<iInterceptionProbability>40</iInterceptionProbability>
			<iEvasionProbability>5</iEvasionProbability>
			<iWithdrawalProb>0</iWithdrawalProb>
			<iCollateralDamage>50</iCollateralDamage>
			<iCollateralDamageLimit>50</iCollateralDamageLimit>
			<iCollateralDamageMaxUnits>2</iCollateralDamageMaxUnits>
			<iCityAttack>0</iCityAttack>
			<iCityDefense>0</iCityDefense>
			<iAnimalCombat>0</iAnimalCombat>
			<iHillsAttack>0</iHillsAttack>
			<iHillsDefense>0</iHillsDefense>
			<TerrainNatives/>
			<FeatureNatives/>
			<TerrainAttacks/>
			<TerrainDefenses/>
			<FeatureAttacks/>
			<FeatureDefenses/>
			<UnitClassAttackMods/>
			<UnitClassDefenseMods/>
			<UnitCombatMods>
				<UnitCombatMod>
					<UnitCombatType>UNITCOMBAT_SQUADRON</UnitCombatType>
					<iUnitCombatMod>200</iUnitCombatMod>
				</UnitCombatMod>
				<UnitCombatMod>
					<UnitCombatType>UNITCOMBAT_RECON</UnitCombatType>
					<iUnitCombatMod>25</iUnitCombatMod>
				</UnitCombatMod>
				<UnitCombatMod>
					<UnitCombatType>UNITCOMBAT_FRIGATE</UnitCombatType>
					<iUnitCombatMod>20</iUnitCombatMod>
				</UnitCombatMod>
				<UnitCombatMod>
					<UnitCombatType>UNITCOMBAT_DESTROYER</UnitCombatType>
					<iUnitCombatMod>15</iUnitCombatMod>
				</UnitCombatMod>
				<UnitCombatMod>
					<UnitCombatType>UNITCOMBAT_CRUISER</UnitCombatType>
					<iUnitCombatMod>20</iUnitCombatMod>
				</UnitCombatMod>
				<UnitCombatMod>
					<UnitCombatType>UNITCOMBAT_CAPITAL_SHIP</UnitCombatType>
					<iUnitCombatMod>25</iUnitCombatMod>
				</UnitCombatMod>
				<UnitCombatMod>
					<UnitCombatType>UNITCOMBAT_CARRIER_SHIP</UnitCombatType>
					<iUnitCombatMod>30</iUnitCombatMod>
				</UnitCombatMod>
				<UnitCombatMod>
					<UnitCombatType>UNITCOMBAT_STARBASE</UnitCombatType>
					<iUnitCombatMod>40</iUnitCombatMod>
				</UnitCombatMod>
			</UnitCombatMods>
			<UnitCombatCollateralImmunes/>
			<DomainMods/>
			<BonusProductionModifiers>
				<BonusProductionModifier>
					<BonusType>BONUS_ALUMINIUM</BonusType>
					<iProductonModifier>5</iProductonModifier>
				</BonusProductionModifier>
				<BonusProductionModifier>
					<BonusType>BONUS_TITANIUM</BonusType>
					<iProductonModifier>5</iProductonModifier>
				</BonusProductionModifier>
				<BonusProductionModifier>
					<BonusType>BONUS_IRON</BonusType>
					<iProductonModifier>5</iProductonModifier>
				</BonusProductionModifier>
				<BonusProductionModifier>
					<BonusType>BONUS_OXYGEN</BonusType>
					<iProductonModifier>5</iProductonModifier>
				</BonusProductionModifier>
				<BonusProductionModifier>
					<BonusType>BONUS_URANIUM</BonusType>
					<iProductonModifier>5</iProductonModifier>
				</BonusProductionModifier>
				<BonusProductionModifier>
					<BonusType>BONUS_HYDROGEN</BonusType>
					<iProductonModifier>5</iProductonModifier>
				</BonusProductionModifier>
			</BonusProductionModifiers>
			<iBombRate>10</iBombRate>
			<iBombardRate>0</iBombardRate>
			<SpecialCargo>NONE</SpecialCargo>
			<DomainCargo>NONE</DomainCargo>
			<iCargo>0</iCargo>
			<iConscription>0</iConscription>
			<iCultureGarrison>0</iCultureGarrison>
			<iExtraCost>0</iExtraCost>
			<iAsset>4</iAsset>
			<iPower>4</iPower>
			<UnitMeshGroups>
				<iGroupSize>1</iGroupSize>
				<fMaxSpeed>10.0</fMaxSpeed>
				<fPadTime>1</fPadTime>
				<iMeleeWaveSize>1</iMeleeWaveSize>
				<iRangedWaveSize>1</iRangedWaveSize>
				<UnitMeshGroup>
					<iRequired>1</iRequired>
					<EarlyArtDefineTag>ART_DEF_UNIT_TRAGA</EarlyArtDefineTag>
				</UnitMeshGroup>
			</UnitMeshGroups>
			<FormationType>FORMATION_TYPE_MACHINE</FormationType>
			<HotKey/>
			<bAltDown>0</bAltDown>
			<bShiftDown>0</bShiftDown>
			<bCtrlDown>0</bCtrlDown>
			<iHotKeyPriority>0</iHotKeyPriority>
			<FreePromotions/>
			<LeaderPromotion>NONE</LeaderPromotion>
			<iLeaderExperience>0</iLeaderExperience>
		</UnitInfo>
Do i need to change both the Unit Class AND the Unit Type or is it just one of them that needs to be changed, if so which one?

Re-reading the post it looks to me like it is just the Unit Class that needs to be changed not the unit type. Please advise because there is an awful lot of changes to be done and I want to make sure I get it right the first time.
 
You could do it by changing only the unit classes and not touching the unit types at all.

<DefaultUnit>UNIT_SPACE_FIGHTER_I</DefaultUnit>
In that example needs to point to some unit that actually exists.

If you set each to their respective unit type then the problem is fixed but everybody will be able to build everybody else's units. This can be fixed by adding each to each of the other civ's Units listing to specify that they get a type of NONE for that unit class (this is a lot of entries).

There is another ways to fix this too, which does not require adding a ton of stuff to CIV4CivilizationInfos.xml (only small adjustements to the already existing Units entries) but does require small changes to CIV4UnitInfos.xml (changing the unit class of most units) and also requires more extensive changes to CIV4UnitClassInfos.xml (since most of the unit classes would be eliminated).

So, one of these two should be done:

1) Keeps most of the stuff the same, prohibiting the building of other civ's units via entires in CIV4CivilizationInfos.xml that block them.

Keep each unit type as having its own unit class, as they do now.
Set each of the civ specific unit class's default unit to be the matching unit type (removing all of the non-existent unit types now used).
Go into CIV4CivilizationInfos.xml and add an entry to every civ that does NOT get the unit to the civ's Units section to set the UnitType for that UnitClassType to be NONE.
Then, if you want to, you could remove the Units entry for the civ that does get the unit since it is redundant to set the unit type for the class to match what the default unit is set to.

Advantages: it is a less complicated adjustment involving two fewer files: you can keep your new "Adv Unit Naming.ini" file since the classes are the same and there are no changes to CIV4UnitInfos.xml. The amount of adjustment to CIV4UnitClassInfos.xml is also much less.

Disadvantages: a lot of stuff to add to CIV4CivilizationInfos.xml; also, in spite of being less complicated to implement, it is (to me) more of a mess and less helpful to the player (in my test game I realized that unless you had a lot of stuff memorized it would be hard to know which ship type was for which role - is ship X that I see equivalent to my ship Y or ship Z or something else? It takes a bit of effort to find out what some civ's first generation battleship is - knowing what yours is called is no help since they are not linked in any way); adding a new civ would require adding new unit classes for all of its ships, other than the few shared types, and require entering all of those classes into all the other civ's Units sections to specify the unit type NONE for them.

2) Revamp the entire unit class system to make the various ship types into regular unique units. (I like this one a little better)

Determine the set of unit classes that you need. Each civ has units that match in function as per some table over in your B5 thread (a few pages back from the end in post 1338 is the last version I see). Each row in that table could be a unit class - 3 classes for the 3 fighter generations, 2 for carriers, 2 for support, 1 for ELINT, etc... (Plus keeping the existing classes for units that are not civ specific, like the orbital defensive units, construction ships, and such.)

Currently most of the unit classes have non-existent unit types as their default unit which match the description of what general type of unit it is - those names could become the basis for the unit class names that are used.

So the UNIT_SPACE_FIGHTER_I of your example unit class indicates a new unit class of UNITCLASS_SPACE_FIGHTER_I (or you could simplify it in this case to just UNITCLASS_FIGHTER_I). The UNIT_TRAGA of your example then gets this new unitclass in the unit info file and the old UNITCLASS_TRAGA goes away entirely. Each of the first generation fighter types for the other civs can then also be changed to have the new unit class and their old individual unit classes can be removed. You could use one of the existing unit classes for each of these, but it might be less clear in the future.

Each of the new "generic" unit classes needs to have a default unit specified. I'd suggest picking some civ, probably the EA, and declaring their units to the the "standard" types and setting their unit as the default unit type. You could use the existing unit classes for this one civ instead of creating all new generic unit classes.

Then in the CIV4CivilizationInfos.xml each civ gets it's Units entries adjusted so the each new class is listed with each having the matching unit type for that civ, Since each civ already has an entry that specifies each of that civ's unit tyes, so you'd just have to change the name of each unit class to the new class names.

Advantage: fewer unit classes leading to far fewer entries in CIV4CivilizationInfos.xml matching unit types to unit classes, also none (or very few) pointing to NONE; also in the civilopedia (and maybe some other places) the equivalent units are matched to each other as unique units are in regular BtS allowing for easier understanding of which ships have matching roles. Adding a new civ in the future would be slightly easier since you would not need to add any new unit classes for units that match the existing roles and would not need to list all of the other civ's specific unit classes in the new civ info to specify that they get type NONE for them or add their new ones to the other civs with type NONE either, and if no new unit classes were added then there would be no changes needed to the "Adv Unit Naming.ini" file.

Disadvantage: even with the fewer changes to CIV4CivilizationInfos.xml, it is more work as it includes adjustments to the unit class entries of most of the units in CIV4UnitInfos.xml and an extensive reworking of CIV4UnitClassInfos.xml and you'll need yet another new "Adv Unit Naming.ini" file (although this new one would be shorter that the last one since there would be a lot fewer unit classes).

I think that probably covers it.
 
I was just looking back at recent events here and noticed we never responded to this post. Whoops.

Hi, I got the following message when a pirate razed a city:

"Error in cityRazed event handler <bound method FinalFrontierEvents.onCityRazed of <FinalFrontierEvents.FinalFrontierEvents instance at 0x257995D0>

There were no problems with gameplay, it went on w/o problems.

There is a bug in the city razing code that has been fixed a couple of times but somhow managed to not get included in the actual patches. It should be fixed in 1.73 (I just compiled the files changed from 1.72 to 1.73 and the fix is in it).

The bug could produce bad effects, like the infamous "can't build buildings anymore" issue. If it doesn't, then I think it is pretty much pure luck.

Also, as a note: Scouts/Recon ships could maybe get some promotions more suited for scouts (as they can't really use combat promotions, esp. the weak scouts). Such as visibility (already there, but not in first selection), more movement, hiding esp.

Also, there are two seemingly identical promotions of +1 visibility (as I remember they have the same name, text and icon). Do they add up? They should have a different name (like Visibility 1 and Visibility 2).

Scouts currently have access to pretty much the same part upgrades as every other ship type. This includes the sensor and engine upgrades, although you are right that these are not immediately available to them. Also, since all of them but the Avower's Recon Ship UU get at least one sensor upgrade for free it is really not practical to take them - even if you manage to get enough prestige for your basic first generation scout to take the second sensor upgrade it is not a good choice since upgrading the ship to a delta scout would give it for free. I generally go for the first two armor upgrades (+15% defense each and therefore better than the weapon upgrades' +10% strength on both attack and defense for the attack disabled scouts) which also unlocks the first engine upgrade for some extra speed, taking that as my 3rd part upgrade. Getting a scout ship up to speed 2 is not only good for revealing new territory faster, it is also good for improving their survivability since that makes it as fast as the pirate destroyers.

The two identically named +1 visibility part upgrades can not be taken by any ship via earning prestige points. They are only used by the Avower's UUs, the three Recon Ship type ships, which get one or both for free depending on which generation it is (one for the original Recon Ship, and both for the delta and omega generations). If more generations of Sensor Station are ever added, they will probably use them too.
 
Depends. How easily can you follow SDK tutorials? bUnique is from Xienwolf's "An Idiot's Guide to Editing the DLL".
I've looked at that at least a dozen times and it makes no sense to me at all :blush:. Guess I'll be going with God-Emperors suggestion, which I did last night. I think I've resolved about 95% of the changes needed now, just a few small xml errors on loading the mod to resolve and I'll be happy.
 
Everything has now been done and the mod loads with no xml errors. There was a CtD but the game reloaded and I was able to continue testing for nearly 300 turns before having to retire to bed for some rest.
I'll carry on testing after I've merged the latest update (1.73) into the mod.
 
Hi all, I think I came across an error in the CIV4UnitSchema.xml.

The line with the error is here at 448
Code:
</ElementType>
		<ElementType name="UnitCombatAttackMods" content="eltOnly">
		<element type="UnitCombatAttackMod" minOccurs="0" maxOccurs="*"/>
	</ElementType>
I believe that the line beginning <ElementType name= has been indented one space too far and should look like this.
Code:
	</ElementType>
	<ElementType name="UnitCombatAttackMods" content="eltOnly">
		<element type="UnitCombatAttackMod" minOccurs="0" maxOccurs="*"/>
	</ElementType>
This would then be the same as other entries in the Schema. This however does not resolve the problem I am having with the schema failing to load the xml files after adding in the CombatAttack/CombatDefense tags on to one unit. Is this part of the Mod working correctly for anyone? Maybe I am using the wrong tags. Here is what I tried to add.
Entered directly after the UnitCombatMods
Code:
<UnitCombatAttackMods>
				<UnitCombatAttackMod>
					<UnitCombatType>UNITCOMBAT_RECON</UnitCombatType>
					<iUnitCombatMod>-50</iUnitCombatMod>
				</UnitCombatAttackMod>
				<UnitCombatAttackMod>
					<UnitCombatType>UNITCOMBAT_TRANSPORT</UnitCombatType>
					<iUnitCombatMod>-50</iUnitCombatMod>
				</UnitCombatAttackMod>
				<UnitCombatAttackMod>
					<UnitCombatType>UNITCOMBAT_FRIGATE</UnitCombatType>
					<iUnitCombatMod>-50</iUnitCombatMod>
				</UnitCombatAttackMod>
				<UnitCombatAttackMod>
					<UnitCombatType>UNITCOMBAT_DESTROYER</UnitCombatType>
					<iUnitCombatMod>-50</iUnitCombatMod>
				</UnitCombatAttackMod>
				<UnitCombatAttackMod>
					<UnitCombatType>UNITCOMBAT_CRUISER</UnitCombatType>
					<iUnitCombatMod>-50</iUnitCombatMod>
				</UnitCombatAttackMod>
				<UnitCombatAttackMod>
					<UnitCombatType>UNITCOMBAT_CAPITAL_SHIP</UnitCombatType>
					<iUnitCombatMod>-50</iUnitCombatMod>
				</UnitCombatAttackMod>
				<UnitCombatAttackMod>
					<UnitCombatType>UNITCOMBAT_CARRIER_SHIP</UnitCombatType>
					<iUnitCombatMod>-50</iUnitCombatMod>
				</UnitCombatAttackMod>
				<UnitCombatAttackMod>
					<UnitCombatType>UNITCOMBAT_STARBASE</UnitCombatType>
					<iUnitCombatMod>-50</iUnitCombatMod>
				</UnitCombatAttackMod>
			</UnitCombatAttackMods>
Is the problem because I have used a negative figure to reflect a -50 to attack strength when attacking the units shown?
 
The indentation of XML files does not actually matter, it is purely cosmetic for purposes of making it easier to read. (It matters in Python.)

Checking the schema file in FFP 1.73 and I don't see any difference in the indentation, but I do see an empty line above it - well, empty except a tab character. Whatever you are looking at it with might be showing it differently, or you have removed the empty line but not the extra tab, but it shouldn't matter.

Where are you adding them in the file? That should be last unless the unit also gets UnitCombatDefenseMods (which is really last). The tags need to be in the order they appear in the ElementType definition for the UnitInfo.
 
Back
Top Bottom