Python: What value does pNukeUnit have?

The PythonErr.log file will be easier to read, but your error is at the very bottom of the file you posted:

def onNukeExplosion(self, argsList):
^
IndentationError: unindent does not match any outer indentation level

In Python, indentation (whitespace) matters. Make sure the whitespace before the "def" on that line is the same as the other "def" lines. Then, make sure the indentation is correct after it.

This is one of the major first gripes of Python, but if you set up a decent editor to use tabs instead of spaces, you'll be a happy camper and get used to it pretty quick. I resisted learning Python for this very reason, and I got over it fast.
 
I just manualy set every single line to +1 indent (what was needed)
Now I got this:

IndentationError: expected an indented block
load_module CvAppInterface

And I am not getting any PythonErr.log only the PythonErr2.log
 
You need to turn on logging to get PythonErr.log. I assume you have exception popups turned off because you aren't seeing popups for each error, right? You can set both in the CivilizationIV.ini file in the My Games BTS folder. Look for "logging"

You didn't show which line had the indentation error, but clearly there still is one. What editor are you using? You can post your code here inside
Code:
 tags (use the # button above the editor window) if you want and I'll look at it. Make sure to include a few lines of context above and below the function you're editor. Or ZIP up the whole file and attach it to a post.
 
I am using Notead, word wrap naturaly disabled.
I have uploaded the whole thing, could you just make it work? I am usless when it comes to python.
 

Attachments

  • zip.zip
    10.3 KB · Views: 49
Notepad won't cut it. You can't tell what the spacing is. Try Notepad++, UltraEdit, Eclipse, or any other real editor. :)

Replace just the onNukeExplosion() function with the following.

Code:
	def onNukeExplosion(self, argsList):
		'Nuke Explosion'
		pPlot, pNukeUnit = argsList
	
		CvUtil.pyPrint('Nuke detonated at %d, %d Crazy santa'
		%(pPlot.getX(), pPlot.getY()))
		if (pNukeUnit is not None and pNukeUnit.getUnitType() == gc.getInfoTypeForString('UNIT_ICBM')):
			iX = pPlot.getX()
			iY = pPlot.getY()
			tt_desert = gc.getInfoTypeForString( 'TERRAIN_DESERT' )
			tt_plain = gc.getInfoTypeForString( 'TERRAIN_PLAINS' )
			tt_grass = gc.getInfoTypeForString( 'TERRAIN_GRASS' )
			tt_tundra = gc.getInfoTypeForString( 'TERRAIN_TUNDRA' )
			tt_snow = gc.getInfoTypeForString( 'TERRAIN_SNOW' )
			tt_ocean = gc.getInfoTypeForString( 'TERRAIN_OCEAN' )
			for iXLoop in range(iX - 1, iX + 2, 1):
				for iYLoop in range(iY - 1, iY + 2, 1):
					pPlot = CyMap().plot(iXLoop, iYLoop)
					if ( pPlot.getTerrainType()==tt_grass ):
						pPlot.setTerrainType(gc.getInfoTypeForString( "TERRAIN_COAST" ), 1, 1)
					elif ( pPlot.getTerrainType()==tt_plain ):
						pPlot.setTerrainType(gc.getInfoTypeForString( "TERRAIN_COAST" ), 1, 1)
					elif ( pPlot.getTerrainType()==tt_tundra ):
						pPlot.setTerrainType(gc.getInfoTypeForString( "TERRAIN_COAST" ), 1, 1)
					elif ( pPlot.getTerrainType()==tt_snow ):
						pPlot.setTerrainType(gc.getInfoTypeForString( "TERRAIN_COAST" ), 1, 1)
					elif ( pPlot.getTerrainType()==tt_ocean ):
						pPlot.setTerrainType(gc.getInfoTypeForString( "TERRAIN_COAST" ), 1, 1)
					elif ( pPlot.getTerrainType()==tt_desert ):
						pPlot.setTerrainType(gc.getInfoTypeForString( "TERRAIN_COAST" ), 1, 1)
 
I tried using dreamviver but the resaults were discouraging.

On the other hand, the code finaly worked! Yipee! All fear the fusion buster.
Thanks eternaly!:D
 
:bump: sorry for bumping, but i have again a problem at this location, but a bit different one.

I'm using meltdowns in my mod for the terraforming.
Although the buildings which cause meltdowns are nukeimmune during xml they are destroyed, but i don't want that.
So the easiest way is to build them up again after the meltdown.
A meltdown triggers also onNukeExplosion (am i wrong, when i guess, that this was an internal circuit, because they were to lazy to code a different thing?).
But i get a CyUnitObject instead of a building (caused through the above mentioned, i think), and pNukeUnit.getUnitType() returns a -1, which is logical, because it's not a unit.

