trying to use a minimalist mod to do big things

You still need to learn basic syntax and stuff. You seem to have a grasp of indentation but seem to have missed that any time the level of indentation goes up (a new block of code) there should be a colon at the end of the previous line. Look at any sample code to verify this. Also, you need to lookup operators, because you can't use the same operator for assigning values and for evaluating them ("is equal to"). EmperorFool's example also makes little sense before you understand what a tuple is and how you index data structures (like tuples). Lastly, when defining a function you need to specify what the arguments are. Likewise, any time you call on a function you need to add the parenthesis where the arguments go (even if there is no arguments).

This is all the most basic stuff imaginable so keep up with the reading and you'll figure it out. You could take a look at my Python modding tutorial as it provides you with sample code to compare with. (Any code displayed is also explained in the text.) It also explains what you do with the Event Manager module.

edit: Regarding Python operators - this is the actual link that got me started with Python some 9 months ago. (You need to scroll one full screen down to see the actual table. It should be an eye-opener.)
 
ah, I should hit the tuple chapter today. I'll have to go back and reread your tutorial...
 
I highly recommend working through the examples in How to Think Like a Computer Scientist. Use the IDLE interpreter so you can type them in and see the results immediately, and then play with them to try your own things. This is the best way to go from understanding what you read to using that knowledge effectively.
 
two things are unclear from my readings.

1) is the dictionary a separate file or should it appear at the end of CvEventManager.py, where I am adding the code for the city name process complication?

2) it was suggested that I add the following code to the existing onCityBuilt code in CvEventManager.py (the custom one for the mod, not the original)
Code:
	def onCityBuilt(self, argsList):
		pCity = argsList[0]
		coord = (pCity.getX(), pCity.getY())
		if coord in cityNameDict:
			name = cityNameDict[coord]
			pCity.setName(name)

But how exactly does that integrate with the original code? I imagine that they can't appear separately as they both begin with "def onCityBuilt" I'm sure that would confuse things. In the Dune mod, the city name alteration code appears in the DuneWars.py file, and not the CvEventManager.py file, so my supposition is that it should appear in my dawnoftime.py file.

From my reading, it seems that if I could get those two things arranged properly I could actually test this function.
 
Ideally you would put everything related to your mod in your own Python modules. And then there's real life. I would of course recommend using BUG Mod (see my sig) as a base so that you can more easily do your modding, but that does require learning more than doing it directly in CvEventManager. It will be better going forward as your mod grows, but it may not give you instant gratification.

My sig has a tutorial to do what you need using BUG. Describing how to do it without BUG would require explaining more Civ4-specific stuff which any modder around here can easily explain. Ultimately, you can learn on your own by grabbing an existing mod and see how it does things. Working examples are a great way to get started quickly because you can make small changes in a working system.
 
Oh, I've lived off working examples for pretty much all the modding I've done so far, but I knew of none for quite what I wanted to do.
 
Not for the feature you're building, no, but there are hundreds of existing mods that have added code to onCityBuilt(), and that's part of what you need to learn to do.
 
you know, I'm actually really surprised that this hasn't been done already. I've thought it would have made sense since the days of Civ II...
 
I've only played on the Earth map a couple times. 90% of the fun in Civ for me is exploring random worlds. Then again, I'm not a history wonk.
 
What the CvEventManager module does is that it offers "hooks" for you own code to be hanged on. You can pretty much build your whole mod into the Event Manager directly, or you could use it to execute your code in another module. It really makes no difference as for functionality.

But this is the principle I would argue for:

Put all of your own code into DawnOfTime.py and wrap it up into separate functions. If you like, you could have separate modules for different features, like city naming. So lets presuppose you start off with a CityNames module then.

In CityNames.py you firstly add import statements and assorted constants (variables). Then you could define your dictionary at the module or __main__ level. Because it will also be assigned to a constant - and all variables at the very first indentation level are available to the entire module.

After the dictionary you can add the various functions associated with city names. So lets take the code previously submitted and add it to the module, like:
Code:
def onCityBuilt(pCity):
  coord = (pCity.getX(), pCity.getY())
  if coord in cityNameDict:
    name = cityNameDict[coord]
    pCity.setName(name)
