Various call questions (Python)

Create a VS2005 project (I assume you did this already, right?) and then add all the files to the project. Now you can hit CTRL + SHIFT + F to Find in Files. I think you can limit it to various file extensions from there. I think you could add the Python and XML files to the project, but you may have to add a build rule for those file extensions that says "do nothing".

I'm not very familiar with it either. I have my project set up to use a make file using nmake and VC++ 2003 Toolkit.

I use Eclipse for all my Python work. It's an IDE (integrated development environment) like VS in that it will handle various languages through plugins. Since Python is interpreted, there's no build step. Instead I export the changed files to my CustomAssets folder. In Eclipse I have many projects created: BUG, BAT, Core Assets, SDK, etc. The last two are only for searching similar to the above.
 
OK, will have to try that. Will be a very nice feature, didn't even know it was possible.

I've been trying to get this to work for the past few hours. Getting nowhere. This is the last thing I tried (the bottom line isn't important--everything but the TechMovementChange(i) value works--, just including it for reference to the end function):
Code:
                #for i in range(gc.getNumTechInfos()):
                    #tech = gc.getTechInfo(i)
                    #if( gc.getRouteInfo(pCity.plot().getRouteType()).getTechMovementChange(i) != 0 ) :
                        #if pTeam.isHasTech(i):
                            #cityDistCommBonus += 592 / ( gc.getRouteInfo(pCity.plot().getRouteType()).getFlatMovementCost() + gc.getRouteInfo(pCity.plot().getRouteType()).getTechMovementChange(i) )
No matter what, everytime I call .getTechMovementChange(i) all it returns is zero. Regardless of how or where I try to use this call, 0 is all it ever returns. Any ideas?

The main problem is I'm working off of the previous code where there was the call tech.isTerrainTrade(); nothing similar exists for TechMovementChange(). This is the only call I could find that makes reference to a Tech based movement change to a route, and it is only found in CvRoute, no where else is the Tech based movement change for routes mentioned (how does it know to put the +1 movement button in the tech tree?). So how can I define i for TechMovementChange, so that the function will return an actual value if pTeam has a movement cost modifier tech (default engineering)?

As an asside I could do this by just arbitrarily assigning the movement change, and it's corresponding value to what i know it is: engineering & -10. However I'm trying to move away from XML assumptions with this code, other then core things like TERRAIN_OCEAN, and COMMERCE_CULTURE the code I've written doesn't assume anything about the XML. The point of this is to keep it that way, so that modders can change the tech tree or whatever and it'll still function logically.
 
You've supplied your own solution: look in CvTechChooser for the spot where it places the +1 route movement, "can build roads", and "can build railroads" icons. How? Look in the Art files for the matching icon (slow, brute force search could take a long time unless you get lucky with the name), and then find where it's added. Or read the code to find a function that looks reasonable. Or selectively delete portions of the file and see what changes in the game until you manage to remove those icons, then look at what you deleted.

A large part of coding is learning skills in deductive reasoning and reading other people's code. It can be daunting at first, but if you stick with it, it will become second nature, and you'll end up with a good sixth sense of where to look to find your answer. And this will apply to more than just coding, so it's a good skill to learn. :)
 
OK. One quick question, where can I find CvTechChooser, it's not in the source code that I can find (goes from CvTeamAI to CvTextScreens when arranged in alphabetical order) :dunno:

Edit: Oh, I see it's a python file.
 
Had a look at CvTechChooser and came up with this:

Code:
                for j in range(gc.getNumRouteInfos()):
                    for i in range(gc.getNumTechInfos()):
                        [I]tech = gc.getTechInfo(i)
                        if (gc.getRouteInfo(j).getTechMovementChange(i) != 0 ) :
                            if pTeam.isHasTech(i):[/I]
Still no dice, just 0 is all it gives for getTechMovement change. So what's wrong with the italicized part of the code? wouldn't this call defince when TechMovementChange isn't zero, and then see if the team has the tech under that condition?
 
