Quick question

I'm curious - is the code doing what its supposed to? Also, have you tested it in actual play because enabling the Python callback will add lag to your game. The only question is how much. :p (This is probably the reason it was disabled in the first place.)
I have tested it and it works as requested, the lag doesn't appear to have increased by too much but i haven't had much chance to play a full game as yet. Once i've gone further into the game if the lag has increased too much then i'll have to change it out for the original code but for now it's fine.
 
Its not the added unit classes as such I was worried about, but rather enabling the callback itself in PythonCallbackDefines.xml. Because the core game engine is making a Python callback anytime a unit is to be moved - and compared to the C++ code Python is awfully slow. And not just once per unit movement, but probably once for any tile that the AI is even considering (for path-finding, calculations and general decision making I guess).

But it would however be possible to only define the tRestrictedUnitClasses array once in any game, but that involves more than just copy-pasting some lines of code into one single location in one single file. Because its not optimal to fetch those unit class identifiers every single time the unit classes are evaluated. Pointless repetition of code is probably what causes lag in the game in the first place! :p

edit: You could actually try out this augmented code:
Code:
[B]	tRestrictedUnitClasses = False[/B]

	def unitCannotMoveInto(self,argsList):
		ePlayer = argsList[0]		
		iUnitId = argsList[1]
		iPlotX = argsList[2]
		iPlotY = argsList[3]

		# start movement restriction
		[B]if not self.tRestrictedUnitClasses:
			self.tRestrictedUnitClasses = (
				gc.getInfoTypeForString("UNITCLASS_LIANDRA"),
				gc.getInfoTypeForString("UNITCLASS_SAMPLE"),
				)[/B]
		pUnit = gc.getPlayer(ePlayer).getUnit(iUnitId)
		eUnitType = pUnit.getUnitType()
		eUnitClass = gc.getUnitInfo(eUnitType).getUnitClassType()
		if eUnitClass in self.tRestrictedUnitClasses:
			pPlot = gc.getMap().plot(iPlotX, iPlotY)
			return pPlot.getOwner() != ePlayer
		# end movement restriction

		return False
Changes marked. This should define the array once only, theoretically saving some nano-seconds each time, adding up to a full micro second in the course of a full game, or something. :rolleyes:

It would also be possible to build a data-base of sorts of all unit types and their unit classes, so that the unit class of any given unit type is only checked once. And the same could be done with actual CyUnit instances - their unit type/class would only be checked once - but this doesn't permit unit upgrades... :p But I'm not sure if indexing a Python dictionary really is faster than calling a handful SDK methods...

edit: Ok, its simply too much fun not to do this, so here's a quick solution for the unit types vs unit classes:
Code:
	tRestrictedUnitClasses = False
	[B]unitClassDict = dict()[/B]

	def unitCannotMoveInto(self,argsList):
		ePlayer = argsList[0]		
		iUnitId = argsList[1]
		iPlotX = argsList[2]
		iPlotY = argsList[3]

		if not self.tRestrictedUnitClasses:
			self.tRestrictedUnitClasses = (
				gc.getInfoTypeForString("UNITCLASS_LIANDRA"),
				gc.getInfoTypeForString("UNITCLASS_SAMPLE"),
				)
		pUnit = gc.getPlayer(ePlayer).getUnit(iUnitId)
		eUnitType = pUnit.getUnitType()
[B]		eUnitClass = self.unitClassDict.get(eUnitType, None)
		if eUnitClass == None:
			eUnitClass = gc.getUnitInfo(eUnitType).getUnitClassType()
			self.unitClassDict[eUnitType] = eUnitClass[/B]
		if eUnitClass in self.tRestrictedUnitClasses:
			pPlot = gc.getMap().plot(iPlotX, iPlotY)
			return pPlot.getOwner() != ePlayer

		return False
Anyone interested in learning Python should probably pay attention. :D

