Basic questions

Chaloup

Chieftain
Joined
Aug 25, 2011
Messages
9
Hello All
Another noob questions thread, sorry...

I started looking at Python few weeks ago (thanks Baldyr for the help and links you provided, very usefull).
But I'm still quite confused... Let's check.

For the context, I'm planning a mod that recreates the post war events (revolutions, coup d'etat, guerilla).

First function: to spawn a warrior somewhere
Code:
def guerilla (self, player, iX, iY)
  unitType = gc.getInfoTypeForString("UNIT_WARRIOR")
  player.initUnit(unitType, iX, iY, UnitAITypes.NO_UNITAI)
If I understand well, if I launch in an event a
self.guerilla(5, 22, 13)
it would spawn a unit at x22 y13 as a warrior belonging to the civilization 5 as defined in the WBS, right?

Second function, I would need a bit of help...
Code:
def revCoup (self, player, iX, iY)
  pCity = CyMap().plot(iX,iY).getPlotCity()
  oplayer = CyCity.getOwner()

 # Getting the plots around
  lPlots = list()
  lXCoord = range(iX - 2, iX + 5)
  lYCoord = range(iX - 2, iY + 5)

 # Getting the list of units on the plots
  lUnit = oplayer.getUnitList()    
  for cyUnit in lUnit:
    if cyUnit at (lXCoord, lYCoord):
      randNum = cyGame.getSorenRandNum(100, "revol")
      if randNum < 39:
        # convert (CyUnit pUnit)
        # unitType = getUnitType()
        # exp = getExperience()
        # kill (BOOL bDelay, PlayerType ePlayer)
      else continue

 # Getting the city to change owner
  player.acquireCity(pCity,false,false)

In this case, I would launch a
self.revCoup(2, 22, 13)
for having the player 2 acquirring the city placed at x22 y13, is that right?

I'm using the plots around (or trying to use maybe) to get all the units of the player loosing the city placed in these plots.
And then, it would have a 40% chance to join the civilization getting the city, but for this, I don't know how to use convert... I tried just spawning a new unit a copying the experience, but then I don't know how to kill the first one...

I think I'm confused with the cy py instances, what you can or have to define, what you have to get...
If it's easier, I don't care defining all cities/civs, as the scenario would be closed with spec civs and cannot build cities.

Thanks for your help!!!
 
Ouch, you seem to be in deep water here. You might wanna take one step back and hit the books, or whatever you're using as a reference. Some issues, without writing the whole thing for you:

The self argument is a reference to a class instance. Do you even have a class for these functions? In that case why?

If the functions belong to a class, then the indentation is off.

Any time a line of code is followed by a block of code, then that line should end with a colon.

In order to invoke a method from the Python API you need an instance of a class. In the guerilla() method you are invoking a integer value on the CyPlayer.initUnit() method. What wont work - you need a CyPlayer instance.

And the second method is a mess. I'd have to go through it line by line, so you clearly need some more understanding on the language. Did you try How to Think Like a Computer Scientist?
 
Well, it would be more exact to say that I'm in the abysses actually...

Basically, what I'm doing is looking scripts in other mods, and adapting what I think I understand...
(that's what I have done with the self. stuff, if it's working for others, why not for me :D)

Let me work again on this with those lights.
(yes, I did read the How to think.. ebook, but I fell in the gap between the python theory and the civ api application...)
 
OK, so now it should be cleaner, at least with good instances.

For the spawning:
Code:
def guerilla (player, iX, iY):
  insPlayer = PyPlayer(player)
  unitType = gc.getInfoTypeForString("UNIT_WARRIOR")
  insPlayer.initUnit(unitType, iX, iY, UnitAITypes.NO_UNITAI)
Still intending to spawn a warrior for player 1 in x2 y3 if launched as : guerrilla (1,2,3)

