trying to use a minimalist mod to do big things

alright...eliminating the last argument (facing direction) spawns in the right place!

however...it spawned 19 settlers...

This is interesting as when I would get the error popup it would get multiple ones. It seems to want to follow the code at the start of every civ's turn (18+barbarians would give me the 19...)
 
got it...just moved the command from beginplayerturn to begingameturn.

Now...need to set them at war with the celts and the greeks so dropping the units on the city will capture it (if it exists)
 
actually...I don't think I need to make war...just to look on the spawn plot and if there is a city there, use acquireCity to simply give it to the new civ...
 
no, I do after all.

Alright...here are my next three short-range goals

1) before the unit spawn, declare war with selected future neighbors
2) search the plot for the existence of a city and, if one exists, flip it to the new owner
3) remove all culture on selected tiles in the area to be flipped and replace it with the new culture (this is so that the new civ has some breathing room.

...oh yes...and the announcement "the Latin people have declared their independence"...that's probably the easiest part...I guess that makes four...
 
I really think you need to back up instead of move forward. Because you only got the unit to spawn by accident - and learned nothing.

I'm not sure what the exception was about, but PyPlayer.initUnit() and CyPlayer.initUnit() aren't at all the same thing. I thought that it would be helpful for you to use PyHelpers for spawning your first unit, because the CyPlayer method is kinda weird to be honest. But right now you probably don't even know what you did, how it works or really anything.

If you post your current script we could sort it out firstly.

edit: The other guy is absolutely correct - you substituted the player ID with the civilization's ID. This is why the exception was complaining about a None type object, or whatever. You clearly missed what EmperorFool told you earlier...
 
This is an example of a missed opportunity for learning:
Code:
	if (gc.getGame().getGameTurnYear() == -3985)):
So you fund the code somewhere and you used it. It worked so you're fine with never understanding why your original code didn't work and why this does. Because it gets the job done, right?

Well, wrong. You're probably not gonna use this if statement in any final release anyway, so why not try to understand how this works for when you need to make your own? Because you will need to be able to repeat this in another context.

First of all: What are those parenthesis around the boolean expression for? If you can't answer the question you probably should ask yourself why you're not even interested to know what it does?
Spoiler :
It does absolutely nothing, so you should probably not pick up bad habits like this from copying other peoples code. But since the parenthesis don't cause any problems either, you could make a conscious rule about always wrapping booleans statements into parenthesis - for clarity. Otherwise you probably shouldn't use them as it will only serve to confuse you.

Secondly, why does gc.getGame().getGameTurnYear() work and not just getGameTurnYear()? What does the gc.getGame() add to it? But wait a minute - isn't getGameTurn() a CyGame method and not CyGlobalContext? :confused: The answer is right there, but it might not be obvious.
Spoiler :
gc refers to a CyGlobalContext instance. So that means that getGame() is a CyGlobalContext method - while getGameTurnYear() is not. If you lookup the CyGlobalContext.getGame() method in the API you'll realize that it returns a CyGame instance. So now you have one to use with getGameTurnYear(). This is why it works.

But since you already have a ready-to-use CyGame instance defined as a constant, you probably wanna use that instead: cyGame.getGameTurnYear(). So you don't ever need to use gc.getGame() in your script.

As for the if statement itself, its actually made up of three different things:

Firstly we have the method invocation:
Code:
cyGame.getGameTurnYear()
Since all functions/methods return something, we have to know what this particular method returns. According to the API it returns a integer value. We could assign this to a variable:
Code:
iCurrentYear = cyGame.getGameTurnYear()
Next up we have a boolean statement - and we can use the variable instead of the method invocation for greater clarity:
Code:
iCurrentYear == -3980
Since this is a boolean expression it will either equate to True or False. On the first game turn its False, on the second game turn its True and on any subsequent game turn its False again. We could assign this value to a variable also:
Code:
bIsSpawnDate = iCurrentYear == -3980
(Note the difference between the assignment operator (=) and the equal-to operator (==)!)

So the if statement could simply be:
Code:
if bIsSpawnDate:
The whole thing could be expressed as:
Code:
   iCurrentYear = cyGame.getGameTurnYear()
   bIsSpawnDate = iCurrentYear == -3980
   if bIsSpawnDate:
      ...
