trying to use a minimalist mod to do big things

...
load_module CvAdvisorUtils
load_module DawnOfTime
load_module CvPythonExtension
CvPythonExtension import failed
Traceback (most recent call last):
File "<string>", line 1, in ?
File "<string>", line 52, in load_module
File "CvEventInterface", line 13, in ?
File "<string>", line 52, in load_module
File "CvEventManager", line 23, in ?
File "<string>", line 52, in load_module
File "DawnOfTime", line 1, in ?
ImportError: No module named CvPythonExtension
load_module CvAppInterface
 
Code:
from CvPythonExtension import *
from PyHelpers import *
from Popup import PyPopup
from CvEventManager import *

gc = CyGlobalContext()
cyGame = CyGame()
cyMap = CyMap()
pyGame = PyGame()
pCity = pPlayer.initCity(x, y)
pPlayer = gc.getActivePlayer()

The bug you are seeing: there is no CvPythonExtension. It is CvPythonExtensions

Additional issues:

There is no x or y or even a pPlayer defined at the point you do it so "pPlayer.initCity(x, y)" can not possibly work.

Why are you trying to create a city for some player at some point on the map when this file is loaded? That is what pPlayer.initCity(x, y) does - it creates a city for the player. The pCity that is used in the nameNorm function is passed to it (and the variable it is using is defined on the "def" line for the function by putting it in the parenthesis, which means "we are being passed data that will be stored in a local instance of a variable called, in this case, pCity") it would ignore anything defined for it before the function definition anyway once it is in the function, if that was why you were doing it.

Why are you getting the active player?
a) there is no reason to do it here, you can just do it where you need it
b) you never need it

Just drop those two lines (the ones that attempt to set pCity and pPlayer) - they are not doing anything useful for you.

For that matter, cyGame, cyMap, and pyGame are not used for anything either so you can get rid of those lines of code too. At some point in the future development of the mod you might want one or more of them - add them then if turns out that you do.

Technically gc isn't being used either so you could drop that line too, but it is so universally useful that you could keep it around just for future use.
 
oh, right...the initcity line was a throwback from when I initially misunderstood what it was for. Alright, that explains a lot. I think I'm going to leave the other imports, however, as the minute I get done getting it to recognize this part of the mod I'm goign to set about with the "spawns" that will entail them.

thank you!
 
The name of the extensions module is plural:

Code:
from CvPythonExtensions import *

BTW, an extra comma after the last element of a list, tupie, or dictionary is acceptable. I leave them in when putting each element on a new line as it makes adding new elements easier. It's one thing I really wish other languages would adopt.