Now, there is a onCityBuilt function (actually a method) in CvEventManager also. This is not important, as they appear in different modules. So basically the first one is CvEventManager.CvEventManager.onCityBuilt (module.class.method) and your own is CityNames.onCityBuilt (module.function) - hardly the same, right? You could of course rename your function whatever you like, so lets just do it then:
Code:
def renameCity(pCity):
  coord = (pCity.getX(), pCity.getY())
  if coord in cityNameDict:
    name = cityNameDict[coord]
    pCity.setName(name)
Then we connect the two modules. In CvEventManager.py add a import statement (where exactly is not critical):
Code:
import CityNames
Now all the code and and all the constants and whatever in your module are available in the Event Manager also. So go to onCityBuilt() and add a function call to your own module:
Code:
      CityNames.renameCity(argsList[0])
If you ever read about tuples then you realize that the first entry (indexed as zero) in argsList is the reference to the city (an instance of the CyCity class). In renameCity() we know it as pCity. Compare!

This is pretty much it, but I realize it would be confusing still unless you've finished reading about all the various subjects involved. It'll all make sense, don't worry. :)
 
Thanks, I'll check that thread out tomorrow.

I was advised not to emulate Rhye's "map" method of static plot names, but I'll take a look and see if there's any other part of the code that can help me with my "list" method.

I do want to understand it all, but honestly if that other guy has already done what I'm looking to do with a "map," I'm not going to spend three weeks reinventing the wheel. I can devote my limited time to the other aspects of this mod...
 
Just having taken a browse, I've saved the mod, but I'm still going to call the dictionary method I've been working on "plan A"...given some of the changes I've made to map etc it may yet be simpler to finish this method than to alter the alteration of Rhye's.

But I'll play with it all tomorrow some more...
 
Oh, that's what that mod does. :blush: I thought it was related to Dynamic Civ Names in some way.


You were correct, he has a working mod that ostensibly does what I'm looking for, but I've altered the map from the one he's using. From what I recall from modding RFC, playing around with that excel sheet doing the map was less than fun :lol:

I may build off the Dynamic City Names mod after all (I'm too tired to seriously look through it now, it's 2am), or I may try to finish what I've started the other way.

Make no mistake, I am quite grateful that you've pointed me toward that link. :goodjob:
 
alright...so...I can't quite figure out where to put

Code:
        DawnOfTime.nameNorm(argsList[0])

in
Code:
	def onCityBuilt(self, argsList):
		'City Built'
		city = argsList[0]
		if (city.getOwner() == gc.getGame().getActivePlayer()):
			self.__eventEditCityNameBegin(city, False)	
		CvUtil.pyPrint('City Built Event: %s' %(city.getName()))

as for my mod file, I want to put everything I'm going to do in one file, DawnOfTime.py ...here's what it looks like so far (some of the import things I don't need right now (like the popup) but I'll need for the rest of the mod down the road)

Spoiler :

Code:
from CvPythonExtension import *
from PyHelpers import *
from Popup import PyPopup
from CvEventManager import *

gc = CyGlobalContext()
cyGame = CyGame()
cyMap = CyMap()
pyGame = PyGame()
pCity = pPlot.getplotCity()