That code looks fine. How exactly are you testing that you get 0 for every route/tech combination? I don't see any code under the last if test. Perhaps the code in there doesn't do anything or gets overridden below? It's extremely hard to diagnose a problem without any surrounding context.

Insert the following line to validate your assumption. It will print a message for any tech/route combo whether or not the team has researched the tech.

Code:
if (gc.getRouteInfo(j).getTechMovementChange(i) != 0 )
    [B][COLOR="Green"]CyInterface().addImmediateMessage("Tech %d gives %+d MP to route %d" % (i, gc.getRouteInfo(j).getTechMovementChange(i), j), "")[/COLOR][/B]
    if ...
 
Nothing was printed.

What's happening is that the citydistance calculation remains unaltered, so I can only assume that it is returning a value of zero.

Code:
                for j in range(gc.getNumRouteInfos()):
                    for i in range(gc.getNumTechInfos()):
                        tech = gc.getTechInfo(i)
                        if (gc.getRouteInfo(j).getTechMovementChange(i) != 0 ) :
                            CyInterface().addImmediateMessage("Tech %d gives %+d MP to route %d" % (i, gc.getRouteInfo(j).getTechMovementChange(i), j), "")
                            if pTeam.isHasTech(i):
                                cityDistCommBonus += 592 / ( gc.getRouteInfo(pCity.plot().getRouteType()).getFlatMovementCost() + gc.getRouteInfo(pCity.plot().getRouteType()).getTechMovementChange(i) )
                            else :
                                cityDistCommBonus += 592 / gc.getRouteInfo(pCity.plot().getRouteType()).getFlatMovementCost()

That's the whole snipet. Actually, it just no longer asseses the entire effect of the route at all if I use this code, however this works fine on it's own:
Code:
cityDistCommBonus += 592 / gc.getRouteInfo(pCity.plot().getRouteType()).getFlatMovementCost()
But once I nest it inside the above function, it isn't being aplied at all. Which is really weird, I'd think the last else would have caught the effect regardless. But it just fails to apply the whole function if nested.
 
The way you have it coded--and if the getTechMovementChange() part were working--the distance value would be altered for every route-modifying tech the player has researched, ignoring the route the city has. Can you explain with an example what exactly you want? Note that the city's plot will always be whatever the best route is for the player. If you research Railroads and have Coal, all your cities will have the Railroad route automatically. Are you trying to tie the city's route to the part that picks which route-tech combination to check?

Let me rephrase that. If they have Railroads and the city has Railroads on its plot, should they get a bonus for having researched The Wheel and Engineering? This code will give them a bonus for those techs, but the bonus will be whatever bonus they get for Railroads each time. In fact, it won't be a bonus since you add to the distance value.

What I suspect you want is to track whether or not to apply a bonus based on their techs, and then apply a single bonus or not. Right? For that you want this pattern:

Code:
bBonus = False
for iTech in ...:
    for iRoute in ...:
        if tech gives bonus to route and team has tech:
            bBonus = True
            break
    if bBonus:
        break
if bBonus:
    apply bonus
else:
    apply normal calculation

That, of course, doesn't solve you immediate problem. Can you post the part of CvTechChooser that you have found draws the route bonus? What about the part that gives you access to roads and railroads? How are those done?

Edit: The breaks are to break out of the nested-loop structure. A single break under the first if-test won't be enough.
 
