Dungeon Adventure MOD MOD

ZOMG! That's a brilliant idea! I should abandon the DA mod completely and concentrate instead on making "Civ4:Pokemon Spasm" a reality! :lol:

Or how about: "Civ4: The Arbuckle Destiny?"
 
Fair enough. No Pokemon mods for me. [secretly sighs in relief]

Got a python question for people:

I am trying to make a call that would check to see if a unit has one move left and, if so, deny them access to a certain feature. It will NOT work for me to just up the move cost of this type of feature -- I have plans for the feature that precludes using this easy way out.

I am thinking that some sort of addition to onUnitMove would work.

Is there a python function that can check a unit to see how many moves it has left? I know it can check for Base moves, but I need unexpended moves.

Also is there a logical way to code the denial of entry bit? Conceptually, I was thinking of a call in onUnitMove that goes something like:

if the new plot is xFeature,
and if the unit moving has only one move left,
then refuse access/make the feature impassable.

Is there any way to dynamically assign the "terrain and feature impassable" section that we see in the UnitInfos.xml file for a specific unit?

Your insight would be much appreciated!
 
here is my helper function for getting moves left :)
Code:
def getMovesLeft(pUnit): return (pUnit.movesLeft() / gc.getMOVE_DENOMINATOR())
 
Isn't there a CanMove function? Might be a different thing, I'm working from vague memory of posted code here :)
 
in CvGameUtils there is this function:
Code:
def unitCannotMoveInto(self,argsList):
	ePlayer = argsList[0]		
	iUnitId = argsList[1]
	iPlotX = argsList[2]
	iPlotY = argsList[3]
	return False

If it returns true the unit cannot move into the tile.
To make the game actually use it you have to set this:

Code:
<Define>
	<DefineName>USE_UNIT_CANNOT_MOVE_INTO_CALLBACK</DefineName>
		<iDefineIntVal>0</iDefineIntVal>
</Define>

...in PythonCallbackDefines.xml to 1.
 
Though it may be better to use Kael's new tags for 0.33 to make individual Tiles become Python Active. As then you avoid doing a python check for every move of every unit.

Where can I find more info on that? Would it apply to the Scions "Haunted Lands" terrain? Here's a snippet of the associated python. It runs in CvEventManager's "onBeginGameTurn":

Spoiler :

Code:
		for i in range (CyMap().numPlots()):
			pPlot = CyMap().plotByIndex(i)
			if pPlot.getFeatureType() == iSC1:
				for i in range(pPlot.getNumUnits()):
					pUnit = pPlot.getUnit(i)			
					if...
 
You can find more information on that in the Bug thread for 0.33, which will be posted within 24 hours or so I imagine :) And in the Python itself from 33. Really there is not too much more to know beyond that though. It is a nifty trick with the DLL to make it ask a plot in C++ when a unit moves there if the plot would like to do anything using Python. If it responds with an affirmative, then it will go and check the python for the plot based on the stored function call. Speeds up the code massively I imagine.
 
Okay - I tried this as a test of the new function:

1. In the CvEventManager.py file I added the new helper:

Code:
	def getMovesLeft(pUnit): 
		return (pUnit.movesLeft() / gc.getMOVE_DENOMINATOR())

2. And later in the same file, as a conditional check under the onUnitMove def, I added this:

Code:
		if pUnit.getMovesLeft() == 1:
			self.addPopup(CyTranslator().getText("TXT_KEY_POPUP_MOVECHECK",()))

... and this is not working. The game starts up fine, but no longer does ANY of the onUnitMove checks.

Any ideas what I have done wrong?
 
I got something else to work.

I just set the PythonCallbackDefine to 1, as suggested, and added some new code directly to the unitCannotMoveInto def.

This may eventually end up being expensive on the computational budget, but right now it works beautifully!

For some reason, doing these checks in the onUnitmove def ended up being buggy. I'd still like to plumb into that a bit more to see if I can untangle it, but for now I'll stick with the code that works!
 