cityNameDict = {
(55, 54): "London",
(55, 53): "London",
(56, 53): "London",
(56, 54): "London",
(63, 52): "Berlin",
(62, 52): "Berlin",
(62, 53): "Berlin",
(63, 53): "Berlin",
(51, 55): "Dublin",
(51, 56): "Dublin",
(55, 58): "Edinburgh",
(54, 57): "Edinburgh",
(54, 58): "Edinburgh",
(58, 51): "Paris",
(59, 51): "Paris",
(58, 52): "Paris",
(57, 51): "Paris",
(58, 50): "Paris",
(57, 50): "Paris",
(55, 45): "Madrid",
(55, 46): "Madrid",
(54, 46): "Madrid",
(54, 45): "Madrid",
(53, 45): "Lisbon",
(53, 44): "Lisbon",
(53, 46): "Lisbon",
(61, 46): "Rome",
(62, 45): "Rome",
(62, 46): "Rome",
(61, 47): "Rome",
(60, 47): "Rome",
(67, 43): "Athens",
(66, 42): "Athens",
(67, 44): "Athens",
(66, 44): "Athens",
(68, 46): "Byzantium",
(67, 46): "Byzantium",
(69, 45): "Byzantium",
(73, 40): "Jerusalem",
(73, 41): "Jerusalem",
(72, 40): "Jerusalem",
(73, 42): "Antioch",
(73, 43): "Antioch",
(68, 39): "Alexandria",
(69, 39): "Alexandria",
(67, 39): "Alexandria",
(30, 47): "New York",
(31, 47): "New York",
(29, 47): "New York",
(29, 46): "New York",
(31, 48): "Boston",
(31, 49): "Boston",
(32, 49): "Boston",
(24, 43): "New Orleans",
(23, 43): "New Orleans",
(25, 43): "New Orleans",
(24, 48): "Chicago",
(24, 47): "Chicago",
(24, 49): "Chicago",
(25, 47): "Chicago",
(12, 48): "San Francisco",
(12, 47): "San Francisco",
(12, 49): "San Francisco",
(12, 50): "Seattle",
(12, 46): "Los Angeles",
(75, 35): "Mecca",
(74, 36): "Mecca",
(75, 34): "Mecca",
(74, 37): "Mecca",
(73, 37): "Mecca",
(76, 34): "Mecca",
(68, 57): "St. Petersburg",
(67, 56): "St. Petersburg",
(66, 56): "St. Petersburg",
(68, 56): "St. Petersburg",
(68, 58): "St. Petersburg",
(67, 58): "St. Petersburg",
(71, 54): "Moscow",
(71, 53): "Moscow",
(70, 53): "Moscow",
(72, 53): "Moscow",
(72, 54): "Moscow",
(70, 54): "Moscow",
(70, 56): "Moscow",
(71, 55): "Moscow",
(72, 55): "Moscow",
(69, 52): "Kiev",
(69, 51): "Kiev",
(70, 51): "Kiev",
(70, 50): "Odessa",
(71, 50): "Odessa",
(89, 35): "Bombay",
(89, 34): "Bombay",
(88, 35): "Bombay",
(88, 36): "Bombay",
(92, 34): "Pondicherry",
(91, 34): "Pondicherry",
(91, 33): "Pondicherry",
(102, 47): "Peking",
(101, 48): "Peking",
(102, 48): "Peking",
(103, 48): "Peking",
(101, 47): "Peking",
(103, 47): "Peking",
(101, 46): "Peking",
(102, 46): "Peking",
(103, 46): "Peking",
(115, 46): "Tokyo",
(115, 45): "Tokyo",
(114, 44): "Tokyo",
(106, 43): "Shanghai",
(106, 44): "Shanghai",
(106, 45): "Shanghai",
(107, 43): "Shanghai",
(107, 42): "Shanghai",
(105, 39): "Canton",
(105, 40): "Canton",
(106, 40): "Canton",
(106, 41): "Canton",
(103, 38): "Hong Kong",
(103, 39): "Hong Kong",
(104, 39): "Hong Kong",
(102, 38): "Hong Kong",
(102, 32): "Saigon",
(103, 33): "Saigon",
(102, 33): "Saigon",
(1, 38): "Honolulu",
(3, 37): "Honolulu",
(4, 35): "Hilo",
(5, 36): "Hilo",
(103, 14): "Perth",
(104, 14): "Perth",
(104, 13): "Perth",
(104, 12): "Perth",
(103, 12): "Perth",
(107, 19): "Darwin",
(107, 20): "Darwin",
(108, 20): "Darwin",
(108, 21): "Darwin",
(118, 13): "Sydney",
(117, 12): "Sydney",
(118, 15): "Sydney",
(118, 16): "Sydney",
(118, 17): "Sydney",
(114, 10): "Adelaide",
(114, 11): "Adelaide",
(113, 11): "Adelaide",
(115, 10): "Adelaide",
(77, 41): "Babylon",
(77, 40): "Babylon",
(76, 41): "Babylon",
(77, 42): "Babylon",
(76, 42): "Babylon",
(58, 47): "Marseilles",
(58, 48): "Marseilles",
(59, 48): "Marseilles",
(56, 48): "Bordeaux",
(56, 49): "Bordeaux",
(55, 51): "Brest",
(56, 51): "Brest",
(56, 44): "Gibraltar",
(54, 44): "Gibraltar",
(53, 54): "Cardiff",
(54, 54): "Cardiff",
(53, 55): "Cardiff",
(53, 53): "Cardiff",
(55, 56): "York",
(54, 56): "York",
(49, 54): "Cork",
(49, 56): "Galway",
(50, 56): "Galway",
(51, 54): "Waterford",
(50, 54): "Cashel",
(51, 57): "Belfast",
(50, 57): "Derry",
(50, 55): "Limerick",
(60, 55): "Copenhagen",
(60, 54): "Copenhagen",
(60, 53): "Copenhagen",
(62, 52): "Trondheim",
(63, 63): "Trondheim",
(64, 64): "Trondheim",
(60, 58): "Oslo",
(59, 57): "Oslo",
(58, 58): "Oslo",
(62, 58): "Uppsala",
(62, 59): "Uppsala",
(62, 60): "Uppsala",
(63, 58): "Stockholm",
(63, 57): "Stockholm",
(63, 56): "Stockholm",
(62, 56): "Stockholm",
(66, 59): "Helsinki",
(65, 59): "Helsinki",
(65, 58): "Helsinki",
(65, 58): "Vilnius",
(65, 55): "Vilnius",
(66, 54): "Vilnius",
(66, 55): "Vilnius",
(64, 53): "Vilnius",
(65, 53): "Vilnius",
(66, 53): "Vilnius",
(63, 50): "Vienna",
(64, 50): "Vienna",
(65, 50): "Vienna",
(65, 49): "Vienna",
(64, 49): "Vienna",
(63, 49): "Vienna",
(64, 52): "Warsaw",
(63, 51): "Warsaw",
(62, 48): "Venice",
(61, 48): "Venice",
(63, 48): "Venice",
(61, 43): "Palermo",
(62, 43): "Palermo",
(76, 32): "Sanaa",
(77, 32): "Sanaa",
(77, 33): "Sanaa",
(78, 33): "Sanaa",
(64, 12): "Cape Town",
(64, 13): "Cape Town",
(65, 13): "Cape Town",
(65, 12): "Cape Town",
(42, 18): "Rio De Janeiro",
(43, 19): "Rio De Janeiro",
(41, 18): "Rio De Janeiro",
(39, 15): "Montevideo",
(38, 14): "Montevideo",
(38, 13): "Montevideo",
(36, 13): "Buenos Aires",
(37, 13): "Buenos Aires",
(36, 12): "Buenos Aires",
(18, 39): "Mexico City",
(18, 38): "Mexico City",
(19, 39): "Mexico City",
(19, 40): "Mexico City",
(18, 49): "Mexico City",
(24, 45): "St. Louis",
(25, 45): "St. Louis",
(25, 46): "St. Louis",
(24, 46): "St. Louis",
(27, 38): "Havanna",
(28, 38): "Havanna",
(31, 37): "Port-au-Prince",
(31, 39): "Port-au-Prince",
(56, 50): "Nantes",
(59, 53): "Amsterdam",
(59, 52): "Amsterdam",
(28, 46): "Philadelphia",
(28, 45): "Philadelphia",
(27, 45): "Washington",
(27, 44): "Washington",
(96, 39): "Calcutta",
}




 	def nameNorm(pCity):
		coord = (pCity.getX(), pCity.getY())
		if coord in cityNameDict:
			name = cityNameDict[coord]
			pCity.setName(name)