And for the second function:
It intends to create a list of plots instances around the city, to get the units instances of the player losing the city, to convert a part of them to the player gaining the city and finally changing the city ownership.
Code:
def revCoup (player, iCityX, iCityY):
  insCity = CyMap().plot(iCityX, iCityY).getPlotCity()
  oplayer = CyCity.getOwner()
  insOplayer = PyPlayer(oplayer)
  insPlayer = PyPlayer(player)

  # Getting the plots around the city
  lPlots = list()
  lXCoordinates = range(iCityX - 2, iCityX + 5)
  lYCoordinates = range(iCityY - 2, iCityY + 5)
  for iX in lXCoordinates:
    for iY in lYCoordinates:
      insPlot = cyMap.plot(iX, iY)
      lPlots.append(insPlot)

  # Getting the list of units on the plots
  lUnit = insOplayer.getUnitList()
  lUnitConv = list ()   
  for cyUnit in lUnit:
    unitPLot = getPathEndTurnPlot()
    if unitPlot in lPlots
      unitConv = PyPlayer.CyGet()
      lUnitConv.append(unitConv)
    else continue

  # Converting the units
  for cyUnit in lUnitConv:
  randNum = cyGame.getSorenRandNum(100, "revol")
  if randNum < 29:
    convUnit = insPlayer.initUnit(cyUnit.getUnitType(), cyUnit.getX(), cyUnit.getY(), UnitAITypes.NO_UNITAI)
    convUnit.convert(cyUnit)
  else continue

  # Getting the city to change owner
  insPlayer.acquireCity(insCity,false,false)

In the mean time, all the API/py/cy stuff become clearer when you take care of instances... :goodjob:
 
Using PyPlayer(player) gets you a PyPlayer object rather than a CyPlayer object, assuming you have included the PyHelpers module.

There is nothing wrong with this, although it is a more complex object. You do, however, probably need to know that the PyPlayer.initUnit method does not have the same arguments as the CyPlayer method of the same name. In particular, it has no unit AI argument. Instead, it has an optional argument for a number of units to create, which defaults to 1 if you leave that argument off the call. Specifying UnitAITypes.NO_UNITAI for this should get you 1 unit created since that value is actually -1 and the function in PyPlayer checks this argument and creates 1 unit if it is not greater than 1. (UNITAI_UNKNOWN and UNITAI_ANIMAL would also get you 1, any other unit AI type value would get you more, with the most coming from UNITAI_ATTACK_CITY_LEMMING which would get you 40 units.) Since it will appear to work as you intended it may not seem like it matters, but the fact that it works is just a coincidence so it should probably be fixed (either by specifying a number or by leaving it out).
 
I tried to learn Python modding the same way, and learned about 10% of the actual Python I learned once I read the entry-level textbook... In 10 times the time. So it would have been 100 times as effective to do the homework...

Did you see this?
 
First question: Are you asking for general advice, general supervision, or is something specific not working?
Second question: Did you activate the python exceptions in your main .ini file?
Third question: Do you know about the Civ4 python API?

From looking at the code, the line
PHP:
insPlot = cyMap.plot(iX, iY)

is wrong. If you have not defined cyMap somewhere where we can't see it, you would need CyMap() instead (uppercase/lowercase matters, as well as the brackets).
 
Unfortunately, there is a lot more to Python scripting than to copy-paste stuff from mods. Since there is no one way to do just about anything, "merging" code only works if you're able to actually read and understand all or most of it yourself. Otherwise you're doomed to fail again and again, and could have used the time and effort to actually reading up on the language. Then the same task would be easy or even trivial.
 
Thank you All!

God EMperor,
Got it, only a coincidence... So insPlayer.initUnit(unitType, iX, iY, 1) would be better, right?