edit: I decided to take it up another notch:
Code:
	# initiate class variables
	tRestrictedUnitClasses = False
	unitClassDict = dict()
	[B]playerUnitDict = dict()
	iCurrentGameTurn = 0[/B]

	def unitCannotMoveInto(self,argsList):
		ePlayer = argsList[0]		
		iUnitId = argsList[1]
		iPlotX = argsList[2]
		iPlotY = argsList[3]
		# set unit class array once only
		if not self.tRestrictedUnitClasses:
			self.tRestrictedUnitClasses = (
				gc.getInfoTypeForString("UNITCLASS_LIANDRA"),
				gc.getInfoTypeForString("UNITCLASS_SAMPLE"),
				)
		# check each unit's type only once every turn
[B]		iGameTurn = gc.getGame().getGameTurn()
		tPlayerUnit = ePlayer, iUnitId
		if iGameTurn == self.iCurrentGameTurn:
			eUnitType = playerUnitDict.get(tPlayerUnit, None)
		else:
			eUnitType = None
			self.iCurrentGameTurn = iGameTurn
		if eUnitType == None:
			eUnitType = gc.getPlayer(ePlayer).getUnit(iUnitId).getUnitType()
			self.playerUnitDict[tPlayerUnit] = eUnitType[/B]
		# fetch each unit type's class only once
		eUnitClass = self.unitClassDict.get(eUnitType, None)
		if eUnitClass == None:
			eUnitClass = gc.getUnitInfo(eUnitType).getUnitClassType()
			self.unitClassDict[eUnitType] = eUnitClass
		# return True if restriction condition is met
		if eUnitClass in self.tRestrictedUnitClasses:
			pPlot = gc.getMap().plot(iPlotX, iPlotY)
			return pPlot.getOwner() != ePlayer
		# else return default False
		return False
Now, none of this is actually tested and might contain any number of errors. But the idea is not to check anything more than once, hopefully saving valuable computing time. This exercise also demonstrates why it would make more sense to implement any new game rules in the SDK! :p
 
You really don't need to test any of it, unless you really wanna try to get rid of some of the lag. :p Writing that stuff is just my idea of a "good time". :lol:
 
Yeah, I missed something:
Spoiler :
Code:
	# initiate class variables
	tRestrictedUnitClasses = False
	unitClassDict = dict()
	playerUnitDict = dict()
	iCurrentGameTurn = 0

	def unitCannotMoveInto(self,argsList):
		ePlayer = argsList[0]		
		iUnitId = argsList[1]
		iPlotX = argsList[2]
		iPlotY = argsList[3]
		# set unit class array once only
		if not self.tRestrictedUnitClasses:
			self.tRestrictedUnitClasses = (
				gc.getInfoTypeForString("UNITCLASS_LIANDRA"),
				gc.getInfoTypeForString("UNITCLASS_SAMPLE"),
				)
		# check each unit's type only once every turn
		iGameTurn = gc.getGame().getGameTurn()
		tPlayerUnit = ePlayer, iUnitId
		if iGameTurn == self.iCurrentGameTurn:
			eUnitType = [COLOR="Red"]self.[/COLOR]playerUnitDict.get(tPlayerUnit, None)
		else:
			eUnitType = None
			self.iCurrentGameTurn = iGameTurn
		if eUnitType == None:
			eUnitType = gc.getPlayer(ePlayer).getUnit(iUnitId).getUnitType()
			self.playerUnitDict[tPlayerUnit] = eUnitType
		# fetch each unit type's class only once
		eUnitClass = self.unitClassDict.get(eUnitType, None)
		if eUnitClass == None:
			eUnitClass = gc.getUnitInfo(eUnitType).getUnitClassType()
			self.unitClassDict[eUnitType] = eUnitClass
		# return True if restriction condition is met
		if eUnitClass in self.tRestrictedUnitClasses:
			pPlot = gc.getMap().plot(iPlotX, iPlotY)
			return pPlot.getOwner() != ePlayer
		# else return default False
		return False
Thinking about it, it would be possible to cache the conditional statement also. So if a unit has been considered for a plot earlier, then the callback returns whatever it did before - without having to run the rest of the code. The only thing is that unit type/class for a CyUnit instance might change during the game due to upgrades. So the unit type would also have to be cached... This is getting complicated, fast! :p

edit: I just realized that plot ownership will also vary from game turn to game turn, so this should have to be done on a turn to turn basis. :p
 