so...how horrifically off-base am I? :crazyeye:
 
Like this:
Code:
	def onCityBuilt(self, argsList):
		'City Built'
		city = argsList[0]
		if (city.getOwner() == gc.getGame().getActivePlayer()):
			self.__eventEditCityNameBegin(city, False)	
		CvUtil.pyPrint('City Built Event: %s' %(city.getName()))
		[B]DawnOfTime.nameNorm(argsList[0])[/B]
But since argsList[0] has already been assigned to a variable you'd get away with this also:
Code:
	def onCityBuilt(self, argsList):
		'City Built'
		[B]city = argsList[0][/B]
		if (city.getOwner() == gc.getGame().getActivePlayer()):
			self.__eventEditCityNameBegin(city, False)	
		CvUtil.pyPrint('City Built Event: %s' %(city.getName()))
		DawnOfTime.nameNorm([B]city[/B])
Makes sense?

as for my mod file, I want to put everything I'm going to do in one file, DawnOfTime.py ...here's what it looks like so far (some of the import things I don't need right now (like the popup) but I'll need for the rest of the mod down the road)
Basically you need to know why you're doing something or you shouldn't be doing it. I'm very impressed with anyone who actually tries to learn this stuff, but ask yourself why you've added this line for example:
Code:
pCity = pPlot.getplotCity()
This will cause a Python exception because the name pPlot hasn't been defined anywhere. The interpreter simply doesn't recognize it - and it could be a module, a class, a function or a variable. It doesn't compute, as simple as that.