Which of course is the same as:
Code:
   if cyGame.getGameTurnYear() == -3980:
So the if statement can really either be if True: or if False:. Which is all that a if statement ever does! It executes the block of code following it if all the expressions included equate to True. Otherwise that block of code will not be executed, at all.

There are actually several important lessons here. If you take the time to understand them then all this won't be for nothing and you'll be able to use all of it going forward. You don't need to copy-paste stuff from other people's modules, because all you need is some basic knowledge to make your own code. But you can only learn how to do this if you want to know how to do it.

It wouldn't be hard (or even much work) for someone who already knows Python to just make your code for you, to your exact specifications, if you only care about getting it to work rather than understanding how to do it yourself. And there is nothing wrong with wanting results, but it is obviously painful for you to learn in this manner. It doens't have to be this hard.
 
Furthermore, I thought I'd explain what PyHelpers is and what it does.

If you open up PyHelpers.py (found in the Assets\Python\ folder of the game/BtS) you'll see class and method definitions. Like these:
Spoiler :
Code:
class PyPlayer:
	' CyPlayer Helper Functions - Requires Player ID to initialize instance '
	
	def __init__(self, iPlayer):
		' Called whenever a new PyPlayer instance is created '
		if iPlayer:
			self.player = gc.getPlayer(iPlayer)
		else:
			self.player = gc.getPlayer(0)
...
	def initUnit(self, unitID, X, Y, iNum = 1):
		"none - spawns unitIdx at X, Y - ALWAYS use default UnitAIType"
		if (iNum > 1): #multiple units
			for i in range(iNum):
				self.player.initUnit(unitID, X, Y, UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION)
		else:
			return self.player.initUnit(unitID, X, Y, UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION)