The code I posted above, the seconed snippet, alone by itself figures out if the civilization has roads or rails. And applies the bonus perfectly (the civ doesn't need coal to put rails under cities, this is automatic regardless--at least that's the numbers I'm getting). The only issue is that the roads do not adjust their effect when the civ has engineering, and no matter what I have tried, I can't get the number to change from what the original function above does. Well actually they changed, in the above long snippet ( first one with the modifier in it, the second short one as stated, works fine on it's own), it no longer applies the bonus at all.

This is the code from CvTechChooser:
Code:
			# Route movement change
			for j in range(gc.getNumRouteInfos()):
				if ( gc.getRouteInfo(j).getTechMovementChange(i) != 0 ):
					szMoveButton = "Move" + str(j)
					screen.addDDSGFCAt( szMoveButton, szTechRecord, ArtFileMgr.getInterfaceArtInfo("INTERFACE_TECH_MOVE_BONUS").getPath(), iX + fX, iY + Y_ROW, TEXTURE_SIZE, TEXTURE_SIZE, WidgetTypes.WIDGET_HELP_MOVE_BONUS, i, -1, False )
					fX += X_INCREMENT

			j = 0
			k = 0

Thanks again for the help EF.

Edit: OK now I'm really confused:

cityDistCommBonus += 592 / (gc.getRouteInfo(pCity.plot().getRouteType()).getFlatMovementCost())

The above works for railroads, I am indeed getting numbers that only makes sense if the number being returned is 6, this works fine if I gift the tech Railroad to the civ. (Testing in WB now). But, and this makes no sense, it doesn't do anything for regular roads. Now how is this possible? If it were returning 1, it should utterly destroy the city distance modifier, and I should see it disapear. If it were returning 0, it should crash, or at the very least I should be getting an exception pop up. Even if it were returning 60, I should still be seeing some small effect in the city distance calculation. Anyway, just to check I added in an if statement where if the returned value was >= 30 it would run the function as though it was 30. But still nothing, just nothing. How is this possible? How can the function work with railroads, yet just do nothing when there are no railroads. Ie, it's taking 592 and dividing it by whatever the call returns, so how dos it get nothing out of 592 when railroads aren't present (yet function fine when they are)?
 
I don't see anything wrong with the code that checks for the route-tech bonus, so I don't know how to fix it. What you need to do is add more debug statements (the addImmediateMessage() call you're using now) to track down what exactly is happening. Are you getting Python errors in PythonErr.log?
 
Nope nothing in the python log. Well, it shows the error for the "Can't find Type for Enum 'HOLY_ROM'" I'd like to know what that is, but it's not important at the moment (doesn't seem to do anything).

I think though my initial assesment have have been wrong, there may be a deeper problem here.

Before I was using: 100 - (gc.getRouteInfo(pCity.plot().getRouteType()).getF latMovementCost())*1.67 And it was behaving as expected, at least the numbers I was getting were in the ballpark of what I'd expect if it were returning 30 for roads and 6 for rails, so I assumed it worked fine.

I then changed the calculation to: 592 / gc.getRouteInfo(pCity.plot().getRouteType()).getF latMovementCost() Now here is the thing, I get the numbers I'd expect when the civ has railroad. Checked them this time and they come out exactly to what I'd expect, so I can only assume this function works correctly for when the civ has the ability to build railroads. But, it does nothing for roads. Which is odd, and in my mind makes no sense. If the call returned zero, it would then divide 592 by zero, and should crash, or at the very least cause a python exception. If it returned 1, then the citydistance modifier would end up being divided by a large number, so I should see a dramatic decrease in it's value. The only way I should see nothing is if it were returning a number close to 592 (which would make no sense). To check this I threw in a catch all if statement so that if the call returned more then 30, it would return 30 for the whole function. Got absolutly no change in behavior when I did that. So this just seems odd, how is it this call is returning a correct value for Railroads, yet not doing anything when just plain roads are present under pCity, and since it's aparently not doing anything, how am I not getting a python exception when it tries to divide 592 by whatever it is returning when roads are there?
 
I don't totally understand what you're saying. I think you'll be far better off adding those debug statements to each branch to see what happens when instead of trying to reverse engineer it from the resulting distance modifier.
 
Code:
                bTechRouteModifier = False
                for i in range(gc.getNumTechInfos()):
                    for j in range(gc.getNumRouteInfos()):
                        tech = gc.getTechInfo(i)
                        if(gc.getRouteInfo(j).getTechMovementChange(i) != 0 and pTeam.isHasTech(i)):
                            bTechRouteModifier = True
                            break
                    if bTechRouteModifier:
                        break
                if bTechRouteModifier:
                    cityDistCommBonus += 100 - ( gc.getRouteInfo(pCity.plot().getRouteType()).getFlatMovementCost() + gc.getRouteInfo(pCity.plot().getRouteType()).getTechMovementChange(i) )*1.67
                else:
                    cityDistCommBonus += 100 - ( gc.getRouteInfo(pCity.plot().getRouteType()).getFlatMovementCost() )*1.67

Worked, thanks. I screwed up earlier on my math as well, the code was actually working, but your method is superior to what I had going on anyway :)
 
So I didn't like the old way jdog's function calculated the raw distance of a city from it's capital. It used plotdistance[stuff] which made it so that cities at diagonals had basically their distance doubled. So I decided to invoke Pythagoras. Now this sounds like a great idea, and worked OK, until I realized there was a huge flaw in wrapped maps. So trying to correct for this I came up with the above code:

Code:
            if map.isWrapX() :
                cityDistX = ( CyMap().getGridWidth() / 2 - capital.getX() ) - ( CyMap().getGridWidth() / 2 - pCity.getX() )
            else :
                cityDistX = capital.getX() - pCity.getX()
            if map.isWrapY() :
                cityDistY = ( CyMap().getGridHeight() / 2 - capital.getY() ) - ( CyMap().getGridHeight() / 2 - pCity.getY() )
            else :
                cityDistY = capital.getY() - pCity.getY()

Seems simple enough, will just use the cityDistY and cityDistX variables in Pythagoras later down the line. But I've got an error in my syntax. Python exceptions throws back an error: "AttributeError: 'builtin_function_or_method' object has no attribute 'isWrapX'"
I tried if( map.isWrapX() = true ) : instead of just if map.isWrapX() as well, but strangely enough this just causes the python to fail to initialize when I boot up civ entirely.

Edit found it hiding in Keal's python snippits thread :)
Code:
if CyMap().isWrapX():

Edit2:
Wow, this math problem is really hard. I don't know how to calculate it, lol.

This is what I have:
Code:
            cityDistModifier = 0
            if CyMap().isWrapX():
                cityDistX = ( CyMap().getGridWidth() / 2 - capital.getX() ) - ( CyMap().getGridWidth() / 2 - pCity.getX() )
            else :
                cityDistX = capital.getX() - pCity.getX()
            if CyMap().isWrapY():
                cityDistY = ( CyMap().getGridHeight() / 2 - capital.getY() ) - ( CyMap().getGridHeight() / 2 - pCity.getY() )
            else :
                cityDistY = capital.getY() - pCity.getY()
Yeah, the math just doesn't work out right. The formula I need seems to be much more complex.
 
Here's a random stab. Calculate the distance first, and then if the map wraps use the shorter of the distance and the length minus the distance:

Code:
map = CyMap()
deltaX = abs(pCity.getX() - pCapital.getX())
if map.isWrapX():
    deltaX = min(deltaX, map.getGridWidth() - deltaX)

Do the same for Y and height.
 
Heading to bed so not stopping to review the whole setup, so sorry if I am way out in left field with this one, but it seems potentially helpful.

As I recall the current discussion: you are seeking to figure out how long a unit would take to get from one tile to the next, and PlotDistance assumes travel in only the cardinal directions. So now you are looking at how to calculate the distance manually?

If you use raw geometry, then you are ignoring things like inland lakes or large mountain ranges which you have to walk around. If you are willing to ignore those, then your answer is pretty simple: The distance requirement is just the larger of the X or the Y displacement of the 2 cities (since you always travel 1 unit in that direction each movement, and stop moving in the other direction at all once you get on the same line - ie - 5 up and 2 over takes 5 turns, 2 of them you move diagonal, 3 of them you move straight up).

In the case of wrapping, you can (as EF wrote) just subtract the distance between the 2 cities (seperately for each direction) from the total grid width/height to get what the wrapped distance would be.


Really though I am shocked that plotdistance works so horribly. There ought to be a function which tells you the pathlength between two points which is available to python (I know there is one available to the DLL, even though for all I can tell it isn't actually IN the DLL at all), which would account for terrain costs and unit movement capability (ie - impassibles)


EDIT: Ok, on brief re-read it seems this might be for city maintenance type of stuff, where using the pythagorean theorem might be fitting. But travel time for a worker also seems somewhat appropriate (lots of mountains/forests in the way, no roads yet, high maintenance; Railroads everywhere, low maintenance)
 
Yeah, EF's code worked. Thanks, I feel stupid for not figuring that out, spent like 2 hours racking my brain on it, lol.
All of this works perfectly. The only thing left to do on this end is to add in the descriptions. But that's for another time. Now shifting focus to the art end, is that is giving me the most problems atm. But I just couldn't accept jdog's city distance calculation, and mine was poorly coded, so I had to get that fixed first.

Thanks again. Probably be back in a few days with trying to figure out how to add in the text for the buildings. I fiddled with this, but I don't like my solution right now. Just added extra text in the XML, but it looks crappy, and I couldn't figure out how to do it for the gold and culture modifiers, buildings seem to only have a call for commerce modification, and the differentiation is is done somewhere else.

For those interested this is the end result (also if you see a way to optomize something let me know):

Code:
           # Distance to capital City Distance modified by communication techs and structures
            cityDistModifier = 0
            map = CyMap()
            deltaX = abs(pCity.getX() - capital.getX())
            if map.isWrapX():
                deltaX = min(deltaX, map.getGridWidth() - deltaX)
            deltaY = abs(pCity.getY() - capital.getY())
            if map.isWrapX():
                deltaY = min(deltaY, map.getGridWidth() - deltaY)
            cityDistRaw = ( deltaX**2 + deltaY**2 )**0.5
            cityDistMapModifier = ( CyMap().getGridWidth()**2 + CyMap().getGridHeight()**2 )**0.5
            cityDistCommBonus = 0
            pTeam = gc.getTeam(pPlayer.getTeam())
            bCanTradeOverCoast = False
            bCanTradeOverOcean = False
            for i in range(gc.getNumTechInfos()):
                tech = gc.getTechInfo(i)
                if tech.isTerrainTrade(gc.getInfoTypeForString("TERRAIN_COAST")):
                    if pTeam.isHasTech(i):
                        bCanTradeOverCoast = True
                if tech.isTerrainTrade(gc.getInfoTypeForString("TERRAIN_OCEAN")):
                    if pTeam.isHasTech(i):
                        bCanTradeOverOcean = True
            if bCanTradeOverOcean :
                cityDistCommBonus += 50
            if(pCity.getTradeRoutes() > 0 ) :
                cityDistCommBonus += (pCity.getTradeRoutes() - 1)*20
            if( pCity.isConnectedTo(capital) ) :
                bTechRouteModifier = False
                for i in range(gc.getNumTechInfos()):
                    for j in range(gc.getNumRouteInfos()):
                        tech = gc.getTechInfo(i)
                        if(gc.getRouteInfo(j).getTechMovementChange(i) != 0 and pTeam.isHasTech(i)):
                            bTechRouteModifier = True
                            break
                    if bTechRouteModifier:
                        break
                if bTechRouteModifier:
                    cityDistCommBonus += 100 - ( gc.getRouteInfo(pCity.plot().getRouteType()).getFlatMovementCost() + gc.getRouteInfo(pCity.plot().getRouteType()).getTechMovementChange(i) )*1.67
                else:
                    cityDistCommBonus += 100 - ( gc.getRouteInfo(pCity.plot().getRouteType()).getFlatMovementCost() )*1.67
                if( pCity.isCoastal(-1) ) :
                    if bCanTradeOverOcean :
                        cityDistCommBonus += 67
                    elif bCanTradeOverCoast :
                        cityDistCommBonus += 33
                cityDistCommBonus += pCity.getTradeRouteModifier()
                cityDistCommBonus += pCity.getCommerceRateModifier(CommerceTypes.COMMERCE_CULTURE)
                cityDistCommBonus += pCity.getCommerceRateModifier(CommerceTypes.COMMERCE_GOLD)
            cityDistCommBonus -= pCity.getMaintenanceModifier()
            if pCity.isPower() :
                cityDistCommBonus += 100
            if (pCity.getMaxAirlift() > 0 ) :
                cityDistCommBonus += 200
            cityDistModifier = ( 307*cityDistRaw / cityDistMapModifier ) / ( 1 + ( cityDistCommBonus / 100 ) )
            cityDistModifier -= 666 / cityDistMapModifier
 
Minor unnoticeable speed improvements:

Replace all CyMap()s with map after the first one.
Assign info types to variables outside loops for use in them:

Code:
iTerrainCoast = gc.getInfoTypeForString("TERRAIN_COAST")
for ...:
    ...iTerrainCoast...

You might want to force the calculations at the end to use floating point numbers instead of ints. I didn't read everything to see if you will get them, but I don't think so.

Code:
cityDistModifier = ( 307.0 * cityDistRaw / cityDistMapModifier ) / ( 1.0 + ( cityDistCommBonus / 100.0 ) )
cityDistModifier -= int(666.0 / cityDistMapModifier)

Unless of course you want fractions to be dropped along the way. For example, if cityDistCommBonus is 375, dividing it by 100 equals 3, not 3.75. Adding .0 forces floating point division (as long as either side of the / is a floating point number, the calculation will be done using floats). You use int() at the end to drop the fraction since cityDistModifier is an int (is it?).
 
Hehe, so onto the next step of improving Revolutions. Currently there is a mechanic where the gold rate is calculated as a penalty to civ stability. While I understand the mechanic, and agree with it in principle, it's effect on gameplay is to pigeon hole out novel and creative strategies, and it even hurts the SE economy as a whole. Besides, I don't think the science slider represents a tax rate, instead it represents the direction that tax revenues are being spent. With that that in mind I need to calculate another way to represent an overal economic burden on the populace. My idea is to do this:

1)Calculate all commerce (all types--ie science, gold, culture, espionage), generated in the entire empire. Calculate all trade exports (double dipping I know, but hey I think it'll work). Add these two and store it in an iGDP variable.
2)Calculate total gold spent on Unit maintenance, City Maintenance, Civic Maintenance, and Inflation. Calculate total trade outflowing in exports. Add these and store them in a iWaste vaiable.
Take iWaste and devide it by iGDP, multiply it by 100. This will give me a % of economic burden, which I will then plug into the already created tax rate and use the old code, with this new value as the considered gold rate (currently it just pulls the player's gold rate).

Here are possible relevant calls I've found in CyPlayer:

Looks like a very promising call, seems to combine things I want to do for me:
getTotalMaintenance ()
calculateTotalCommerce ()
calculateTotalExports (YieldType eYield)
calculateUnitCost ()
calculateTotalImports (YieldType eYield)

Looks like a call I might need to use:
calculateInflatedCosts ()
calculateInflationRate ()
calculateUnitSupply ()
countTotalCulture ()
calculateTotalYield (YieldType eYield)
getGoldPerTurn ()


I'm hoping I don't need to create some huge math equation and take these calls into account, but I may need to:
calculateBaseNetResearch ()
calculatePreInflatedCosts ()
getEspionageSpending (PlayerType ePlayer)
getNumCitiesMaintenanceModifier ()
getNumUnits ()
getUpkeepCount (UpkeepType eIndex)
getUpkeepModifier ()


Now here is my best case scenario code:
Code:
iGDP = pPlayer.calculateTotalCommerce() + pPlayer.calculateTotalExports (??e?)
iWaste = pPlayer.getTotalMaintenance() + pPlayer.calculateUnitCost() + pPlayer.calculateInflatedCosts() + pPlayer.calculateTotalImports(??e?)
pGoldPer = (iWaste / iGDP)*100

Please tell me it is that simple, :lol: Also on the off chance it is, what do I need to put in the Exports and imports brackets?
 
What do I need to put in the Exports and imports brackets?

The game mechanics allow trade routes to yield :food:, :hammers:, and :commerce:, but the original game only uses :commerce:. Therefore, pass in YieldTypes.YIELD_COMMERCE.
 
Top Bottom