Do you even know what the variables with the "p" prefix (like pCity and pPlot) are? (If not, I'd gladly explain. Because its vital to know this!)

so...how horrifically off-base am I? :crazyeye:
Not very off-base at all, its just that you're doing things without knowing why you're doing them. Like there is an extra level of indentation for the function definition. (Another exception right there - your module won't even load.) If you don't know why the definition line is intended how do you know it should be? Its like you're either copying something else - or plain guessing. If you knew the basics of indentations levels you wouldn't need to do neither. (And I'd gladly explain if something is unclear at this point.)

But as I said, I'm very exited that you're nearing actual results with this. Try to make a point of understanding every single thing that you do. If you can't find the answer in any of the guides/tutorials/books - just ask. Because there is a very good and perfectly logical explanation for (almost) everything. So there is no reason not to know what you're doing. Not when all the information is readily available. Ask away - we could go through the whole thing line by line if you need to.
 
Makes sense?

yes, perfect sense.

please do explain both those other items as I'm reading four different guides simultaneously while trying to stay on top of lesson plans and whatnot ;-)
 
Ok, here goes:

CivIV is basically coded in a programming style called Object-Oriented Programming, and AFAIK this is true both for the C++ and the Python portions of the game. This involves using "classes" and "methods". I'll try to cover the bare minimum here, but the subject is covered in great detail in chapters 12-16 in the textbook.

So, try to imagine all the different game elements of CivIV as "actual entities" - at least as far as the game is concerned. Think of the different players/Civs, cities, units, plots and whatnot. Now, these are all "objects" in computing terms. To be more specific - they are class instances - instances of a class.

So if we pretend that CivIV would be written in Python (which its not - the Python is merely an extension of the C++ code in the SDK/DLL), then these game elements, these independent entities, these objects, would be created with classes. So a city class would be called CyCity, for example:
Code:
class CyCity:
    <method definitions>
The methods are basically functions but they belong to that class - and are accessed with a class instance. So if we go
Code:
CyCity()
...then we've just created a new instance of the CyCity class. An actual city object - a CyCity instance - as "real" or "valid" as any city in the game. (Still pretending that the game is coded in Python. :rolleyes:) But we should assign the new city - the CyCity instance - to a variable of some sort. The naming convention for class instances is the prefix p (for "pointer") just as i is for integer values, t is for tuples and l is for lists.
Code:
pCity = CyCity()
Now we can use the CyCity instance any time we want with the pCity variable. One of the CyCity methods is CyCity.setName() so its available to all instanced of the CyCity class. Like pCity:
Code:
pCity.setName()
What you're looking at above is called dot-notation. First you put a object of some sort, then you add a dot, and finally a method (of that class). Now you can probably figure out why its called "Object-Oriented" Programming. Note that strings, lists, tuples and all other data structures (like dictionaries) are also objects. So they have methods belonging the their respective "classes". The textbook is full of examples of this.

Back to reality: All the various game "objects" are in reality created by C++ code and not Python, but they are still available to Python modding via the CivIV Python API. (Note that its titled "Sid Meier's Civilization IV Python Class Reference".) You can use methods found in the API for creating new instances of the various classes (like PyCity) but they are still created in the core executable. (The game differentiates between Cv classes that are the originals created with C++ and their mirror images the Cy classes.)

But you can't of course create a CyCity object with the phrase "CyCity()" - its all just an illusion. Instead there is a initCity() method belonging to the CyPlayer class. So firstly you'd have to have a valid CyPlayer instance (an instance of the CvPlayer class - masked as a CyPlayer instance for Python purposes). Lets call it pPlayer. All you need to do is:
Code:
pPlayer.initCity(x, y)
If you lookup the CyPlayer.initCity() method in the API you can see that the first thing in the entry is the word "CyCity" - this is in fact what the method returns. I will just assume that you know what a "return value" is - at this point. So by invoking the initCity() method on the CyPlayer instance you get a - brand new CyCity instance (via the SDK where its created as a CvCity instance, but whatever). You could assign this return value to a variable:
Code:
pCity = pPlayer.initCity(x, y)
And then use that CyCity object to invoke CyCity methods, like CyCity.setName():
Code:
pCity.setName(string)

I fully understand if your head is spinning right now, but as I said - this is vital information. So try to digest it before going forward.

As for indentation: Every time you add some number of white space at the beginning of a line of Python you also create a new block of code. That block of code "belongs" to the previous line (the one with the colon at the end).

The indentation level starts a module level or __main__ and is always equal to zero number of white space. So your function would start out here - no indentation at all for the first line. But since the def line has a colon at the end this means that all the lines following should be a new block of code. So you add some number of whitespace. And when some line in that block of code ends with a colon - then you add the same amount of white space to create yet another block of code. And so on.

Questions?
 
Another example of Object-Oriented Programming would be my own PyScenario application. As you probably already know its used for event scripting with scenarios (currently only with the RFC Epic/Marathon mod). So there is a Trigger class:
Code:
class Trigger:
        
        def __init__(self, label=None):
                scenario.triggers.append(self)
                self.description = label
                scenario.labels[label] = self
                self.ShowMessage = None
                self.targetPlayer = None
                self.coords = None
                self.plotList = None
                self.keyList = list()
                self.Conditions = Conditions()
                self.Actions = Actions()
                self.onlyOnce = False
                self.isFired = False
                self.active = False
                debug("Trigger initialized: ", self.debug())

        def debug(self):
                if isValid(self.description):
                        return "'" + self.description + "'"
                return "(unlabeled " + str(self)[1:-1] + ")"
What you have here is a class and two method definitions. The __init__() method is automatically invoked any time the Trigger() constructor is called. So all those assignment statements are done for each new instance of the Trigger class on initialization.

In case you wonder about all the "self" bits - those are just references to the instance/object itself - and all this is of course covered in the textbook once you feel like knowing more about this subject. (Not really required for simple Python scripting.)

Two notable assignment statements are:
Code:
                self.Conditions = Conditions()
                self.Actions = Actions()
These are class constructors just like Trigger(). So what this code does is it creates an instance of the Conditions class and an instance of the Actions class and assigns them to the values self.Conditions and self.Actions. (The correct names would probably by self.pConditions and self.pActions, but whatever. Its my program, after all. :p)

There is no real difference between my Trigger instances and the objects created by the game. So for the game a CyPlayer instance or a CyCity instance is as tangible as a Trigger instance. And any Conditions or Actions instance that is assigned as a field of that Trigger instance is just as "concrete" as any map tile or unit in the game. So what I've done here is actually something pretty potent! (And it was pretty easy to do to boot. :D)

The second method definition is the debug() method. What it does isn't of any interest here (it prints out a debug message) but rather how its used (or could be used). Lets suppose that I have some code creating a Trigger class instances - and I have. So I could create a new Trigger object and assign it to a variable:
Code:
pTrigger = Trigger()
Then I can invoke the debug() method on the new instance of the Trigger class by going:
Code:
pTrigger.debug()
I can of course skip the variable altogether if I, for some reason, only wanna create the instance to use the method:
Code:
Trigger().debug()
A class instance is something completely different from say a integer value. So all the players (CyPlayer instances) and teams (CyTeam instances) are enumerated by the SDK code. Lets for the sake of argument call those simple integer values, ranging from zero and up. There is a real difference between the index number of the third CyPlayer object (2) and the CyPlayer instance itself. This is the reason why the reference to the CyPlayer instance is called pPlayer and the player ID number is assigned to the variable ePlayer (or iPlayer in RFC). Because a integer value is not the same thing as the actual class instance, which isn't any number but rather a "real game object".

As trivia, you can get the CyPlayer instance of any player ID with the method CyGlobalContext.getPlayer(ePlayer) and likewise you can get the player ID of any CyPlayer instance with the method CyPlayer.getID(). This is basically true for all the major Cy classes in the API (check to verify).
 
Back
Top Bottom