But lets back up to when I told you to use this in the console:
Code:
PyHelpers.PyPlayer(eRome).initUnit(eSettler, 61, 46)
Ok, the first bit ("PyHelpers") is the name of the module where the PyHelpers class is found. The second bit ("PyPlayer(eRome)") is the class constructor and it actually takes an argument - just like a function/method. What happens any time you call the class constructor is that the __init__ method of that class is invoked. This is the definition line (from the code above):
Code:
def __init__(self, iPlayer):
You always need to ignore the first parameter within a class method definition (they aren't even displayed in the CivIV Python API), because it must always be "self". This is rather complex and not really important at this juncture, but the parameter is a reference to the class instance it-self. So then there really is just the one iPlayer parameter. And you would have supplied it with the whatever value that the eRome constant is referring to. So within the __init__ method iPlayer will be equal to eRome then. (Take a moment to contemplate this.)

This is what the method does with your eRome value:
Code:
self.player = gc.getPlayer(iPlayer)
Firstly it gets the CyPlayer instance corresponding to the player ID referenced with by iPlayer using CyGlobalContext.getPlayer(). Secondly it assigns that reference to the field PyPlayer.player (remember that self is a reference to the class instance). This is practically a variable that is available in all methods belonging to this instance/class (via the self reference that is passed on at every juncture). You could probably revisit what exactly this means at a later date.

Then we have the initUnit() method - this is the definition line:
Code:
def initUnit(self, unitID, X, Y, iNum = 1):
You can count to 5 parameters inside the parenthesis, right? Well, the first one ("self") doesn't really count, so there are four left. But the last one is actually assigned a value, namely the integer value 1. So this is a optional argument then - if not specified within the method invocation it will be set to this default value - and is thus supplied anyway. This leaves us with only three required parameters: unitID, X and Y.

unitID is the UnitTypes value and can also be expressed with a integer value. The X and Y parameters are pretty obvious. This is all the parameters you need for using PyPlayer.initUnit(), even though you might wanna use the fourth iNum parameter also.

But now for the interesting part: This is what we find inside the method definition:
Code:
self.player.initUnit(unitID, X, Y, UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION)
This explains how PyHelpers can be used for spawning units - it uses the CivIV Python API. Of course. The "self.player" part we covered above - this is the CyPlayer instance corresponding to the iPlayer parameter - which in your case was the eRome variable. So we have a valid CyPlayer (or should have - you didn't because your eRome value wasn't a valid player to begin with, so it returned None with CyGlobalContext.getPlayer()) and can now invoke initUnit() on it. And as you can see, the method is using all five required parameters, including UnitAITypes and DirectionsTypes.

The point of using PyHelpers instead of the regular API then, is of course that you don't have to bother with all the parameters. But not just that - if you wanna create several units you just change the last iNum parameter to whatever value corresponds to the number of units you need. Because the method has its own built-in iteration code saving you the trouble of setting that up every time you wanna spawn more than one unit. So you would spawn 3 Settlers like this:
Code:
PyHelpers.PyPlayer(eRome).initUnit(eSettler, 61, 46, [COLOR="Red"]3[/COLOR])
This is very convenient, not? Because this is basically what you'd have to do without PyHelpers:
Code:
iNumUnits = 3
while iNumUnits > 0:
   pPlayer.initUnit(eSettler, 61, 46, UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION)
   iNumUnits = iNumUnits - 1
The module is called PyHelpers for a reason.
 
mea culpa, mea culpa =(

alright...one thing at a time (I'm basically writing this as I read your responses to ensure that I don't forget anything and that I deal with issues in the order you raise them). I was interested about the extra parenthesis (there is one that opens, but two that close... "(gc...3985))"), but I found that if I deleted it (one of the ones after the 5) I got a syntax error. As for enclosing the entire phrase in parentheses in the first place, you are correct, I did that because my example from CvEventManager did so.

I knew gc was for global context because I remembered having to define that up at the top of the file, but I'm really not clear on this:

gc refers to a CyGlobalContext instance. So that means that getGame() is a CyGlobalContext method - while getGameTurnYear() is not. If you lookup the CyGlobalContext.getGame() method in the API you'll realize that it returns a CyGame instance. So now you have one to use with getGameTurnYear(). This is why it works.

I did know from the API that getgameturnyear was an integer, and I do see what you mean with the iCurrentYear assignments and bSpawnDate. I do, however, like the simpler assignment cyGame.getGameTurnYear() == -3980 (where you simplified my original line)

I did understand that about the if statement looking for true or false (in this case, if the year is -3980 it is true, if it is any other year, it is false...the reference to dawnoftime.function in cveventmanager determines when the game looks to see if it's true)

entering that line in the console was only giving me an error, which is why I went to testing with the actual file (I can't paste text into the console, and it is neon green text against the map in the background, making it hard to see if I have any typos).

I understand what you said about init and referencing self.

here's the way it stands right now (with the suggestions you just made). I've got some things commented out as placeholders and reminders. I think I'm going to just stick with using straight integers for the civs and units...I'll take out the constant definitions I don't need before the final release:

Spoiler :
Code:
#done by Antaine with great indebtedness to Baldyr, God-Emperor, and EmperorFool

from CvPythonExtensions import *
from Popup import PyPopup
from CvEventManager import *
from PyHelpers import *

gc = CyGlobalContext()
cyGame = CyGame()
cyMap = CyMap()
pyGame = PyGame()

#units for later civ spawns
eSettler = 4
eWorker = 5
eArcher = 57
eLongbowman = 60
eMusketman = 42
eRifleman = 46

#for barbarian spawns
eGalley = 90
eViking = 36
eZulu = 38
eMongol = 68
eNative = 33
eAztec = 27
eInca = 25
eMaya = 39

#civs in the game
eCeltia = 6
eEgypt = 8
eBabylon = 3
eGreece = 13
eIndia = 16
eChina = 7
eRome = 28
ePersia = 26
eJapan = 17
eKorea = 19
eArabia = 1
eEngland = 9
eFrance = 11
eSpain = 30
eGermany = 12
eRussia = 29
eAmerica = 0


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, 39): "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",
(58, 42): "Carthage",
(59, 42): "Carthage",
(59, 41): "Carthage",
(59, 40): "Carthage",
(60, 40): "Tripoli",
(61, 40): "Tripoli",
(62, 40): "Tripoli",
(47, 61): "Reykjavik",
(47, 62): "Reykjavik",
(48, 62): "Reykjavik",
(46, 63): "Reykjavik",
(69, 41): "Knossos",
(96, 39): "Calcutta"
}



#searches in the dictionary to get a name for a key tile
def nameNorm(pCity):
   coord = (pCity.getX(), pCity.getY())
   if coord in cityNameDict:
      name = cityNameDict[coord]
      pCity.setName(name, True)
      return True
   else:
      return False


#colony spawn for Greeks and Celts 1600BC
def ancientSpawn():
	if cyGame.getGameTurnYear() == -1600:
#Celts
		PyHelpers.PyPlayer(0).initUnit(4, 51, 55, 1)
		PyHelpers.PyPlayer(0).initUnit(5, 51, 55, 2)
		PyHelpers.PyPlayer(0).initUnit(57, 51, 55, 3)
		PyHelpers.PyPlayer(0).initUnit(4, 54, 55, 1)
		PyHelpers.PyPlayer(0).initUnit(5, 54, 55, 2)
		PyHelpers.PyPlayer(0).initUnit(57, 54, 55, 3)
		PyHelpers.PyPlayer(0).initUnit(4, 55, 58, 1)
		PyHelpers.PyPlayer(0).initUnit(5, 55, 58, 2)
		PyHelpers.PyPlayer(0).initUnit(57, 55, 58, 3)
#Greeks
		PyHelpers.PyPlayer(5).initUnit(4, 68, 46, 1)
		PyHelpers.PyPlayer(5).initUnit(5, 68, 46, 2)
		PyHelpers.PyPlayer(5).initUnit(57, 68, 46, 3)
		PyHelpers.PyPlayer(5).initUnit(4, 69, 41, 1)
		PyHelpers.PyPlayer(5).initUnit(5, 69, 41, 2)
		PyHelpers.PyPlayer(5).initUnit(57, 69, 41, 3)
		PyHelpers.PyPlayer(5).initUnit(4, 64, 47, 1)
		PyHelpers.PyPlayer(5).initUnit(5, 64, 47, 2)
		PyHelpers.PyPlayer(5).initUnit(57, 64, 47, 3)

#spawns the classical civs 800BC on the map (different date used for testing change before release)
def classicalSpawn():
	if cyGame.getGameTurnYear() == -3985:
#Romans
		PyHelpers.PyPlayer(7).initUnit(4, 61, 46, 1)
		PyHelpers.PyPlayer(7).initUnit(5, 61, 46, 2)
		PyHelpers.PyPlayer(7).initUnit(57, 61, 46, 5)
#Persians		
		PyHelpers.PyPlayer(8).initUnit(4, 81, 41, 1)
		PyHelpers.PyPlayer(8).initUnit(5, 81, 41, 2)
		PyHelpers.PyPlayer(8).initUnit(57, 81, 41, 5)
#Japanese
		PyHelpers.PyPlayer(9).initUnit(4, 115, 46, 1)
		PyHelpers.PyPlayer(9).initUnit(5, 115, 46, 2)
		PyHelpers.PyPlayer(9).initUnit(57, 115, 46, 5)
#Koreans
		PyHelpers.PyPlayer(10).initUnit(4, 109, 46, 1)
		PyHelpers.PyPlayer(10).initUnit(5, 109, 46, 2)
		PyHelpers.PyPlayer(10).initUnit(57, 109, 46, 5)

#Roman colony spawn 1AD
def romeSpawn():
	if cyGame.getGameTurnYear() == 1:
#Lisbon
		PyHelpers.PyPlayer(7).initUnit(4, 53, 45, 1)
		PyHelpers.PyPlayer(7).initUnit(5, 53, 45, 2)
		PyHelpers.PyPlayer(7).initUnit(57, 53, 45, 5)
#Carthage
		PyHelpers.PyPlayer(7).initUnit(4, 58, 42, 1)
		PyHelpers.PyPlayer(7).initUnit(5, 58, 42, 2)
		PyHelpers.PyPlayer(7).initUnit(57, 58, 42, 5)
#Tripoli
		PyHelpers.PyPlayer(7).initUnit(4, 60, 40, 1)
		PyHelpers.PyPlayer(7).initUnit(5, 60, 40, 2)
		PyHelpers.PyPlayer(7).initUnit(57, 60, 40, 5)

#spawns Arab civ 400AD
#def arabSpawn():
#	if cyGame.getGameTurnYear() == 400:

#spawns medieval civs 800AD
#def medievalSpawn():
#	if cyGame.getGameTurnYear() == 800:
#English
#French
#Spanish
#Germans
#Russians

#spawns German colonies in Scandinavia and Iceland 1200AD
#def scandiSpawn():
#	if cyGame.getGameTurnYear() == 1200:

#American spawn and flip of eastern US 1770AD
#def ameriSpawn():
#	if cyGame.getGameTurnYear() == 1770:

#American spawns and flip of western US and Hawaii 1900AD
#def ameriFlip():
#	if cyGame.getGameTurnYear() == 1900:

#colony spawns tied to discovery of Astronomy
#colonySpawn
#Arabs
#English
#French
#Spanish
#Russia

#spawns medieval barbarians 1000AD
#def barbSpawn():
#	if cyGame.getGameTurnYear() == 1000:
#Vikings
#Mongols

#spawns native Americans 1400AD
#def nativeSpawn():
#	if cyGame.getGameTurnYear() == 1400:
#North America
#Aztec
#Inca

What do you think the next step should be?
 
(I'm also interested to know more about the other way of calculating the year so that different speeds can be enabled easier. I'm going back to read that now, but I remember we weren't actually "there" yet, so there may be more explanation/lesson you'd like to go with that)

edit: ah, it seems you weren't able to get this to work, either. well...I may just end up enabling only marathon speed...
 
I was interested about the extra parenthesis (there is one that opens, but two that close... "(gc...3985))"), but I found that if I deleted it (one of the ones after the 5) I got a syntax error.
Just to be perfectly clear: Anytime you use a parenthesis (or a bracket, or a curly brace, or a quotation, or...) it has to be closed again. Otherwise its an instant syntax error.

I knew gc was for global context because I remembered having to define that up at the top of the file, but I'm really not clear on this:
These are the two methods in question - you've already looked them up in the API:
CyGame.getGameTurnYear()
CyGlobalContext.getGame()


The first one returns a integer and the second one returns a CyGame instance.

If you go:
Code:
CyGlobalContext().getGame()
Then you get a return value consisting of a CyGame instance. You could assign it to a name/variable if you like:
Code:
game = CyGlobalContext().getGame()
And then you could use the variable as a valid CyGame instance for getGameTurnYear():
Code:
game.getGameTurnYear()
Which would give you the integer you're looking for. But the variable isn't needed (although it becomes much clearer this way) and you can just use the return value from getGame() directly as a CyGame instance:
Code:
CyGlobalContext().getGame().getGameTurnYear()
Contemplate this and let it sink in.

Another example:
Code:
pUnit = pPlayer.initUnit(eType, iX, iY, eAI, eDirection)
pUnit.setName(name)
So the initUnit() method returns a CyUnit instance, which gets assigned to the pUnit variable. Then, on the next line you invoke the CyUnit.setName() method on the pUnit variable - which points to the CyUnit instance returned on the line above. But you could also do this:
Code:
pPlayer.initUnit(eType, iX, iY, eAI, eDirection).setName(name)
Since the return value isn't being assigned to any variable it is still available for another method. But the setName() method returns nothing (well, actually None) so you couldn't stack yet another method on top of it.

Stacking methods like this is actually the whole design principle behind my PyScenario application. Because all the Trigger methods return the Trigger instance (=self), so you can stack virtually any number of methods to one single Trigger constructor, like:
Code:
Trigger().player(0).check().area().valid().city(45,34).buildings(0).religions(0,1).units().AI().promotions(3,4)
Simply because each of these methods ends with the line:
Code:
		return self
here's the way it stands right now (with the suggestions you just made). I've got some things commented out as placeholders and reminders. I think I'm going to just stick with using straight integers for the civs and units...I'll take out the constant definitions I don't need before the final release:
First of all: These two lines of code don't match:
Code:
from PyHelpers import *
Code:
		[COLOR="Red"]PyHelpers.[/COLOR]PyPlayer(0).initUnit(4, 51, 55, 1)
The way you're importing PyHelpers means that all the constants and classes of PyHelpers become part of your module. So you don't use the PyHelpers module name to access the PyPlayer class anymore - the PyPlayer class is available in your module.

Either change the import line or the unit spawning line. (Read what I wrote last night on this subject for more information.)

As for your constants for the various enumerated players - are you sure that you're using player IDs and not Civilization IDs? Is the distinction between the two obvious to you yet? (Right now there seems to be at least 68 players in your mod. Really? :eek:) You cannot move forward until you are absolutely certain on this!

Next up:
Code:
	if cyGame.getGameTurnYear() == -1600):
#Celts
		PyHelpers.PyPlayer(0).initUnit(4, 51, 55, 1)
		PyHelpers.PyPlayer(0).initUnit(5, 51, 55, 2)
		PyHelpers.PyPlayer(0).initUnit(57, 51, 55, 3)
		PyHelpers.PyPlayer(0).initUnit(4, 54, 55, 1)
		PyHelpers.PyPlayer(0).initUnit(5, 54, 55, 2)
		PyHelpers.PyPlayer(0).initUnit(57, 54, 55, 3)
		PyHelpers.PyPlayer(0).initUnit(4, 55, 58, 1)
		PyHelpers.PyPlayer(0).initUnit(5, 55, 58, 2)
		PyHelpers.PyPlayer(0).initUnit(57, 55, 58, 3)
#Greeks
		PyHelpers.PyPlayer(5).initUnit(4, 68, 46, 1)
		PyHelpers.PyPlayer(5).initUnit(5, 68, 46, 2)
		PyHelpers.PyPlayer(5).initUnit(57, 68, 46, 3)
		PyHelpers.PyPlayer(5).initUnit(4, 69, 41, 1)
		PyHelpers.PyPlayer(5).initUnit(5, 69, 41, 2)
		PyHelpers.PyPlayer(5).initUnit(57, 69, 41, 3)
		PyHelpers.PyPlayer(5).initUnit(4, 64, 47, 1)
		PyHelpers.PyPlayer(5).initUnit(5, 64, 47, 2)
		PyHelpers.PyPlayer(5).initUnit(57, 64, 47, 3)
Besides the references to the PyHelpers module (see above) the unit spawning code will only fire if the game date is exactly 1600 BC. Just to make this perfectly clear, taking into account various game speeds and such (like you might wanna mess around with the calendars in the future, which would potentially break whatever setup you design this way).

Furthermore, this kind of repetitive coding will make any programmer cringe. :p This is simply not a good implementation, or perhaps not a efficient way of programming. But I think stability of the code and keeping things simple gets you off the hook this time around, as this is your first venture into programming. :D

Basically you could only have one single function for all the Civ spawns. And your code only needs one line with the PyPlayer.initUnit() function in it. If you wanna learn how to do this the proper way, that is. This would involve creating new data structures, like the dictionary you did for the city names and coordinates:
Code:
unitsSpawnCoordinates = {
eCeltia: [ (51, 55), (54, 55), (55, 58) ],
eGreece: [ (68, 46), (69, 41), (64, 47) ],
}

startingSpawnUnits = {
eCeltia: [ (4, 1), (5, 2), (57, 3) ],
eGreece: [ (4, 1), (5, 2), (57, 3) ],
}
(If all ancient Civs have the same entries, there is of course no need to duplicate this information either. You could just have a list of units and numbers of units for each age - and then divide all Civs into different groups. More complexity, but less repetition.)

I'm certainly not saying that it will be easier to implement a dynamic function that will index all these dictionaries and apply the right number of units spawns on the correct map tiles. But I though that you should at least know about this kind of setup before you do any more of this copy-pasting business.

And if you wanna do something like this instead (the exact makeup of the actual data structures is of course up to you) someone might be able to make the code for you. I'm not sure if you would learn more or less from this though... (Well, I'd gladly explain anything, but you already know that... :p)