The J,
Ok, so that's the use of defining cyMap = CyMap()... It was included but I had no idea about the use :)
I was asking for global checking if my writing is decent. Sometimes, the API is not so explicit (like CyUnit.convert, I had to look at other mods to see that you have to use a second unit to be "converted").
The python exceptions are activated, but it returns me strange stuff, like unreadable or even empty boxes.
I think it should update the ThemeParseLog.txt file, but the errors are not from me.
Spoiler :
Error : Decl - ('Civ4Theme_Common.thm', Ln:401, Col:13) Assignment source propertyId 'SF_CtrlTheme_Civ4_Control_Font_Size3_Italic' not found
Error : Decl - ('Civ4Theme_Common.thm', Ln:402, Col:13) Assignment source propertyId 'SF_CtrlTheme_Civ4_Control_Font_Size3_BoldItalic' not found
Error : Decl - ('Civ4Theme_Common.thm', Ln:408, Col:13) Assignment source propertyId 'SF_CtrlTheme_Civ4_Control_Font_Size1_Bold' not found
Error : Decl - ('Civ4Theme_Window.thm', Ln:3048, Col:9) Assignment source propertyId 'SF_CtrlTheme_Civ4_Control_Font_Size1_Bold' not found
Error : Syntax - ('Civ4Theme_HUD.thm', Ln:887, Col:13) Unexpected '.' in the identifier assignment statement
Error : Syntax - ('Civ4Theme_HUD.thm', Ln:927, Col:13) Unexpected '.' in the identifier assignment statement

These two functions will be the 70% of python for my mod, so I wanted to know if they are correct before getting to the events.

Baldyr,
Of course I know your tutorial (I thanked you in my first message, it's the most comprehensive tutorial I found)!
The revCoup function is shamelessly based on the Rebels mods (for doing the lists). So I don't just do copy/paste praying for it working, but I feel like there is a huge gap between the basic python and its civ4 application...
 
Baldyr,
Of course I know your tutorial (I thanked you in my first message, it's the most comprehensive tutorial I found)!
The revCoup function is shamelessly based on the Rebels mods (for doing the lists). So I don't just do copy/paste praying for it working, but I feel like there is a huge gap between the basic python and its civ4 application...
You're simply wrong on this, as simple as that. If you wanna be able to do this, you should pick up a real textbook before even attempting to read my own tutorial. About 90% of what you learn in any given entry-level Python textbook should be applicable to Python modding. (Stuff like keyboard input and saving/loading on file clearly isn't, so you can just skip those chapters.) You need to know stuff like Object-Oriented Programming (that is classes and methods) if you even wanna understand Python modding!

Right now you're copy-pasting, and that is not programming. My opinion. Your loss.
 
Arrogance makes you look bad, Baldyr.

God EMperor,
Got it, only a coincidence... So insPlayer.initUnit(unitType, iX, iY, 1) would be better, right?

:yup:

In general you should, as beginner/as long as you don't know to 100% what you're doing, use rather the Cy***** classes from the API. Easier to look up.

I was asking for global checking if my writing is decent.

:D no idea if the code is decent, but looks in general right.

Sometimes, the API is not so explicit (like CyUnit.convert, I had to look at other mods to see that you have to use a second unit to be "converted").

That's still the case for me too, so don't worry ;).

The python exceptions are activated, but it returns me strange stuff, like unreadable or even empty boxes.

That's normal. Just hit enter until you get the boxes with error messages, there's no way around that.

I think it should update the ThemeParseLog.txt file, but the errors are not from me.
Spoiler :
Error : Decl - ('Civ4Theme_Common.thm', Ln:401, Col:13) Assignment source propertyId 'SF_CtrlTheme_Civ4_Control_Font_Size3_Italic' not found
Error : Decl - ('Civ4Theme_Common.thm', Ln:402, Col:13) Assignment source propertyId 'SF_CtrlTheme_Civ4_Control_Font_Size3_BoldItalic' not found
Error : Decl - ('Civ4Theme_Common.thm', Ln:408, Col:13) Assignment source propertyId 'SF_CtrlTheme_Civ4_Control_Font_Size1_Bold' not found
Error : Decl - ('Civ4Theme_Window.thm', Ln:3048, Col:9) Assignment source propertyId 'SF_CtrlTheme_Civ4_Control_Font_Size1_Bold' not found
Error : Syntax - ('Civ4Theme_HUD.thm', Ln:887, Col:13) Unexpected '.' in the identifier assignment statement
Error : Syntax - ('Civ4Theme_HUD.thm', Ln:927, Col:13) Unexpected '.' in the identifier assignment statement

