[SCENARIO] Dungeon Adventure - the 133rd K.R.A.G.

Fiddling with the setGameturn function does not help, so the error must be based off of Elapsed game turns. And although there is a getElapsedGameTurns function, there is not a function to setElapsedGameTurns.
 
I will try a few other things, but this would be a lot easier if someone knew what changed between 0.41a and now that happens after turn 40.
 
I found it using a debug DLL! The game is calling CvCity::getPopulation for every AI Khazad player's capital city after turn 40 in some code designed to increase AI Khazad gold. This must have been added in one of the Wild Mana merge patches. Unfortunately, Kandros has no capital city, and this code in CvPlayerAI.cpp (line 3441)

Code:
                CvCity* pCity = getCapitalCity();
                if (pCity->getPopulation() < 6 && pCity->angryPopulation(1) > 0 && pCity->foodDifference(false) > 1)
                {
                    addGold+=getGold();
                }
                iGold += addGold;

is assuming blindly that every player will have a capital city. pCity.getPopulation() is triggering an unhandled exception because Kandros has no capital city, and therefore getPopulation() can't return anything.

Having found it, I also think I can fix it. But it will require a new DLL.

The full code fragment is this:

Code:
        if (bIncludeForcedGold)
        {
            if (getCivilizationType() == GC.getDefineINT("CIVILIZATION_KHAZAD"))
            {
                int addGold=0;
                int iNumCities = getNumCities();
                if (GC.getDefineINT("TECH_TAXATION") != NO_TECH)
                {
                    if (isHasTech(GC.getDefineINT("TECH_TAXATION")))
                    {
                        addGold += iNumCities * 150;
                    }
                }
                if (GC.getDefineINT("TECH_TAXATION") != NO_TECH && GC.getDefineINT("TECH_FESTIVALS") != NO_TECH)
                {
                    if (isHasTech(GC.getDefineINT("TECH_WAY_OF_THE_EARTHMOTHER")) || isHasTech(GC.getDefineINT("TECH_FESTIVALS")))
                    {
                        addGold += iNumCities * 150;
                    }
                }
                CvCity* pCity = getCapitalCity();
                if (pCity->getPopulation() < 6 && pCity->angryPopulation(1) > 0 && pCity->foodDifference(false) > 1)
                {
                    addGold+=getGold();
                }
                iGold += addGold;
            }
        }

All that needs to be done is add a check to only run the last bit of code if the player has more than 0 cities. Something like this:

Code:
        if (bIncludeForcedGold)
        {
            if (getCivilizationType() == GC.getDefineINT("CIVILIZATION_KHAZAD"))
            {
                int addGold=0;
                int iNumCities = getNumCities();
                if (GC.getDefineINT("TECH_TAXATION") != NO_TECH)
                {
                    if (isHasTech(GC.getDefineINT("TECH_TAXATION")))
                    {
                        addGold += iNumCities * 150;
                    }
                }
                if (GC.getDefineINT("TECH_TAXATION") != NO_TECH && GC.getDefineINT("TECH_FESTIVALS") != NO_TECH)
                {
                    if (isHasTech(GC.getDefineINT("TECH_WAY_OF_THE_EARTHMOTHER")) || isHasTech(GC.getDefineINT("TECH_FESTIVALS")))
                    {
                        addGold += iNumCities * 150;
                    }
                }
               [COLOR="Red"]if (iNumCities > 0)[/COLOR]
                {
                    CvCity* pCity = getCapitalCity();
                    if (pCity->getPopulation() < 6 && pCity->angryPopulation(1) > 0 && pCity->foodDifference(false) > 1)
                    {
                        addGold+=getGold();
                    }
                    iGold += addGold;
                }
            }
        }
 
Couldn't you give the Khazad a capital in the corner of the map, where the player would never see it?
 
That would be possible as a plan B or C.

I originally designed this scenario with a city present, but it really messes up the game because the city is still accessable for builds, it spews culture onto the map, and even if tucked into a corner, it is still there doing what cities do.

This makes my map switching especially troublesome. In this scenario, the entire map switches out from inside maps to an outside map and back again.

I would like to investigate some other work around first but, yeah, this would be a possibility.

Of course I suppose I could just switch from Khazad to a different Civ. Maybe the other dwarves?

But then I would need to keep the correct dwarf arts, since I don't want golems. I wonder if it is possible to assign civ's arts to each other... ?
 