What do you think the next step should be?
Firstly clean up the code you have. Make sure you're importing PyHelpers correctly ("import PyHelpers" is sufficient) and that all the enumerated constants have the correct values.

Then decide how you wanna script all the various spawns. By copy-pasting the same line of code several hundred times - making it harder to change the setup later - or by putting all the info into data structures instead. You don't wanna have to redo everything later, so you might as well make a decision now - and live with it.

What you wanna tackle next up is basically your choice... Are you happy with the set spawn dates, by the way? If you wanna move the spawn condition away from onBeginGameTurn and a set spawn date you could move the function call to another callback in the Event Manager. Like onTechAcquired for spawning with Techs.

Figure out the basic design for you mod before you do too much code that needs to be replaced once you figure out what you wanna do.
 
(I'm also interested to know more about the other way of calculating the year so that different speeds can be enabled easier. I'm going back to read that now, but I remember we weren't actually "there" yet, so there may be more explanation/lesson you'd like to go with that)

edit: ah, it seems you weren't able to get this to work, either. well...I may just end up enabling only marathon speed...
No, no... It works fine with PyScenario where I use it. It was just a lot of work to get it right, and you can't just copy-paste whatever my exact code was, because the whole context is very different. But we can absolutely figure that out. I can supply you with a conditional statement to test out. We can debug it and fix any issues, for sure. :goodjob:

Unless you wanna go dynamic with Tech triggers instead. :king:
 
Just add this function to your module, as a "helper function":
Code:
def isDate(iDate):
    return cyGame.getTurnYear(cyGame.getGameTurn() +1) > iDate and cyGame.getGameTurnYear() <= iDate
Now, simply replace the boolean expression with isDate(-1600) in your conditional statement and it should work the same. Plus it would fire on the appropriate turn on any game speed! :king:

Like this:
Code:
	if isDate(-1600):
 
Also, just because PyPlayer.initUnit() is a wrapper method for the CyPlayer.initUnit() method doesn't mean that you can't wrap that up in a spawnUnits() function - another helper function for your mod:
Code:
def spawnUnits(ePlayer, eType, iX, iY, iNum):
	PyPlayer(ePlayer).initUnit(eType, iX, iY, iNum)
You don't need to do any of this, its just an example of one style of programming you could adopt... :king: As long as you understand what you're doing and it doesn't confuse you too much you can experiment with such things yourself also. Its actually a good way of practical learning. Little programming jobs to get you familiarized with functions - definitions and calls.
 
the enumerated player constants are for the ids from the API, which are not the ones I need for this. If I never use them, I'll take them out when I'm done, but since I'd found and listed them all I figured I'd leave the definitions for the time being in case I need them later. I'm using the right ids (the ones from the WBS file) after PyPlayer.