These are "default errors", ignore them. They also only come from the HUD, so everything in the resources\theme subfolder, has nothing to do with python.
If a python error comes up, it will be in the respective python error logs and will also jump into your face ingame, so there's no chance to miss it.
 
In the spirit of being helpful: Any time you have a new block of code - a new indentation level - the previous line needs to end with a colon. Else its an instant syntax error. All if/elif/else statements also needs to end with a colon, even if you put the following block of code on the same line (which is legal in Python). So it would be:
Code:
else[COLOR="Red"]:[/COLOR] continue
Which really should be:
Code:
else:
    continue
But it works just the same.

This also means that any line following a line ending with a colon has to be indented. Because anything following such a line is in fact a block of code belonging to that line.

Talking about basics of Python programming... The sort of thing you would learn from a entry-level textbook. I guess I need to write a general Python tuturial also, disguised as a modding tutorial. :rolleyes:
 
That's normal. Just hit enter until you get the boxes with error messages, there's no way around that.

Sometimes the in-game Python exception pop-ups show the whole thing at once, other times (much more often than not, I think) it does one pop-up per line, or even character sometimes, of the error message. No idea what the difference is.

But the entire error message will appear in the PythonErr.log file in an easier to read format, like on those rare occasions when the whole thing is on one pop-up.
 
In my experience, the in-game exception pop-up only works flawlessly the first time around. The second exception is always messed up, split into multiple pop-ups. Its probably an issue with parsing the error log, or something. Perhaps it never was designed to work repeatedly? So you're supposed to restart the game any time you encounter a error. Or the programmers never bothered to check if the pop-up fires properly more than once. :p
 
But the entire error message will appear in the PythonErr.log file in an easier to read format, like on those rare occasions when the whole thing is on one pop-up.

Well, my log file is not that clear! It is basically one line per pop-up, with the empty pop-ups replaced by something like "Unable to load CvEventManager"! :lol:
I can't show you as I have cleaned all the errors at loading, so the file is empty now. I will test in-game this week-end when I will have more time.

By the way, I finished my big script: a global war launched when achieving a world wonder.
But I have 2 questions on this.
- the pCity instance we get at onBuildingBuilt, is it still valid when calling another function in another module? (I think so as the variable should be destroyed at the end of the onBuildingBuilt function, which is ended after the function called is itself ended, right?)
- (Ok, 3 questions so) is it the same for callback functions? I use a pop-up to give a variable a value. Is this value then accessible by the "mother" function?
- what kind of instance do we get with gc.getInfoTypeForString(cyVictim.getWorstEnemyName())? Is that a player? A leaderhead?
In the API, getWorstEnemyName returns a name. Which one?