Ok, this is what the next generation of this code could look like:
Spoiler :
Code:
# start unit movement restriction code

	unitCannotMoveIntoDict = dict()
	tRestrictedUnitClasses = False
	unitClassDict = dict()

	def unitCannotMoveInto(self,argsList):

		def getUnitClass(eUnitType):
			eUnitClass = self.unitClassDict.get(eUnitType, None):
			if eUnitClass == None:
				eUnitClass = gc.getUnitInfo(eUnitClass).getUnitClassType()
				self.unitClassDict[eUnitType] = eUnitClass
			return eUnitClass

		def getRestrictedClasses():
			return ( 
				 gc.getInfoTypeForString("UNITCLASS_LIANDRA"),
				 gc.getInfoTypeForString("UNITCLASS_SAMPLE"),
				 )
			
		ePlayer, iUnit, iX, iY = argsList
		eUnitType = gc.getPlayer(ePlayer).getUnit(iUnit).getUnitType()
		tKey = ePlayer, eUnitType, iX, iY
		bRestricted = self.unitCannotMoveIntoDict.get(tKey, None)
		if bRestricted == None:
			if not self.tRestrictedUnitClasses:
				self.tRestrictedUnitClasses = getRestrictedClasses()
			eUnitClass = getUnitClass(eUnitType)
			if eUnitClass in self.tRestrictedUnitClasses:
				bRestricted = gc.getMap().plot(iX, iY).getOwner() != ePlayer
			else:
				bRestricted = False
			self.unitCannotMoveIntoDict[tKey] = bRestricted

		return bRestricted

# end unit movement restriction code
Next, add these lines in CvFinalFrontierEvents.py:
Spoiler :
Code:
[B]from CvGameUtils import CvGameUtils[/B]

	def onBeginGameTurn(self, argsList):
		'Called at the beginning of the end of each turn'

[B]		CvGameUtils.unitCannotMoveIntoDict = dict()[/B]
Now the cache is reset each game turn - otherwise it would keep bloating, eating away at memory and probably even causing lag! :eek:
 
Since we're doing speed enhancements, it dawned on me that you probably could define a new UnitCombatType in XML for these defensive vessels: UNITCOMBAT_DOMESTIC, or something. The change would of course have to be implemented everywhere, throughout the mod. But then there wouldn't be any need to look up various UnitClassTypes - just the one UnitCombatType. :D So this wouldn't be a restriction on unit classes - but rather on a specific unit combat type.
Spoiler :
Code:
# start unit movement restriction code

	unitCannotMoveIntoDict = dict()
	unitCombatDict = dict()

	def unitCannotMoveInto(self,argsList):
		ePlayer, iUnit, iX, iY = argsList
		eUnitType = gc.getPlayer(ePlayer).getUnit(iUnit).getUnitType()
		tKey = ePlayer, eUnitType, iX, iY
		bRestricted = self.unitCannotMoveIntoDict.get(tKey, None)
		if bRestricted == None:
			eUnitCombat = self.unitCombatDict.get(eUnitType, None):
			if eUnitCombat == None:
				eUnitCombat = gc.getUnitInfo(eUnitClass).getUnitCombatType()
				self.unitCombatDict[eUnitType] = eUnitCombat
			if eUnitCombat == gc.getInfoTypeForString("[COLOR="Red"]UNITCOMBAT_DOMESTIC[/COLOR]"):
				bRestricted = gc.getMap().plot(iX, iY).getOwner() != ePlayer
			else:
				bRestricted = False
			self.unitCannotMoveIntoDict[tKey] = bRestricted
		return bRestricted

# end unit movement restriction code
That other code should still be added to the other .py file to reset the cache in between turns.
 
Somehow i missed these responses.
Since we're doing speed enhancements, it dawned on me that you probably could define a new UnitCombatType in XML for these defensive vessels: UNITCOMBAT_DOMESTIC, or something. The change would of course have to be implemented everywhere, throughout the mod. But then there wouldn't be any need to look up various UnitClassTypes - just the one UnitCombatType. So this wouldn't be a restriction on unit classes - but rather on a specific unit combat type.
I already have this i used UNITCOMBAT_DEFENCE to apply to the units i wanted protecting the star systems. I can now add the last bit of Python you created along with UNITCOMBAT_DEFENCE and transform the B5 mod quite a bit.
 