Here is a 0.41j DLL with the fix and the file I edited. It fixes the crash. Drop it into your Fall from Heaven II/Assets folder and overwrite the existing DLL with it. (That's the CvGameCoreDLL file, not the CvPlayerAI.cpp file. You don't need the CvPlayerAI.cpp file).

Maybe we can post this in the bug thread and Kael could add this to regular FFH in a future patch?
 

Attachments

  • CvGameCoreDLL.zip
    1.6 MB · Views: 249
TC01, once again Sir, you rock!

I was fiddling around with trying to spoof a Luichirp civ that LOOKED like Khazad, but your fix is much more elegant. I just never really got into the C++ end of things much.

Thanks again for the merge and the DLL fix. I will repost both at the top of the thread and give you some much deserved kudos.
 
Thread splash page has been updated with TC01's updated merge and DLL fix, which I am decreeing to be the official:

Release 5 (TC01 Update)

Happy adventuring!
 
You're welcome.

Out of curiosity, are you planning to add other stuff to this scenario in the future? Like a Mission 3?
 
The question is: keep fleshing out this scenario, or change over to a stand-alone modmod?

What do you think, TC01?
 
The question is: keep fleshing out this scenario, or change over to a stand-alone modmod?

What do you think, TC01?

I basically agree with this post.

What do you mean by stand-alone modmod? Just modifying things outside the constraints of the scenario contest? If that's what you mean, then I would support creating a stand-alone modmod.

I like a lot of the features in this scenario, and would like to see them expanded. Maybe the best way to do this is to create a stand-alone modmod, but if you did, I wouldn't like to see this scenario completely dropped.
 
The current scenario could be expanded, even with the existing constraints. It isn't a very clean or elegant coding job, so that makes it a little harder to add to, but it is still doable. If you have taken a close look at my coding you can probably tell that I was just making it all up as I went along! :)

I was intrigued by some of the stuff I noticed in the updated python for the core FfH2 build. If I am uderstanding it correctly, they are reading and writing information to some sort of data storage other than script data while still in the game? If that is true, it would be possible to acheive the holy grail of DA implementation which would be a persistent multi-map world!

TC01, did you notice the code to which I am referring and , if so, do you understand it? Alternatively, do you know your way around script data arrays and/or the pickle function? I could really benefit from collaborating with someone who has a more solid and reputable programming background than I do. Since I am self taught -- mostly by banging on things and seeing what happens -- I have some pretty big gaps in my understanding of python.
 
The current scenario could be expanded, even with the existing constraints. It isn't a very clean or elegant coding job, so that makes it a little harder to add to, but it is still doable. If you have taken a close look at my coding you can probably tell that I was just making it all up as I went along! :)

I was intrigued by some of the stuff I noticed in the updated python for the core FfH2 build. If I am uderstanding it correctly, they are reading and writing information to some sort of data storage other than script data while still in the game? If that is true, it would be possible to acheive the holy grail of DA implementation which would be a persistent multi-map world!

TC01, did you notice the code to which I am referring and , if so, do you understand it? Alternatively, do you know your way around script data arrays and/or the pickle function? I could really benefit from collaborating with someone who has a more solid and reputable programming background than I do. Since I am self taught -- mostly by banging on things and seeing what happens -- I have some pretty big gaps in my understanding of python.

I'm not actually sure what you're referring to, I'm afraid. Around where in the FFH python is it?

I think I understand how pickling arrays works, since Final Frontier uses it to store solar system data to plot script data and load it when the game loads, and that is the other mod I have played around with. pickle.dumps(object) converts that object to a string. The object, in this case, would be an array, or possibly an array of arrays. pickle.loads(string) converts the string back to the array. So you would save the map during the event to leave the outpost, and then when coming back to the outpost you load the map and then save it again.


The SDK and Python forum is very helpful. As is the Python API, although that's for BTS only.
 
Well, here is the basic idea I am working on for better map switching.

Right now, DA has random dungeon maps -- which are fun -- but they are not persistent. Once you leave that level, you will never see it again. The world map and Shaft X maps are pulled from static data arrays, and modified slightly by ingame variables.

But I want dungeons that you can return to and still see the changes that were made before. For example, if you blasted through a wall to get somewhere, the hole would still be there.

Anyway, here are two functions that show a dumbed-down version of what I really want:

Code:
def goDeeperDungeon(self):
	
	terrainList = []
	for i in range (CyMap().numPlots()):
		pPlot = CyMap().plotByIndex(i)
		pTerrain = pPlot.getTerrainType()
		terrainList.append(pTerrain)

	elevationList = []
	for i in range (CyMap().numPlots()):
		pPlot = CyMap().plotByIndex(i)
		pElevation = pPlot.getPlotType()
		elevationList.append(pElevation)

	featureList = []
	for i in range (CyMap().numPlots()):
		pPlot = CyMap().plotByIndex(i)
		pFeature = pPlot.getFeatureType()
		featureList.append(pFeature)

	improvementList = []
	for i in range (CyMap().numPlots()):
		pPlot = CyMap().plotByIndex(i)
		pImprovement = pPlot.getImprovementType()
		improvementList.append(pImprovement)

	scriptList = []
	for i in range (CyMap().numPlots()):
		pPlot = CyMap().plotByIndex(i)
		pScript = pPlot.getScriptData()
		scriptList.append(pScript)

	levelMap = []
	levelMap.append(terrainList)
	levelMap.append(elevationList)
	levelMap.append(featureList)
	levelMap.append(improvementList)
	levelMap.append(scriptList)
	
	levelNumber = CyGame().getGlobalCounter()
	
	CyMap().plot(levelNumber,19).setScriptData(levelMap)
	
def unpackfunction(self):

	levelNumber = CyGame().getGlobalCounter()
	levelMap = CyMap().plot(levelNumber,19).getScriptData()
	
	terrainList = levelMap[0]
	elevationList = levelMap[1]
	featureList = levelMap[2]
	improvementList = levelMap[3]
	scriptList = levelMap[4]
	
	for i in range (CyMap().numPlots()):
		pPlot = CyMap().plotByIndex(i)
		pPlot.setBonusType(-1)
		pPlot.setFeatureType(-1, -1)
		pPlot.setImprovementType(-1) #will also clear all warning posts.  Replacement improvements are aded back in below
		if i < 20:
			pPlot.setScriptData('')
		#pPlot.setPythonActive(False) #may need to add this back in later as a part of the flushing process
		pPlot.setMoveDisabledHuman(False) #clears dungeon specific blocks
		pPlot.setMoveDisabledAI(False)
		CyEngine().removeLandmark(pPlot)

		pTerrain = terrainList[i]
		pPlot.setTerrainType(pTerrain,True,True)

		pElevation = elevationList[i]
		pPlot.setPlotType(pElevation, True, True)

		pFeature = featureList[i]
		pPlot.setFeatureType(pFeature, 0)

		pImprovement = improvementList[i]
		pPlot.setImprovementType(pImprovement)


And of course the code would include a block to reestablish the stored scriptdata for plots, too.

The real function would look at other things in addition to basic map data, but you get the drift. Because the maps in DA are usually only about 20X20, doing a raster scan of each variable and creating data dumps for the info does not take long. I am already doing this in DA in a way, and the map switch only takes a second.

Each variable becomes a list. All the lists are combined into a list of lists and then stored. The code above has a rough plan to store these in the top row of the map as plot scriptdata. I suppose you could also create invisible helper units, sort like DA has for traps and secret doors, to act as dumping spots for unit scriptdata.

But as you move from level to level, and then return to a previous level, the python code would look for the relevant data dump, read the data, and then recreate the previous map.

I sort of got this to work with scriptdata, as shown above, but not in time for the scenario design contest. I tried a number of times to do it with pickling, but have NEVER been able to get that function to behave for me.

So, what do you think about the idea above? It would never work on a traditional huge map, as it would take forever to execute, but in DA it should work fine.
 
It seems like it should work.