I'm keeping the import * and eliminating PyHelpers from the lines of code.

I know that the line repetition is not proper etiquette, but for now it lets me see what's happening, making it a little easier to tinker.

I've added your dating suggestion.

I think the next thing that makes sense to try to do is lay out the welcome mat for the spawning civs. In seeing how stuff goes, I think Rhye had the right idea after all. I'm going to need to construct a "kill box" which will remove all culture from selected tiles as well as deleting all units and cities which may be found on them&#8230;then planting the newly spawning civ. I'd also like to set war with certain neighbors to encourage expansion (I may need to beef up the starting garrisons, but that's easy enough now that I know how to spawn units).

All the initial spawns are related to years, as are some of the early colonies, but most colonies will be tied to the civ acquiring Astronomy.

Also (but this is only once I get the whole "kill box" concept squared away), I'm going to need to do a non-random Holy City reallocation (just in case a holy city is destroyed in this process. Basically, and I'll use Judaism as an example here, I want to script it to do the following
When Judaism is founded, make Jerusalem the holy city if Jerusalem exists (if not, follow the default procedure). When Jerusalem is founded, make it the holy city of Judaism if Judaism already exists.

Basically, this will force Jerusalem to be Judaism's holy city while ensuring that a holy city for that religion is likely to stay on the map (even if it's razed and re-founded by someone else it will become the new holy city). The same can be done with cities like Rome and Mecca for Christianity and Islam, and a city (each) which I will select for it's likelihood of being on the map most of the time for Hinduism, Taoism, Confucianism and Buddhism.