OK, i've added in the following stuff.

Into the CvGameUtils.py

Code:
# start unit movement restriction code

	unitCannotMoveIntoDict = dict()
	unitCombatDict = dict()

	def unitCannotMoveInto(self,argsList):
		ePlayer, iUnit, iX, iY = argsList
		eUnitType = gc.getPlayer(ePlayer).getUnit(iUnit).getUnitType()
		tKey = ePlayer, eUnitType, iX, iY
		bRestricted = self.unitCannotMoveIntoDict.get(tKey, None)
		if bRestricted == None:
			eUnitCombat = self.unitCombatDict.get(eUnitType, None):
			if eUnitCombat == None:
				eUnitCombat = gc.getUnitInfo(eUnitClass).getUnitCombatType()
				self.unitCombatDict[eUnitType] = eUnitCombat
			if eUnitCombat == gc.getInfoTypeForString("UNITCOMBAT_DEFENCE"):
				bRestricted = gc.getMap().plot(iX, iY).getOwner() != ePlayer
			else:
				bRestricted = False
			self.unitCannotMoveIntoDict[tKey] = bRestricted
		return bRestricted

# end unit movement restriction code

Into the CvFinalFrontierEvents.py

Code:
from CvGameUtils import CvGameUtils

	def onBeginGameTurn(self, argsList):
		'Called at the beginning of the end of each turn'

		CvGameUtils.unitCannotMoveIntoDict = dict()
and into the PythonCallbackDefines.xml

Code:
<Define>
	<DefineName>USE_UNIT_CANNOT_MOVE_INTO_CALLBACK</DefineName>
	<iDefineIntVal>1</iDefineIntVal>
</Define>
Unfortunately all i now get are a series of Python exceptions as the mod loads. Please advise. Have i missed something?
 
If its a syntax error, then it would be because of this line:
Code:
			eUnitCombat = self.unitCombatDict.get(eUnitType, None)[COLOR="Red"]:[/COLOR]
That colon at the end is probably a left over from some revision of the code where the method invocation was part of a conditional statement.

If you get further errors, please make sure to post any exceptions.
 
OK, new update for you. I made the minor change you mentioned but i am still getting a Python error on load. The message i get is split over quite a few screens but each message reads as follows.

1st line - Traceback (most recent call last):
2nd line - File "<string>",line 1, in ?
3rd line - File "<string>",line 52, in load_module
4th line - File "CvEventInterface", line 8, in ?
5th line - File "<string>", line 35, in load_module
6th line - File "<string>", line 13, in_get_code
7th line - File "
8th line - CvFinalFrontierEvents
9th line - ", line
10th line - 45
11th line -
12th line -
13th line - self.parent = CvEventManager.CvEventManager
14th line -
15th line -
16th line -
17th line -
18th line - ^
19th line - IndentationError:
20th line - expected an indented block
21st line -
22nd line - Failed to load python module CvEventInterface.

I'm guessing i have something wrongly indented somewhere but everything is as you said to do in your previous post.
 
It seems like you did a mistake on some line, probably replaced tabs used for indentation with blank spaces, or simply deleted the indentation. Zip up your copy of CvFinalFrontierEvents.py and attach here.

You only removed that one colon, right?
 
It seems like you did a mistake on some line, probably replaced tabs used for indentation with blank spaces, or simply deleted the indentation. Zip up your copy of CvFinalFrontierEvents.py and attach here.

You only removed that one colon, right?
Yes, just the one colon you highlighted in red.

Here is the file you requested.
 
Since the lines don't match I suspect that the culprit is the text encoding. The file you attached seems to be encoded as "Windows 1252: Western European" when it should be "Unicode, UTF-8". Just make sure it says UTF-8 when you select the encoding before saving.
 
What text editor are you using?
 
This is actually very useful... Now I only have to think up a unit to use it... Perhaps a Guerilla? Although I could also make a Guerilla with hidden nationality, which is also nice, although I remember that that didn't work or something... Anyway, this can be used in the original game, right? I understand that, for example, some of the code has to go in CvEvents.py (if that exists even) instead of in CvFinalFrontierEvents.py, but, besides such things, the code is compatible with normal BTS (and, in extension, RevDCM), right?
 
Top Bottom