Can someone imagine a way, how to get the building, which exploded?
I have 3 regular buildings and 6 wonders, which can "meltdown", so just replacing or guessing will not do it.
But i think, it can't be done, or does anybody have an idea?
 
CvCity::doMeltdown() starts by calling CvGameUtil.doMeltdown(CyCity). Then it loops over all the buildings that can meltdown, rolling dice to see if there is a meltdown. The first one to "succeed" causes the function to call CvPlot::nukeExplosion() with a NULL pNukeUnit.

If you aren't going to make SDK changes, the only way I see to do this is to store the list of all buildings that can meltdown during the call to CvGameUtil.doMeltdown(CyCity) and comparing it to the new list of buildings in the onNukeExplosion event.

CvGameUtils.py

Code:
import MeltdownUtil    # put this line at top with other imports
...
def doMeltdown(self,argsList):
	pCity = argsList[0]
	MeltdownUtil.storeBuildingsBeforeMeltdown(pCity)
	return False

CvEventManager.py

Code:
import MeltdownUtil    # put this line at top with other imports
...
def onNukeExplosion(self, argsList):
	'Nuke Explosion'
	pPlot, pNukeUnit = argsList
	CvUtil.pyPrint('Nuke detonated at %d, %d'
		%(pPlot.getX(), pPlot.getY()))
	if pPlot.isCity():
		MeltdownUtil.restoreBuildingsAfterMeltdown(pPlot.getPlotCity())

MeltdownUtil.py

Code:
## MeltdownUtil.py
##
## Restores the building that had the meltdown.

from CvPythonExtensions import *

gc = CyGlobalContext()
MELTDOWN_BUILDINGS = None
buildingsBeforeMeltdown = None

def init():
	# create list of buildings that can have meltdown
	global MELTDOWN_BUILDINGS
	if MELTDOWN_BUILDINGS is None:
		MELTDOWN_BUILDINGS = []
		for eBldg in range(gc.getNumBuildingInfos()):
			info = gc.getBuildingInfo(eBldg)
			if info.getNukeExplosionRand() != 0:
				MELTDOWN_BUILDINGS.append(eBldg)

def getMeltdownBuildings(pCity):
	init()
	# store number of buildings city has that can meltdown to restore later
	buildings = {}
	for eBldg in MELTDOWN_BUILDINGS:
		iCount = pCity.getNumRealBuilding(eBldg)
		if iCount > 0:
			buildings[eBldg] = iCount

def storeBuildingsBeforeMeltdown(pCity):
	global buildingsBeforeMeltdown
	buildingsBeforeMeltdown = getMeltdownBuildings(pCity)

def restoreBuildingsAfterMeltdown(pCity):
	global buildingsBeforeMeltdown
	if buildingsBeforeMeltdown:
		newBuildings = getMeltdownBuildings(pCity)
		for eBldg, iCount in buildingsBeforeMeltdown.iteritems():
			if eBldg not in newBuildings:
				pCity.setNumRealBuilding(eBldg, iCount)
				break
		buildingsBeforeMeltdown = None

I should point out that the list of buildings that can meltdown will be created for every city every turn whether or not there is an actual meltdown. While this shouldn't cause a noticeable slowdown, it is something to keep in mind.
 
:wow: holy...!

No, i don't want to make SDK changes...okay, atm i don't have the abilities to do them, so i've tried your python solution.
Great, i couldn't have done something like that myself :goodjob:!

But i get a python exception, which says, that buildingsBeforeMeltdown has no function iteritems(), and i don't fully understand, what you're doing, so i can't solve it :(.
 
I see three possible explanations:

1. I made some mistake that I can't see now. If it truly said that the buildingsBeforeMeltdown object doesn't have the iteritems() function, then this is the case. Can you paste the exact error message?

The other two would happen if the error was "NoneType has no method iteritems()."

2. You dropped a nuke instead of causing a meltdown. I have fixed the restoreBuildingsAfterMeltdown() function in my post above to account for this.

3. You didn't hook up your GameUtils, so only the onNukeExplosion event is being called but not doMeltdown. Can you add some print statements to the doMeltdown() function to verify that it's being called? Make this the first line of the function:

Code:
CyInteface().addImmediateMessage("doMeltdown called", "")
 
Oops, there was an error in onNuckExplosion. I have fixed the code above: added if pPlot.isCity() test and grab the city from the plot using pPlot.getPlotCity().
 
Can you tell me how it doesn't work? I assume there is simply no effect, and you aren't getting any Python errors.

Given that, the best way to proceed is to add debugging (message) statements to the code to verify that a) it's being called and b) it's what you expect to see.