In Lua also only nil (like Python's None) and false evaluate to false in a logical expression. 0, "", and an empty table all evaluate to true.
 
I keep getting an error in the DawnOfTime.nameNorm line that 'city' is not defined
Code:
	def onCityBuilt(self, argsList):
		'City Built'
		city = argsList[0]
        DawnOfTime.nameNorm(city)
        if (city.getOwner() == gc.getGame().getActivePlayer()):
            self.__eventEditCityNameBegin(city, False)
            CvUtil.pyPrint('City Built Event: %s' %(city.getName()))
 
Your indentation appears to be incorrect.

The files that come with Civ use tabs to indent. You should also use tabs when editing those files since a tab and a bunch of spaces are not necessarily equal (by default, I believe the Python parser considers a tab to be equal to eight spaces). If you are using an editor that is set to convert a tab that you enter into some number of spaces, make it stop doing that.

The line that starts DawnOfTime is probably matching in indentation level to the "def" line before, which means that it is not inside the function definition like it should be.

The line that says "city = argsList[0]" is indented by two tabs. The two lines after it should also be indented two tabs, and the line after the "if" should be indented 3 tabs. Then the line after that is back to 2.
 
Alright, I was using tabs but it was converting them. It got so mucked up I just re-cut-and-pasted it and added the line again from scratch.

It now loads with no problems.


...however...

When anyone tries to found a city on a key tile, the following exception is made:
 

Attachments

  • Screen shot 2010-09-17 at 10.45.32 PM.png
    Screen shot 2010-09-17 at 10.45.32 PM.png
    301.1 KB · Views: 86
When anyone tries to found a city on a key tile, the following exception is made:
Spoiler :
I'm only guessing here, but this seems to me as an issue with text encoding. This sort of thing can be really annoying, but try this:

Check the encoding of your module (and of you custom copy of CvEventManager). It should probably be UTF-8 and not ANSI or Unicode or whatever. (I believe you're using a Mac so this could be part of your problem.)

If thats not it, then try to force Unicode encoding you the city name by going:
Code:
      name = unicode(cityNameDict[coord])
And lastly you could try turning it into a regular string (in case its Unicode):
Code:
      name = str(cityNameDict[coord])
This stuff is really confusing to me but someone else would probably be able to tell heads from tails here... :confused:

edit: Duh! I totally missed the second boolean parameter that is required for the setCity() method. Just go with what God-Emperor said. :rolleyes:
 
There is no x or y or even a pPlayer defined at the point you do it so "pPlayer.initCity(x, y)" can not possibly work.
About this, there is something called a "name space" where all the things that are defined in a program are stored. So if you assign a value to a variable - or define a function - then that name will be present in the name space and can't be shared with any other. (If defined anew it will actually be replaced.)

So there is a global name space for the entire module, but there are also local spaces for each function/method and so on. Each module has its own set of names - so every module can have whatever in its own name space without it conflicting with any other module. (But careless importing of modules will of course cause conflict with names.)

In your module, the names present from scratch are only the built-in functions (and methods) of the Python programming language. These include names like str, import and append - stuff that are part of Python. So you should be aware of these because you shouldn't be defining anything yourself with such a name - because this will confuse the interpreter!

You start by importing CvPythonExtensions to your module. You actually do it in a rather careless (but very convenient) way (again copying from others):
Code:
from CvPythonExtensins import *
What this does is it copies the entire Boost Python thingy from the SDK and adds it on top of your module's name space. This means that all classes, methods, functions, constants and whatnot are now available to your module - they are part of its global name space. This is why you can do the next thing:
Code:
gc = CyGlobalContext()
The interpreter wouldn't be able to execute this assignment statement without there first being a CyGlobalContext class to initiate. This class is in fact part of CvPythonExtensions.

Another way of doing this - and even if its probably more correct I would advice against it - would be to import the CvPythonExtensions "module" but not make it's content part of your module's name space:
Code:
import CvPythonExtensions
gc = CvPythonExtensions.CyGlobalContext()
Less convenient but you'd be able to define your own CyGlobalContext as a variable, function, class of whatever in your module. (Not that you'd want to do that.) Or you could just import the bits you actually need, like the class itself:
Code:
from CvPythonExtensions import CyGlobalContext
gc = CyGlobalContext()
But this would mean that the interpreter wouldn't recognize the other stuff in CvPythonExtensions, like the CyPlayer or CyCity classes. So this is clearly inconvenient.

So gc is now defined as a global variable (constant) in your module. So if CvPythonExtensions added 1001 names (just making a figure up) you now have 1002 defined names.

The problem with the line:
Code:
pCity = pPlayer.initCity(x, y)
...is that while you're free to assign just about anything to the name pCity, the stuff that you're assigning to it should already be defined. Once again the interpreter won't know that that stuff is. The names pPlayer, x an y aren't defined anywhere. But if you do this instead:
Code:
x = 10
y = 34
pPlayer = gc.getActivePlayer()
pCity = pPlayer.initCity(x, y)
...then the interpreter will actually be able to make sense of it.

So firstly you would assign integer values to the x and y variable. Next you would assign the return value of the CyGlobalContext.getActivePlayer() method to the name pPlayer. You can do this only because both gc and getActivePlayer have already been defined in your script. (getActivePlayer is defined as a part of the CyGlobalContext class that you imported above, so the interpreter can't even recognize whether or not you've defined it in your module or not. Its part of it's name space - as simple as that.)

Now it would be valid to define pCity the way you did. Because the name pPlayer was defined and added to the name space on the above line. And the variables x and y actually point to valid integer values. (The initCity name was of course defined within the CyPlayer class and imported from the SDK.)

As a side note, you could do this instead:
Code:
pCity = gc.getActivePlayer().initCity(10, 34)
The point is that the interpreter can't see the difference. The only thing the sample code above added was some names that cluttered up the module's name space. It was easier for you and me to read, however, and this is why you probably shouldn't try to shorten things up like this. But still, all the variables used added no functionality to the script in themselves.

Regarding importing - you don't ever need to import the Event Manager to any other module. It actually works the other way around - all other modules should - in directly or indirectly - be imported to the Event Manager.

I can't shake the feeling that you're still thinking you can "figure" programming out by looking at sample code. While that can be very helpful, you should realize that you're basically wasting your time when you're "experimenting" with Python without knowing exactly what you're trying to do. Its all explained in plain English in the sources and there really is no reason to do anything in a random fashion. The debugging time could be used for actual learning instead. With your academic background you'd be able to learn the basics of Python in no time at all.
 
Alright, I was using tabs but it was converting them. It got so mucked up I just re-cut-and-pasted it and added the line again from scratch.

It now loads with no problems.


...however...

When anyone tries to found a city on a key tile, the following exception is made:

If you look in the modiki you'll see that CyCity.setName actually takes two arguments, a text string for the name and a boolean value called "bFound". From the name, and looking at how it is used in CvEventManager.py, it looks like you should use True since this is for applying an initial name, not a rename (the difference is probably in what message everybody sees). So you should probably be using "pCity.setName(name, True)".
 
If you look in the modiki you'll see that CyCity.setName actually takes two arguments, a text string for the name and a boolean value called "bFound". From the name, and looking at how it is used in CvEventManager.py, it looks like you should use True since this is for applying an initial name, not a rename (the difference is probably in what message everybody sees). So you should probably be using "pCity.setName(name, True)".
Oh yeah? This is actually interesting!
 
That was it...absolutely perfect...and if someone tries to found two cities where the same keyname will be called for it simply goes to the default list for all after the first (I was worried about a crash).

I'm actually not looking at any other code except for the two files I'm working with. I did browse the dynamic naming mod, but that was just to see if they built off rhye's mapping method (which they did). There was nothing there for me to use, even if I had been so inclined (although I was debating simply starting with that mod).

That said, I have not fully understood every bit of what I've been reading enough to say "ah, I really understand python now" but I'm getting enough to make educated attempts. This way, when I'm wrong, it's (hopefully) not so annoying for those who help me.

Now, I've got the naming thing set up, and the workboats set up. The next part of what I'd like to do is to start in on the "spawns" (plus there are a couple third party mods I would like to integrate...namely the one that moves a holy city in the case it is razed (which I've done before), and the one that adds an inquisitor unit (which I have not, and could probably live without)).

So...there is actually a python tutorial I've bookmarked that teaches you how to spawn a unit, so I'm going to go and read that. I've then got to figure out how to make it spawn as well as give the tile on which it stands 100% culture for the new civ (so they can found a city there it it's already in someone else's territory)...THEN I need to figure out how to deal with it if there's already a city on that tile (but one thing at a time). When all that happens, I'm going to need to print an event announcement like "The Frankish people declare their independence" for each one.

I'd like to tie these spawns to the game year rather than the turn so that different speeds can be used. So...back to studying for this next step, and then I'll be posting my code attempt when I've got something.
 
Glad to hear about your success! :goodjob:

So...there is actually a python tutorial I've bookmarked that teaches you how to spawn a unit, so I'm going to go and read that.
Spawning units is also covered in my tutorial. Just saying... Its basically just CyPlayer.initUnit() but PyPlayer.initUnit() is actually easier if you make a point of firstly figuring out how to use PyHelpers...

I've then got to figure out how to make it spawn as well as give the tile on which it stands 100% culture for the new civ (so they can found a city there it it's already in someone else's territory)...
Note that any player can have any number of culture invested on any plot, so in order to get 100% its not sufficient to add some amount of culture to the plot. You need to erase all that culture already present. (Otherwise it will never ever become 100%)

But I don't think you need to even deal with culture values. There is a CyPlot.setOwner() method. Look it up in the API. Obviously you need a valid CyPlot instance for that plot... As long as you have the coordinates you can use CyMap.plot() - its in the API and you could check out both its return value and parameters.

THEN I need to figure out how to deal with it if there's already a city on that tile (but one thing at a time).
CyPlot.isCity() for checking for cities and CyPlayer.acquireCity() for flipping it.

When all that happens, I'm going to need to print an event announcement like "The Frankish people declare their independence" for each one.
Whatever messages you end up printing out, you only need to use the CyInterface.addImmediateMessage() method once in your code. You can either do like Rhye did in RFC and have a generic message that dynamically inserts the name of the spawning Civ. Or you could store custom strings for each Civ in a data structure of some sort (like a list) - then you just index that structure with the player ID.

I'd like to tie these spawns to the game year rather than the turn so that different speeds can be used.
You access the game turn with CyGame.getGameTurn() and you can fetch the game date with CyGame.getGameTurnYear(). The problem with comparing the game date with a pre-defined value is that the actual date will seldom match exactly with the spawn date. So you need some logic for making sure that while the date has passed - its not any game turn beyond that turn. (I've struggled with this extensively myself, I can add. Hopefully someone will be able to help you sort it out.)

So...back to studying for this next step, and then I'll be posting my code attempt when I've got something.
Yeah, you probably jumped the gun on the city naming code, as that was quite a challenge for you. (We all pretty much ended up copy-pasting each others code until a parameter was lost in the setName() method... :rolleyes:) The more actual studying you do the easier will the actual coding be.
 
my first step was going to be to get my hands on the list of calendar dates associated with each of the speed settings, and simply use dates that only appear in all settings (should they exist).

For instance, if all speeds eventually display 800AD, then I will use that as the spawn date for the medieval civs...
 
my first step was going to be to get my hands on the list of calendar dates associated with each of the speed settings, and simply use dates that only appear in all settings (should they exist).

For instance, if all speeds eventually display 800AD, then I will use that as the spawn date for the medieval civs...
I suppose that would work... but programming is basically for not having to resort to these sorts of second best plans. This is also the difference between XML modding and programming. :king:

So its basically about logic. Say you have a spawn date at AD 800. That would be expressed with the integer value 800. Lets say, for arguments sake, that the nearest dates for the various game speeds are 800, 825, 810 and 805. (In reality all game speeds might have a actual game turn associated with the value 800, but this is just an example.)

What you need is a conditional statement that passes if the return value of CyGame.getGameTurnYear() is equal to or greater than 800. But since you only wanna fire the spawn once, you could add another condition for the game turn (not game year) not to be the following game turn - or greater. Because there is a CyGame.getTurnYear() method which takes a game turn as a parameter.

This is somewhat what I've done myself:
Code:
Game = CyGame()
iDate = 800
iTurn = Game.getGameTurn()
iYear = Game.getGameTurnYear()
if Game.getTurnYear(iTurn +1) > iDate and iYear <= iDate:
Its not easy to explain the logic behind this, but you basically need a setup like it for it to work relieably. (I might have messed the example up, but in that case it can be fixed - no worries.)

And you of course don't need to copy-paste the same code for each spawn - this is what data structures and functions are for. So we wrap the statement into a function:
Code:
def isDate(iDate):
   return Game.getTurnYear(Game.getGameTurn() +1) > iDate and Game.getGameTurnYear() <= iDate
And then you use it with a data structure of some sort with all the spawn dates:
Code:
tSpawnDates = (
-4000,
-4000,
-3500,
-2000,
-1500,
)
What you need to do is employ a looping technique that allows you to go through all the tSpawnDates entries on onBeginGameTurn() or whatever the callback is called in CvEventManager. Preferably with a function like DawnOfTime.checkSpawnDates():
PHP:
def checkSpawnDates():
   iNumEntries = len(tSpawnDates) # fetches the number of spawn date entries
   for ePlayer in range(iNumEntries): # loops through a list containing that number of values from zero and up
      iDate = tStartingDates[ePlayer] # fetches the spawn date entry for the current player
      if isDate(iDate): # calls isDate() which returns True/False
         initSpawn(ePlayer) # calls another function if the condition passes
initSpawn() would of course be yet another function for you to define. I have to take care so I don't make your mod for you. :p
 
hahaha...thanks.

Oh, yes, I do recall this being in some way similar to the stuff that got used for the RFC modding we did. Still, as I'm trying to learn as I go along, I'll probably wind up using the simplest possible method for version 1. I may decide to complicate it later with more elgant code.

The years I'd been intending to use as landmarks I believe show up in all speeds:
1600 BC
800 BC
1AD
800AD
1200AD
1770AD
1900AD

If nothing else, I can get this set up the quickest so I can test the other mechanics of what I wanted to do. This will spawn civs in era-based blocs (and some ancient colonies...most colonies will be tied to the discovery of the astronomy tech). I may decide to stagger them later on, in which case I will have to bring in years not on all lists and use a looping and evaluation technique as you've suggested.

I can also test the spawn code itself by entering 3980BC and advancing the game one turn...once i'm sure the mechanics of my code work, I can change the date to what I really want it to be (remember, since I'm new, I have to actually test each little thing I do to make sure it works before moving on to the next...seeing things work is good for morale, too)
 
Can you access the Python console in-game? Because that is basically for this sort of testing.

If you fire it up you can enter this on the command prompt:
Code:
CyGame().setGameTurn(100)
This will set the game date to whatever is game turn 100 on the current game speed.

You can also define variables in the console:
Code:
game = CyGame()
iDate = 800
iTurn = game.getTurnYear(iDate)
game.setGameTurn(iTurn)
If you prefer to do everything on one line without the variables:
Code:
CyGame().setGameTurn(CyGame().getTurnYear(800))
This will enable you to set the game turn by entering the game date instead of the game turn. :goodjob:

Of course you could wrap this into a function in your own module instead:
Code:
def setGameDate(iDate):
   Game.setGameTurn(Game.getTurnYear(iDate))
Then you'd be able to import your module and use the function in the console:
Code:
import DawnOfTime
DawnOfTime.setGameDate(800)
Hopefully all this is making more and more sense to you... :king:
 
lol...one function at a time...i want to get a unit to appear on the map first, so I can pop this champagne...then I'll worry about making them appear in the types/numbers/owners/turns i really want...I'll settle for a roman settler on tile 61, 46 on turn 2 right now!
 
No, you can make that unit spawn in the console! Get it over with!
Code:
import PyHelpers
PyHelpers.PyPlayer(eRome).initUnit(eSettler, 61, 46)
You of course have to define the variables eRome and eSettler - or you could just figure out what their IDs are and use the integer values.

Once you get something to work in the console its only a matter of putting that code into the Event Manager - or into a function that you call upon from the Event Manager.

To get the unit to spawn on turn 2 you need a conditional statement and some way of fetching the actual game turn. (Hint: The game turn is already supplied within the onBeginGameTurn() method.) This you can't test in the console.

Also, the examples for the console in the previous post was actually to help you to figure out exactly which game dates are present on all game speeds. Instead of having to play each speed - or to calculate this from the XML entries. But whatever - you'll need to be able to do those things in the console for testing also. It'll save you some hours of testing since you don't have to replay games anytime you need to test some spawn.
 
I remember looking up the integer values for those things in Rhye's...in what file do I find those IDs (civs and units)? It was one of the C++ files, was it not?
 
Back
Top Bottom