I made changes in red where I would add stuff to get pickle to work. I also combined the loop for goDeeperDungeon (that's in green), because it didn't make sense to loop all over the map so many times.

Code:
[COLOR="Red"]import pickle[/COLOR]

def goDeeperDungeon(self):
	
	terrainList = []
	[COLOR="Green"]elevationList = []
	featureList = []
	improvementList = []
	scriptList = [][/COLOR]

	for i in range (CyMap().numPlots()):
		pPlot = CyMap().plotByIndex(i)
		pTerrain = pPlot.getTerrainType()
		terrainList.append(pTerrain)

		[COLOR="Green"]pElevation = pPlot.getPlotType()
		elevationList.append(pElevation)

		pFeature = pPlot.getFeatureType()
		featureList.append(pFeature)

		pImprovement = pPlot.getImprovementType()
		improvementList.append(pImprovement)

		pScript = pPlot.getScriptData()
		scriptList.append(pScript)[/COLOR]

	levelMap = []
	levelMap.append(terrainList)
	levelMap.append(elevationList)
	levelMap.append(featureList)
	levelMap.append(improvementList)
	levelMap.append(scriptList)
	
	levelNumber = CyGame().getGlobalCounter()
	
	[COLOR="red"]szLevelMap = pickle.dumps(levelMap)
	CyMap().plot(levelNumber,19).setScriptData(szLevelMap)[/COLOR]
	
def unpackfunction(self):

	levelNumber = CyGame().getGlobalCounter()
	[COLOR="red"]szLevelMap = CyMap().plot(levelNumber,19).getScriptData()
	levelMap = pickle.loads(szLevelMap)[/COLOR]
	
	terrainList = levelMap[0]
	elevationList = levelMap[1]
	featureList = levelMap[2]
	improvementList = levelMap[3]
	scriptList = levelMap[4]
	
	for i in range (CyMap().numPlots()):
		pPlot = CyMap().plotByIndex(i)
		pPlot.setBonusType(-1)
		pPlot.setFeatureType(-1, -1)
		pPlot.setImprovementType(-1) #will also clear all warning posts.  Replacement improvements are aded back in below
		if i < 20:
			pPlot.setScriptData('')
		#pPlot.setPythonActive(False) #may need to add this back in later as a part of the flushing process
		pPlot.setMoveDisabledHuman(False) #clears dungeon specific blocks
		pPlot.setMoveDisabledAI(False)
		CyEngine().removeLandmark(pPlot)

		pTerrain = terrainList[i]
		pPlot.setTerrainType(pTerrain,True,True)

		pElevation = elevationList[i]
		pPlot.setPlotType(pElevation, True, True)

		pFeature = featureList[i]
		pPlot.setFeatureType(pFeature, 0)

		pImprovement = improvementList[i]
		pPlot.setImprovementType(pImprovement)

What other sorts of things would you want to store?
 
Well, pretty much an entire savegame file!

The rest of the map features -- I have already done this, including rivers, etc., so I know that it can be done.

Specific units should be saved, such as helper units and monsters.

If the maps have cities at all, you would need to get city location, buildings, granary and production status and make sure that the cities will be reproduced.

So -- map, cities, units, and scripts.

Frankly, if we could bust open the entire savegame routine from the main DLL and make it into an in-game python pickle dump, it would be awesome and very flexible. It would only work for very small maps, but that is exactly what DA uses.
 
I have an idea, in that case.

Civ IV Worldbuilder is written in python. Not just the in-game interface but the reading, writing, and applying of scenarios is all handled in python. (Python functions called from the DLL, anyway).

It might be possible to commandeer these functions (which aren't in one of the interface files but are in Python\pyWB\CvWBDesc.py) and use them to write data to a WBS, read it, and then apply it. The write functions can save the current game state to a WBS file called, say, "Map1.WBS", if we're on Map1. Then if we are moving to Map2, the game reads a WBS file called "Map2.WBS" and applies the data.

Something like this might work.

Code:
import CvWBDesc
import os

WB = CvWBDesc.CvWBDesc()

def goDeeperDungeon(self, szCurrentMap, szNewMap):
	fileCurrentMap = os.path.join(os.getcwd(), "Mods", "Fall from Heaven 2", "Assets", "XML", "Scenarios", szCurrentMap)
	fileNewMap = os.path.join(os.getcwd(), "Mods", "Fall from Heaven 2", "Assets", "XML", "Scenarios", szNewMap)
	WB.write(fileCurrentMap)
	WB.read(fileNewMap)
	WB.applyMap()
	WB.applyInitialItems()

It should write a WBS to Mods\Fall from Heaven 2\Assets\XML\Scenarios\, and then read a different WBS, and then apply all the data in game.

I don't know if that's actually what you want, though, or if it would even work. Maybe only some of the worldbuilder functions need to be used- so this would have to be modified. Maybe to do the actual writing and reading of the file in this function and only point to the plot data (which calls other functions to load units and cities).
 
DA could evolve as a stand-alone scenario or modmod, but it would be really neat if it could work in a popup screen like Somnium. I have wondered if a rogue-like written for python could be adapted for this purpose -- it would end up being a more elaborate version of Marnok's explorable dungeons.

One unit enters the popup dungeon, and explores until it either decides to leave or dies in the dungeon.

I am quite certain that the logic code for either DA or a rogue-like could be adapted to this purpose, but I don't have the background to understand what went into making the Somnium graphical interface.

Anyone have any thoughts on this?
 
Top Bottom