I assume you are attempting to call the function which you wrote, you do that with:

self.getMovesLeft(pUnit)


And if I remember properly, when you defined the function you needed to define it as

def getMovesLeft (self, pUnit)


All Python functions include self in their pass line, not entirely certain why.
 
Okay everyone -- here is a challenge for you:

I am trying to produce a python snippet that would apply a spell effect to all plots of the same terrain type that are contiguous to the plot of the casting unit.

So, for example, if a spellcaster in the middle of a desert casts this spell, the effect would be applied to all desert plots that are a part of that regional desert (but NOT all desert plots all over the world).

Sure, you can search plot by plot in a big box arounf the caster, but this is susceptible to two problems:

1. There may be plots still "connected" to the regional terrain type that fall outside the box, and thus do get affected.
2. There may be a separate regional terrain feature of the same type that is close enough to be in the box, and thus get affected when it shouldn't.

Essentially, I'm looking for a that will:

1. Identify the x, y, and terrain type of the caster plot.
2. Find all contiguous "same terrain" plots.
3. Apply the effect.

I am thinking that some type of path function may need to be called, to see that each terrain plot can trace a clear path through only the same terrain type back to the caster plot in order to qualify.

Wishful thinking? Please prove me wrong!
 
It sounds like you want to use BFS or DFS for part 2. Not familiar with the code thats already avail to you, DFS is prob a better bet honestly, better space complexity iirc, but either would work. see: http://en.wikipedia.org/wiki/Depth-first_search and http://en.wikipedia.org/wiki/Breadth-first_search

Only issue is I'm not sure how expensive it would be to mark visited nodes on the map. It would be really easy if you were modifying all of the pieces of that terrain(aka mapping all desert into plains, as that would handle the marking for you). I guess one option would be to have a dummy of the same graphic, and map it from the starting type (a) -> the dummy (b) while you traverse, then repeat with a mapping of (b) -> (a) after applying the effect you want, leaving you with the original terrain. Seems to be sorta a hack and might be an easier way to handle(theres always the huge array option, where you mark everything 0 to start with, 1 when you visit it and its the type you want, still need to initialize or rerun to map back to 0's though)

To search adjacent vertices's you just check x+1,y x-1,y x,y+1 x,y-1 of course.

I guess depending on your definition of contiguous you might need to check diagonals as well.

Not that familiar with the actual code of the game, so I can't help you with 1 or 3 :).
 
Okay everyone -- here is a challenge for you:

Code:
def get_connected_plots( x,y, connectivity_list=[(0,1),(0,-1),(1,0),(-1,0)] ):
    open_plots = set([(x,y)])
    connected_plots = []
    closed_plots = set()
    get_map_plot = CvPythonExtensions.CyMap().plot
    iTerrain = get_map_plot( x,y ).getTerrainType()
    while open_plots:
        x,y = open_plots.pop()
        pPlot = get_map_plot(x,y)
        if not pPlot.isNone() and iTerrain == pPlot.getTerrainType():
            connected_plots.append( (x,y) )
        closed_plots.add( (x,y) )
        for x_offset, y_offset in connectivity_list:
            near_plot = (x+x_offset, y+y_offset)
            if near_plot not in closed_plots:
                open_plots.add( near_plot )
    return connected_plots

Untested code, but you'll probably have to modify it for your purposes anyway. :) Barring mistakes it should return a list of the coordinates connected to (x,y) which you'll then iterate through to apply the spell effect.
 
I will definitely play around with those ideas.

Here is another problem: Pickle sadness. I cannot seem to get pickles to work well, and cannot find consistent advice on these boards.

Take a look at this:

Code:
def spellSaveMappy():
	aaiStarbaseList = []
	iOcean = gc.getInfoTypeForString('TERRAIN_OCEAN')
	iCoast = gc.getInfoTypeForString('TERRAIN_COAST')
	iGrass = gc.getInfoTypeForString('TERRAIN_GRASS')
	iPlains = gc.getInfoTypeForString('TERRAIN_PLAINS')
	iDesert = gc.getInfoTypeForString('TERRAIN_DESERT')
	iTundra = gc.getInfoTypeForString('TERRAIN_TUNDRA')
	iSnow = gc.getInfoTypeForString('TERRAIN_SNOW')
	iBorder = gc.getInfoTypeForString('TERRAIN_BORDER')
	for i in range (CyMap().numPlots()):
		pPlot = CyMap().plotByIndex(i)
		iClimate = 0
		if pPlot.getTerrainType() == iCoast:
			iClimate = 10
		if pPlot.getTerrainType() == iGrass:
			iClimate = 20
		if pPlot.getTerrainType() == iPlains:
			iClimate = 30
		if pPlot.getTerrainType() == iDesert:
			iClimate = 40
		if pPlot.getTerrainType() == iTundra:
			iClimate = 50
		if pPlot.getTerrainType() == iSnow:
			iClimate = 60
		if pPlot.getTerrainType() == iBorder:
			iClimate = 70
		aaiStarbaseList.append([pPlot, iClimate])
#		aScriptData = str(iClimate)
#		pPlot.setScriptData(pickle.dumps(aScriptData))
	pickle.dump(aaiStarbaseList,mapfile)

That is an example of some code I am trying to implement. It is a spell that takes a snapshot picture of the the terrain type for every plot on the map and saves it to a persistent file. Or so I thought. It is not working. (You can see some permutations of code commented out as I tried different things)

I wanted to pair it with a spell that would then access the persistent file and apply the saved terrains to the current map. Taken together, this could create a "map reset" function.

This can't be impossible. Fall from Heaven 2 and Age of Ice both use something like this for tracking the climate of each plot. I think FfH2 uses it for the Armageddon counter, too.

Just for kicks, here is the other spell:

Code:
def spellLoadMappy():
	caster=CyInterface().getHeadSelectedUnit()

	iOcean = gc.getInfoTypeForString('TERRAIN_OCEAN')
	iCoast = gc.getInfoTypeForString('TERRAIN_COAST')
	iGrass = gc.getInfoTypeForString('TERRAIN_GRASS')
	iPlains = gc.getInfoTypeForString('TERRAIN_PLAINS')
	iDesert = gc.getInfoTypeForString('TERRAIN_DESERT')
	iTundra = gc.getInfoTypeForString('TERRAIN_TUNDRA')
	iSnow = gc.getInfoTypeForString('TERRAIN_SNOW')
	iBorder = gc.getInfoTypeForString('TERRAIN_BORDER')

	aaiStarbaseList = [pickle.load(mapfile)]

	for iStarbaseLoop in range(len(aaiStarbaseList)):

		pPlot = (aaiStarbaseList[iStarbaseLoop][0])
		iClimate = (aaiStarbaseList[iStarbaseLoop][1])

	# for i in range (CyMap().numPlots()):
		# pPlot = CyMap().plotByIndex(i)
		# iClimate = int[aaiStarbaseList]

		if iClimate == 0:
			pPlot.setTerrainType(iOcean,True,True)
		if iClimate == 10:
			pPlot.setTerrainType(iCoast,True,True)
		if iClimate == 20:
			pPlot.setTerrainType(iGrass,True,True)
		if iClimate == 30:
			pPlot.setTerrainType(iPlains,True,True)
		if iClimate == 40:
			pPlot.setTerrainType(iDesert,True,True)
		if iClimate == 50:
			pPlot.setTerrainType(iTundra,True,True)
		if iClimate == 60:
			pPlot.setTerrainType(iSnow,True,True)
		if iClimate == 70:
			pPlot.setTerrainType(iBorder,True,True)

Yes, some unnecessary cruft in here, but no combination of things seemed to work.

With both of these spells implemented, the game works fine. No crashes or weird python behavior -- except that the spells appear to do nothing.

Any one able to shed some light on this? Pickle experts?
 
Taken straight from the python documentation:

3.14.4 What can be pickled and unpickled?
The following types can be pickled:
  • None, True, and False
  • integers, long integers, floating point numbers, complex numbers
  • normal and Unicode strings
  • tuples, lists, sets, and dictionaries containing only picklable objects
  • functions defined at the top level of a module
  • built-in functions defined at the top level of a module
  • classes that are defined at the top level of a module
  • instances of such classes whose __dict__ or __setstate__() is picklable

I'd keep it down to only the first four though, since the last four depend on the code being the same as when they were pickled.

My guess is that whatever is returned by "CyMap().plotByIndex(i)" doesn't pickle well. Are your python exceptions on? Because it looks to me like "mapfile" should raise an exception even if pickle doesn't.

Anyway, you probably shouldn't store the map in a different file, what happens between games? When looking for a way to store data in the game itself I found SD ToolKit, and digging through Fall Further I found no less than two versions of that tool kit. You'd still have to think about what you store, but you wont have to think about how it's stored or deal with pickling it yourself.
 
Code:
import SdToolKit

terrainMapping = {}
terrainMapping[gc.getInfoTypeForString('TERRAIN_OCEAN')] = 0
terrainMapping[gc.getInfoTypeForString('TERRAIN_COAST')] = 10
terrainMapping[gc.getInfoTypeForString('TERRAIN_GRASS')] = 20
terrainMapping[gc.getInfoTypeForString('TERRAIN_PLAINS')] = 30
terrainMapping[gc.getInfoTypeForString('TERRAIN_DESERT')] = 40
terrainMapping[gc.getInfoTypeForString('TERRAIN_TUNDRA')] = 50
terrainMapping[gc.getInfoTypeForString('TERRAIN_SNOW')] = 60
terrainMapping[gc.getInfoTypeForString('TERRAIN_BORDER')] = 70

def spellSaveMappy():
    climateList = []
    cyMap = CyMap()
    for i in xrange( cyMap.numPlots() ):
        pPlot = cyMap.plotByIndex(i)
        iTerrain = pPlot.getTerrainType()
        iClimate = terrainMapping[iTerrain]
        climateList.append( iClimate )
    SdToolKit.sdSetGlobal('DungeonAdventure', 'SavedMap', climateList )
    
    
def spellLoadMappy():
    climateList = SdToolKit.sdGetGlobal('DungeonAdventure', 'SavedMap' )
    if climateList is not None: # Apparently SdToolKit.sdGetGlobal returns None if it can't find the variable. 
        cyMap = CyMap()
        reversedTerrainMapping = dict( (value, key) for key, value in terrainMapping.iteritems() )
        for index, iClimate in enumerate( climateList ):
            pPlot = cyMap.plotByIndex( index )
            pPlot.setTerrainType(reversedTerrainMapping[iClimate],True,True)
    else:
        pass # Replace with whatever should be done about there being no map to load. 

def spellExchangeTerrain( x, y ):
    oldTerrains = []
    cyMap = CyMap()
    offsets = [(tx, ty) for tx in xrange(-1,2) for ty in xrange(-1,2)]
    for x_offset,y_offset in offsets:
        pPlot = cyMap.plot( x+x_offset, y+y_offset )
        if not pPlot.isNone():
            iTerrain = pPlot.getTerrainType()
            oldTerrains.append( ( x_offset,y_offset, iTerrain ) )

    newTerrains = SdToolKit.sdGetGlobal('DungeonAdventure', 'ExchangeTerrain' )
    SdToolKit.sdSetGlobal('DungeonAdventure', 'ExchangeTerrain', oldTerrains )
    
    if newTerrains:
        for x_offset,y_offset, iTerrain in newTerrains:
            pPlot = cyMap.plot( x+x_offset, y+y_offset )
            pPlot.setTerrainType( iTerrain, True, True )

Again, untested code. Think of it as potentially runnable pseudo code.

In replacing the whole map, I felt I missed some tricks that could be useful to know, so I wrote another function that takes a coordinate and replaces the surrounding terrain with the terrain that was replaced last time.
 
Back
Top Bottom