Here is the code if you are interested:
Spoiler :
Code:
def globalwar():
  cyVictim = pCity.getOwner()
 # if 911th is human-built, display a popup to choose the terrorist
  if cyVictim.isHuman() :
    popupInfo = CyPopupInfo()
    popupInfo.setButtonPopupType(ButtonPopupTypes.BUTTONPOPUP_PYTHON)
    popupInfo.setText("You can choose who will be considered a rogue state. A Global War on Terrorism will be launched against this civilization.")  
    popupInfo.setData1(iPlayer)
    popupInfo.setPythonModule("PWScreensInterface")
    popupInfo.setOnClickedPythonCallback("rogueState")
    popupInfo.addPythonButton("America")
    popupInfo.addPythonButton("Petro-Sheikdoms")
    popupInfo.addPythonButton("Bolivarian AMerica")
    popupInfo.addPythonButton("China")
    popupInfo.addPythonButton("Australia")
    popupInfo.addPythonButton("Non-aligned Africa")
    popupInfo.addPythonButton("European Federal Republics")
    popupInfo.addPythonButton("European Democratic Republics")
    popupInfo.addPythonButton("Amercian Militar Juntas")
    popupInfo.addPythonButton("India")
    popupInfo.addPythonButton("Japan")
    popupInfo.addPythonButton("Stabilized Asia")
    popupInfo.addPythonButton("Non-aligned Asia")
    popupInfo.addPythonButton("African Puppet Republics")
    popupInfo.addPythonButton("Lay Middle-East")
    popupInfo.addPythonButton("Islamic Republics")
    popupInfo.addPythonButton("Russia")
    popupInfo.addPopup(cyVictim)   
  else:
    rogue = gc.getInfoTypeForString(cyVictim.getWorstEnemyName()) #give what?
    cyRogue = gc.getPlayer(rogue)
  lPlayers = pyGame.getCivPlayerList()
 # Two lists for the Axis of Good and the rogue states
  lPlayWar = list()
  lPlayNowar = list()
  for pyPlayer in lPlayers:
    cplayer = gc.getPlayer(pyPlayer.getID())
    if cplayer == cyRogue:
      lPlayNowar.append(cplayer)
    elif cplayer == cyVictim:
      lPlayWar.append(cplayer)
 # the human can choose to participate or not
    elif cplayer.isHuman:
      popupInfo = CyPopupInfo()
      popupInfo.setButtonPopupType(ButtonPopupTypes.BUTTONPOPUP_PYTHON)
      popupInfo.setText(cyRogue.getCivilizationDescriptionKey + " is now a rogue state. Do you want to participate to the Global War on Terrorism?")
      popupInfo.setData1(cPlayer)
      popupInfo.setPythonModule("PWScreensInterface")
      popupInfo.setOnClickedPythonCallback("accrefwar")
      popupInfo.addPythonButton("Yes")
      popupInfo.addPythonButton("No")
      popupInfo.addPopup(cplayer)
 # for everyone, DOW and classification
    else:
        if cplayer.getTeam().isAtWar(cyRogue.getTeam()):
          lPlayWar.append(cplayer)
        elif cplayer.getTeam().isDefensivePact(cyRogue.getTeam()):
          lPlayNowar.append(cplayer)
        else :
          attitude = cplayer.AI_getAttitude (cyRogue)
          if attitude < 3 :
            lPlayWar.append(cplayer)
          else :
            lPlayNowar.append(cplayer)
    for cplayer in lPlayWar:
      if cplayer.getTeam().isAtWar(cyRogue.getTeam()):
        continue
      else :
        cplayer.getTeam().declareWar(cyRogue.getTeam(), false, WarPlanTypes.WARPLAN_TOTAL)
      for lRogue in lPlayNowar:
        cplayer.AI_changeAttitudeExtra(lRogue, (cplayer.AI_getAttitudeExtra(lRogue)-5))

So the pop-up either assign a value to cyRogue, or classify the human player in one of the two lists.


It is certainly not very clean, but it would make sense...
 
pCity isn't even defined within your function. And you'd have to understand function calls in order to understand the rest. But I'm sure there will be valiant efforts to explain this for you.

This method invocation:
gc.getInfoTypeForString(cyVictim.getWorstEnemyName())
...will only return -1 (None). You'd have to use:
Code:
name = cyVictim.getWorstEnemyName().replace(" ", "_")
gc.getInfoTypeForString("CIVILIZATION_" + name.upper())
And even then it would be dependent on the exact spelling of the Civilization name to work.

But the problem is that this still only gets you the CivilizationType, not the PlayerType. Because the Civilization is only an attribute of a player - several players might share a Civilization attribute. There is a work-around however, but this is probably the solution you're looking for.

