[Python] onUnitKilled code not working - why??

Maniac

Apolyton Sage
Joined
Nov 27, 2004
Messages
5,588
Location
Gent, Belgium
When an aquaformer or former is destroyed by a barbarian, I want a damaged (aqua)former unit to be created.

This code seemed to do the job:

Code:
	def onCombatResult(self, argsList):
		'Combat Result'
		self.parent.onCombatResult(self, argsList)
		pWinner,pLoser = argsList
		playerX = PyPlayer(pWinner.getOwner())
		unitX = PyInfo.UnitInfo(pWinner.getUnitType())
		playerY = PyPlayer(pLoser.getOwner())
		unitY = PyInfo.UnitInfo(pLoser.getUnitType())

		pPlayer = gc.getPlayer(pWinner.getOwner())
		pLPlayer = gc.getPlayer(pLoser.getOwner())

		if pWinner.getOwner() == gc.getBARBARIAN_PLAYER():
			pPlot = pLoser.plot()
			if pLoser.getUnitType() == gc.getInfoTypeForString('UNIT_AQUAFORMER'):
				newUnit = pLPlayer.initUnit(gc.getInfoTypeForString('UNIT_AQUAFORMER_DAMAGED'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
			elif pLoser.getUnitType() == gc.getInfoTypeForString('UNIT_FORMER'):
				newUnit = pLPlayer.initUnit(gc.getInfoTypeForString('UNIT_FORMER_DAMAGED'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)

However I recently noticed that when a stack of two or more aquaformers is attacked, only one 'damaged aquaformer' unit is created. Thus I tried to experiment with other similar python functions, resulting in this:

Code:
	def onUnitKilled(self, argsList):
		'Unit Killed'
		self.parent.onUnitKilled(self, argsList)
		unit, iAttacker = argsList
		player = PyPlayer(unit.getOwner())
		attacker = PyPlayer(iAttacker)

		pAttacker = gc.getPlayer(iAttacker)
		iPlayer = unit.getOwner()
		pPlayer = gc.getPlayer(iPlayer)

		if pAttacker == gc.getBARBARIAN_PLAYER():
			pPlot = unit.plot()
			if unit.getUnitType() == gc.getInfoTypeForString('UNIT_AQUAFORMER'):
				newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNIT_AQUAFORMER_DAMAGED'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
			elif unit.getUnitType() == gc.getInfoTypeForString('UNIT_FORMER'):
				newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNIT_FORMER_DAMAGED'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)

This code doesn't create any units at all! Can anyone tell why??
And generally does anyone have an idea to create as many 'damaged' units as there were in the stack in the first place?
 
Try changing
if pAttacker == gc.getBARBARIAN_PLAYER():
to
if (pAttacker == gc.getBARBARIAN_PLAYER()):
For some reason you need to enclose if statements with () in Civ4. Or at least I've found that doing this has corrected problems for me in the past.
 
It doesn't help. I've noticed that in the SDK all the if statements are between brackets, but I don't think that's necessary in python.
 
Don't see anything obvious. The calls to self.parent.* are weird to me, but you may be using a different method of extending the event manager than I'm used to. I'd start with the usual:

Check the PythonErr.log for error messages.

Throw a debug print into your onUnitKilled handler and make sure it's actually executing. If so, put some more to see that all the values are returning what you think they should be. If not, work backwards to make sure the handler is added properly, etc.
 
I agree with Dresden - add some pyPrint statements to onUnitKilled. Perhaps the code isn't making it there due to other issues.

Can you get all the units on the plot? I don't know the Python API well enough at this point to know...
 
Don't see anything obvious. The calls to self.parent.* are weird to me, but you may be using a different method of extending the event manager than I'm used to.

It's the method Firaxis uses in their mods. You can't argue with that I guess. It works for all other functions.

Throw a debug print into your onUnitKilled handler and make sure it's actually executing. If so, put some more to see that all the values are returning what you think they should be. If not, work backwards to make sure the handler is added properly, etc.
I agree with Dresden - add some pyPrint statements to onUnitKilled. Perhaps the code isn't making it there due to other issues.

How do you write a print message?? I have never done that before.

I've tried out this code, but nothing appears on screen or so. Where am I supposed to get these messages?

Code:
		print("print onunitkilled")
		CvUtil.pyPrint('utilprint onunitkilled')
		if (pAttacker == gc.getBARBARIAN_PLAYER()):
			print("print isbarbarian")
			CvUtil.pyPrint('utilprint isbarbarian')
			pPlot = unit.plot()
			if unit.getUnitType() == gc.getInfoTypeForString('UNIT_AQUAFORMER'):
				newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNIT_AQUAFORMER_DAMAGED'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
				print("print unit created?")
				CvUtil.pyPrint('utilprint unit created?')
			elif unit.getUnitType() == gc.getInfoTypeForString('UNIT_FORMER'):
				newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNIT_FORMER_DAMAGED'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)

Can you get all the units on the plot? I don't know the Python API well enough at this point to know...

That's possible, but I suspect the list would be empty, because the units have been destroyed at that point.

Does anyone know where in the SDK is the code which automatically destroys zero-combat-strength units in a stack?

Other possibility is that the barbarians don't kill but rather capture the units, but then instantly delete them. Any way this theory can be tested??
 
Use CvUtil.pyPrint(str message) to print out a message to the PythonDbg.log file in C:\Documents and Settings\XXXX\My Documents\My Games\Beyond the Sword\Logs. In order for the log file to show up, set

[DEBUG]
LoggingEnabled = 1

in your ini file. I am looking for a way to send a message to the UI in a friendly manner (I am sure there is somewhere, just haven't found it yet) so that I don't have to check the logs so much...
 
How do you write a print message?? I have never done that before.

I've tried out this code, but nothing appears on screen or so. Where am I supposed to get these messages?

Code:
		print("print onunitkilled")
		CvUtil.pyPrint('utilprint onunitkilled')
		if (pAttacker == gc.getBARBARIAN_PLAYER()):
			print("print isbarbarian")
			CvUtil.pyPrint('utilprint isbarbarian')
			pPlot = unit.plot()
			if unit.getUnitType() == gc.getInfoTypeForString('UNIT_AQUAFORMER'):
				newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNIT_AQUAFORMER_DAMAGED'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
				print("print unit created?")
				CvUtil.pyPrint('utilprint unit created?')
			elif unit.getUnitType() == gc.getInfoTypeForString('UNIT_FORMER'):
				newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNIT_FORMER_DAMAGED'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
Output from CvUtil.pyPrint() and generic print() statements goes to the debug log: PythonDbg.log

Ninja Edit: To log a message in-game instead of (or in addition to) the text log, you can use CyInterface.addMessage() or CyInterface.addImmediateMessage()

continued... said:
Other possibility is that the barbarians don't kill but rather capture the units, but then instantly delete them. Any way this theory can be tested??
Here are the results of a couple of tests run under BTS 3.17 (no mods other than BUG) with the following onUnitKilled handler:
Code:
	def onUnitKilled(self, argsList):
		'Unit Killed'
		unit, iAttacker = argsList
		player = PyPlayer(unit.getOwner())
		attacker = PyPlayer(iAttacker)
		if (not self.__LOG_UNITKILLED):
			return
		CvUtil.pyPrint('Player %d Civilization %s Unit %s was killed by Player %d' 
			%(player.getID(), player.getCivilizationName(), unit.getName(), attacker.getID()))
Test #1: I had a stack of 3 workers protected by a warrior and I gave the barbs a nearby redcoat.
Code:
[COLOR="Red"]PY:Player 0 Civilization Zulu Empire Unit East Warrior (Warrior) was killed by Player 18[/COLOR]
19:48:42 DEBUG: Timer - findDealsByPlayerAndType took 0 ms
19:48:42 DEBUG: Timer - scores took 7 ms
19:48:43 DEBUG: Timer - findDealsByPlayerAndType took 0 ms
19:48:43 DEBUG: Timer - scores took 7 ms
[COLOR="Red"]PY:Player 0 Civilization Zulu Empire Unit East A (Worker) was killed by Player 18
PY:Player 0 Civilization Zulu Empire Unit East B (Worker) was killed by Player 18
PY:Player 0 Civilization Zulu Empire Unit East C (Worker) was killed by Player 18[/COLOR]
In this case, an onUnitKilled event was generated for each worker although the workers were not killed at the same time as the warrior. The "DEBUG: Timer stuff" is unrelated debug info from the BUG mod but it shows that there was a time lapse.

Test #2: 3 unprotected workers and a barb redcoat:
Code:
19:50:55 DEBUG: Timer - findDealsByPlayerAndType took 0 ms
19:50:55 DEBUG: Timer - scores took 7 ms
19:50:55 DEBUG: Timer - findDealsByPlayerAndType took 0 ms
19:50:55 DEBUG: Timer - scores took 7 ms
[COLOR="Red"]PY:Player 0 Civilization Zulu Empire Unit West B (Worker) was killed by Player 18
PY:Player 0 Civilization Zulu Empire Unit West C (Worker) was killed by Player 18[/COLOR]
In this case only 2 of the 3 workers had onUnitKilled events generated; the first (who was probably the "defender") just mysteriously disappeared. It is possible the barbs captured and deleted it since deletion doesn't seem to trigger an obvious event (neither onUnitKilled nor onUnitLost) but it's something you'll have to deal with to accomplish your goal.

As for the SDK, some likely starting points would be CvUnit::updateCombat() and CvUnit::kill()
 
Ninja Edit: To log a message in-game instead of (or in addition to) the text log, you can use CyInterface.addMessage() or CyInterface.addImmediateMessage()

Thanks Dresden! I am going to check it out.
 
In this case, an onUnitKilled event was generated for each worker although the workers were not killed at the same time as the warrior.

Thanks for the test.
So I guess the conclusion is onUnitKilled should in theory be working and give the desired result??

In this case only 2 of the 3 workers had onUnitKilled events generated; the first (who was probably the "defender") just mysteriously disappeared. It is possible the barbs captured and deleted it since deletion doesn't seem to trigger an obvious event (neither onUnitKilled nor onUnitLost) but it's something you'll have to deal with to accomplish your goal.

That's actually perfect. This means that by keeping the code in both onCombatresult and onUnitKilled, you should always get the right amount of units. Provided that the onUnitKilled code can actually be made to work...

I added an in-game message to the code btw. But it gives a python exception, which I don't understand at all. :confused: I just grabbed a line from elsewhere in my event manager, and changed the pPlayer and text content. What could possibly be wrong?? :confused:

Code:
CyInterface().addMessage(pPlayer,True,25,'onunitkilled','AS2D_BOOM',1,'Art/Interface/Buttons/TerrainFeatures/Xenofungus.dds',ColorTypes(8),pPlot.getX(),pPlot.getY(),True,True)
 
Just use addImmediateMessage(message, "") for debugging output to the screen. No colors, no player to send the message to, no plot x or y, nada. Just a message and an empty string (for no sound).

If you wanna get crazy, change "" to the path to a sound file. :cool:
 
First parameter for addMessage should be the player ID number, not the player object.

Aargh, I keep making silly mistakes like that.
On the bright side, it actually made me discover the problem.

In this line
Code:
if (pAttacker == gc.getBARBARIAN_PLAYER()):
I'm comparing a pointer to (I assume) an ID.
So I just needed to replace pAttacker with iAttacker. :mischief:

Thanks for replying to my silly-talk in this thread!
 
Top Bottom