But for now, the kill box. I should set out to define the areas I want to "kill". Should I do like we did in Rhye's by defining opposite corners and writing code to loop through all the tiles in the resulting box, or should I define lists of coords to produce my shapes. Also, can this simply be listed at the start of the existing spawn code? or will I have to have this happen one turn removed from the spawn. I don't want to kill the units I'm making instantly. I know that it follows the code in order, but does everything become effective at the end of the function or as it happens?

I'm thinking if we use the RFC method for defining the kill boxes, we can have an interesting lesson on loops and tuples ;-)
 
I think the next thing that makes sense to try to do is lay out the welcome mat for the spawning civs. In seeing how stuff goes, I think Rhye had the right idea after all. I'm going to need to construct a "kill box" which will remove all culture from selected tiles as well as deleting all units and cities which may be found on them…then planting the newly spawning civ. I'd also like to set war with certain neighbors to encourage expansion (I may need to beef up the starting garrisons, but that's easy enough now that I know how to spawn units).
First of all, this will be a big job. If you wanna take the easy way out you could probably borrow some of Rhye's code from RFCUtils. You could in fact use the module for development purposes, so go and see what methods you can find there! (Then its only a matter of importing the module, getting a class instance for the methods found in the module, and to figure out how to invoke the methods.)

What you will soon realize however is that Rhye has done a lot with this. I'm not sure you need all of it though - it might be total overkill for your "minimalist" mod.

If you wanna start from scratch and do everything yourself - which is proper from almost any point-of-view - you need to break this down into segments. You can basically have functions for each job, so you do one function at the time.

But for now, the kill box. I should set out to define the areas I want to "kill". Should I do like we did in Rhye's by defining opposite corners and writing code to loop through all the tiles in the resulting box, or should I define lists of coords to produce my shapes. Also, can this simply be listed at the start of the existing spawn code? or will I have to have this happen one turn removed from the spawn. I don't want to kill the units I'm making instantly. I know that it follows the code in order, but does everything become effective at the end of the function or as it happens?
Ok, Rhye basically got the right idea with the coordinates defining rectangular shaped areas. This is my helper function from PyScenario for creating plot list, by the way, so you could add this helper function to your module:
Code:
def getPlotList(tTL, tBR):
        lPlotList = []
        for x in range(tTL[0], tBR[0]+1):
                for y in range(tTL[1], tBR[1]+1):
                        lPlotList.append((x, y))
        return lPlotList
Shall I explain the code or do you wanna look these things up yourself? (Because this would be the loops and the tuples you were looking to learn about.)

The tTL argument is the lower left corner of the area, while the tBR argument is the upper right corner. Both should be expressed as tuples, so this is a valid usage:
Code:
tTL = (34, 45)
tBR = (56, 67)
lPlotList = getPlotList(tTL, tBR)
Or just:
Code:
lPlotList = getPlotList((34, 45), (56, 67))
But you only need to use the getPlotList() function once for any single task (like spawning starting units). So the answer is, once again, data structures (just like Rhye's):
spawnCoordinates = {
eCeltia: [ (34, 45), (56, 67) ],
}
This is somewhat more advanced than what is used in RFC, but any setup - or any variant you can imagine - will be valid since you're doing the code that parses the data.

The easy-peasy solution would be to just add constants:
Code:
tCelticSpawnTR = (34, 45)
tCelticSpawnBR = (56, 67)
Or:
Code:
tCelticSpawn = ((34, 45), (56, 67))
But then you'd lose the potential for running the same function for all starting spawns, and you'd have to define one function per player. But any solution will of course work the same, unless you forget to change every single one when you edit one of them.

The benefit from going the extra mileage with dynamic setups is of course that when its time for you to change something, you only have to change one single function. So the effort isn't for nothing - you'll also learn a ton. Provided you can spare the time, effort and attention (away from getting faster results).

Next up we can worry about what to do with lPlotList... :p Just tell me once you're ready and I'll give you pointers for figuring out how to do the things you wanna get done.
 
not to be nitpicky, but can we make these tBL and tTR so they actually match what they are, or is there some hidden reason why these are always backwards?
 
not to be nitpicky, but can we make these tBL and tTR so they actually match what they are, or is there some hidden reason why these are always backwards?
Its your mod, your module. You're the programmer. You can name things any which way you like! :king:
 
One thing you could try at this juncture is to print out debug information into the PythonDbg.log. You do this with the print command. So if you're curious what the lPlotList variable holds, you can add a debug message:
Code:
print "debug: print lPlotList:", lPlotList
Then you lookup what is printed in the debug log. :)
 
alright, what is the difference between:

VOID kill ()
void () - kill the city

VOID raze (CyCity pCity)
void (CyCity pCity)

VOID killCities ()&#8232;void ()&#8232;
?

would I be using

VOID killUnits ()&#8232;void ()


and

VOID killCities ()&#8232;void ()


to eliminate any cities or units on any plots found in lPlotList?
 
Back
Top Bottom