You can print to the screen using

Code:
CyInterface().addImmediateMessage(<string>, "")

that I used above or

Code:
CyInterface().addImmediateMessage(<string>, "")

Make these changes:

Code:
def onNukeExplosion(self, argsList):
	'Nuke Explosion'
	pPlot, pNukeUnit = argsList
	CvUtil.pyPrint('Nuke detonated at %d, %d'
		%(pPlot.getX(), pPlot.getY()))
	if pPlot.isCity():
		pCity = pPlot.getPlotCity()
		CyInterface().addImmediateMessage("Nuke in " + pCity.getName(), "")
		MeltdownUtil.restoreBuildingsAfterMeltdown(pCity)

and

Code:
def init():
	# create list of buildings that can have meltdown
	global MELTDOWN_BUILDINGS
	if MELTDOWN_BUILDINGS is None:
		[B]CvUtil.pyPrint("MELTDOWN BUILDINGS:")[/B]
		MELTDOWN_BUILDINGS = []
		for eBldg in range(gc.getNumBuildingInfos()):
			info = gc.getBuildingInfo(eBldg)
			if info.getNukeExplosionRand() != 0:
				[B]CvUtil.pyPrint("- " + info.getDescription())[/B]
				MELTDOWN_BUILDINGS.append(eBldg)

def getMeltdownBuildings(pCity):
	init()
	# store number of buildings city has that can meltdown to restore later
	buildings = {}
	for eBldg in MELTDOWN_BUILDINGS:
		iCount = pCity.getNumRealBuilding(eBldg)
		[B]info = gc.getBuildingInfo(eBldg)
		CvUtil.pyPrint("- %d %s" % (iCount, info.getDescription()))[/B]
		if iCount > 0:
			buildings[eBldg] = iCount

def restoreBuildingsAfterMeltdown(pCity):
	global buildingsBeforeMeltdown
	if buildingsBeforeMeltdown:
		[B]CvUtil.pyPrint("RESTORING BUILDINGS:")[/B]
		newBuildings = getMeltdownBuildings(pCity)
		for eBldg, iCount in buildingsBeforeMeltdown.iteritems():
			if eBldg not in newBuildings:
				[B]info = gc.getBuildingInfo(eBldg)
				CvUtil.pyPrint("- %d %s" % (iCount, info.getDescription()))[/B]
				pCity.setNumRealBuilding(eBldg, iCount)
				break
		buildingsBeforeMeltdown = None
	[B]else:
		CvUtil.pyPrint("NOT A MELTDOWN")[/B]

Next, run a test and post the file Logs/PythonDbg.log starting with the first "MELTDOWN BUILDINGS:" line. Also, describe your test here, for example:

0. Changed Nuclear Plant to have 100% meltdown chance.
1. Added an NP to London.
2. Forced a meltdown.
3. The NP was destroyed, no error messages were seen.
4. Here's the file: ...
 
Found an error in your code, and had to import CvUtil in the MeltDownUtil.py to get the things printed, but still doesn't work :(.

In RestoreBuildingsAfterMeltdown it jumps into the else-clause :confused:.

In the test, i added all buildings besides one (i forgot it) to my first city, and waited for a meltdown (came fast), but the building was destroyed (no error).
LogFile is attached.

A bit strange: The building "biosphere" is a free building from the palace, so it's not destroyed through a meltdown, but the function also doesn't count it :confused:.
 

Attachments

  • PythonDbg.zip
    3.7 KB · Views: 54
Free buildings are not destroyed during a meltdown as per the SDK code.

Wow, I missed something obvious, but that's what happens when you can't test code you're writing.

Code:
def getMeltdownBuildings(pCity):
	init()
	# store number of buildings city has that can meltdown to restore later
	buildings = {}
	for eBldg in MELTDOWN_BUILDINGS:
		iCount = pCity.getNumRealBuilding(eBldg)
		info = gc.getBuildingInfo(eBldg)
		CvUtil.pyPrint("- %d %s" % (iCount, info.getDescription()))
		if iCount > 0:
			buildings[eBldg] = iCount
	[B]return buildings[/B]

Looking at the log file, it seems like the call to doMeltdown doesn't immediately precede the onNukeExplosion event for the same city. We'll find out after you test again, though.
 
Wow, I missed something obvious, but that's what happens when you can't test code you're writing.

:wow: i couldn't create anything, which works, without testing.

My professor has said, that the code from the best programmers has still one error in 10 lines of code, i think, with this, we have 4 erros in 40 lines :goodjob:.



Can't test it atm, i do not have access to my civ-pc for this weekend, will have to wait until monday :(.
 
Top Bottom