I looked into this in the SDK and it turns out that "worst enemy" is a value used within the game. The method for fetching it (CvTeamAI::AI_getWorstEnemy()) that isn't exposed to Python. This means that the corresponding CyTeam method isn't even available in the Python API. :p

This pretty much means that you need a slightly altered, custom DLL file in your mod. The mentioned method would be exposed to Python, and you'd be able to do what you want.

On a related note, "worst enemy" probably just means "greatest rival". If you wanna check attitudes there is a CyPlayer.AI_getAttitude() method.
 
pCity isn't even defined within your function.
That is my first question. pCity is defined in onBuildingBuilt
Code:
	def onBuildingBuilt(self, argsList):
		'Building Completed'
		pCity, iBuildingType = argsList
		game = gc.getGame()
                          blabla...
and then
Code:
		if (iBuildingType == gc.getInfoTypeForString("BUILDING_NINEELEVENTH")):
			PostWar.globalwar()
So if I understand well my entry-level book, the onBuildingBuilt function is not over until my globalwar is finished. So I assumed the pCity instance is still valid when my function globalwar() begin to execute, as it is defined in the "outer" function.

But then I'm wondering about what happens in case of call back (and I didn't find any answer in "How to think like..."), that I use with the pop-ups for human players.
Code:
    popupInfo.setOnClickedPythonCallback("rogueState")
Code:
  if iButtonId == 0:
    cyRogue = gc.getPlayer(0)
  elif iButtonId == 1:
    cyRogue = gc.getPlayer(1)
  etc....
Is the value of cyRogue as defined in rogueState() still valid when the flow of execution return to the globalwar() function?

This pretty much means that you need a slightly altered, custom DLL file in your mod. The mentioned method would be exposed to Python, and you'd be able to do what you want.
On a related note, "worst enemy" probably just means "greatest rival". If you wanna check attitudes there is a CyPlayer.AI_getAttitude() method.

Oh no, I can't decently start bothering people for SDK while I haven't finished bothering you for python! ;)
Using relative attitudes is a great idea, I will use this. :goodjob:
 
If you read the whole textbook, instead of trying to look-up answers to what you think you don't know, you'd already understand function calls. ;)

You have to explicitly pass any values in function calls for them to be available to them. Any names defined within functions are only available within the function itself (they're local) - its not available in any other function. Unless it has explicitly been defined as a global name (or defined at the module level), but this is not what you want, and that would still only be true on the module level.

This is so elementary that someone will have to give you practical examples. Or you could just read up on Python yourself.
 
Ok, thank you for your patience... :blush:
I understand quickly but I need to be told several times!:lol:

I think that will do the trick:
Code:
		if (iBuildingType == gc.getInfoTypeForString("BUILDING_NINEELEVENTH")):
			PostWar.globalwar(pCity)
and then:

def globalwar(pCity):
  cyVictim = pCity.getOwner()

And for the callback, I assume that just a return statement will be enough. If that don't work, I create a list just to store 1 value! :lol:
 
I think that will do the trick:
Yeah, and the name doesn't even have to be pCity inside globalwar(). Because it will be a local variable you can call it whatever you like. (I never hesitate changing names for values passed on from other people's modules, like CvEventManager. You seem to have your own naming conventions, so you could totally call the CyCity instantance cyCity or insCity or objCity or whatever you like.)
And for the callback, I assume that just a return statement will be enough. If that don't work, I create a list just to store 1 value! :lol:
I don't know what "callback" your looking for, but the code calling the function may be expecting the return value to be some of type, so your function should return that type, and no other type. Interchanging a single value (of some type) and replacing it with an array (list type in this case) will brake your code, unless you make the code accept the array and index the first data entry.

And by the way; just because its called "argsList" doesn't necessarily make it a list type. I actually believe that those arrays passed on from the DLL to Python are tuple type, which is basically a non-mutable variant of the list type. (Thus it can be used as a key in a dictionary, for instance.)
 
Back
Top Bottom