• Our friends from AlphaCentauri2.info are in need of technical assistance. If you have experience with the LAMP stack and some hours to spare, please help them out and post here.

How to make a Python mod

I need to fire up a game and wait until stuff happens before I can answer that. Sorry. I'm on my way out the door as of writing this, and might be busy the rest of the evening also. But I'll check once I have an actual game log to check, and get back to you.
 
I believe so... let me check...

yep:

PY:City Built Event: Antium

this is in the PythonDbg log file...
 
I thought "just a look into ANY existing PythonDbglog, probably it contains ANY city built msg" (without starting the game == init file)
Haha, the funny thing is, that I had to leave too 15min after my last (desperate:lol:) post ...
So I had to wait 3 days 'til I found the answer:

Start game as Portugal, ctrl-shift-click 4 settlers, build cities: Bang, #3 is it.
PY:City Built Event: Guimarães
I was uncertain whether ShowPythonDebugMsgs toggles also this kind of messages. It does not.

Note: with ShowPythonDebugMsgs = 0
NO exception occurs, even the special character is printed correctly!!!

I like ShowPythonDebugMsgs = 1, because I NEVER look into the PythonErrlog files until I get a popup telling me an exception occured ...

So if you had ShowPythonDebugMsgs off all the time, how do you check for exceptions?
(I'm looking for more convenient ways all the time, eg. on purpose load my mod implicit, just load a savegame and click OK for loading mod)
 
This is mostly confusing to me, but here are what seems to be the relevant entries in my CivilizationIV.ini:
Spoiler :
; Establish connection to Python Debugger
HAPDebugger = 0

; NetComm Port
Port = 2056

; Create a dump file if the application crashes
GenerateCrashDumps = 0

; Enable the logging system
LoggingEnabled = 1

; Enable synchronization logging
SynchLog = 1

; Overwrite old network and message logs
OverwriteLogs = 1

; Enable rand event logging
RandLog = 1

; Enable message logging
MessageLog = 1

; Break on memory allocation order #
BreakOnAlloc = -1
 
I'll investigate further and report then.

Meanwhile I'm toying around the idea to implement the option "pay ransom money or see rebellion consequences" (Usually I have not enough money for anything, so probably also not for this - but maybe it changes my playing style :eek::lol:)
Example: "Trouble in Antium",
pay 50$ to avoid rebellion, before you see the possible outcome ... [first input]
...
and/or
...
{{ ?? pay 100$ after the rebels are deployed (and will be removed) ... [2nd input, if refused 1st] }}

Can this be realized with the popup element you have already introduced (another parameter)?
If not, what is the simplest way to INPUT just a YES/NO choice to the game? 'Simply' 2 buttons: YES & NO, instead of the OK ...
 
It is quite possible to add buttons and whatelse to pop-ups with Python. Look at the API!

Otherwise you could use the built-in random events interface and setup the rebellion events, the pop-ups and the ransoms with XML. Only the rebellion itself would be executed with Python. Look into this also!

Other than that, my own idea is to have a political oppositions system in CivIV where you always get to know what the current opposition to your rule is. This would depend on your current Civics, the era, religions makeup of empire, diplomatic circumstances, and so forth. The point is that once the opposition grows large enough you'd have to make secessions of one sort or another to avoid negative consequences. A "rebellion" is just one such consequence and I haven't even considered paying ransoms - which I suppose could work also - like bribing members of the opposition or investing in propaganda.

The way the pop-up events would work with my own setup is that the current opposition - be it political, ethnic or religious - or why not your own military! - makes a demand that you have to respond to. Like change this civic or grant independence that city or adapt some religion or another. You'd be able to reject the demand altogether, to make a compromise, to succumb to the demand - or to convert to the opposition altogether (take charge of it)! The latter would cause a period of anarchy and basically all civics and state religion would be determined by the attributes of the former opposition. Then some other fraction of society would become the new opposition to that rule. If that is also a strong one you could end up in a endless cycle of political turmoil...

So, for example, an industrial era despotic monarchy might have a socialist movement as its main opposition. From time to time - as the opposition gains support among the populace - they would raise demands for political reform. You might agree to adapt Representation to appease the masses, or you might agree to pay a large sum to improve the worker's conditions as a compromise, or you could choose to abandon the your medieval political system and adopt the socialist ideology. In the latter case you'd end up with new civics lineup and probably no state religion. By refusing the demand of the socialist opposition you'd risk a full-blown civil war, but even then you'd still have the chance to lead the rebel army rather than fight for the royalist cause. Defeat would mean game over in any case... And victory might spark a new Golden Age.
 
Just a quick question (thanks a lot for the very informative further thoughts ... I have to think about them much more to make up a serious reply):
I'm quite comfortable now with the CYcity, plot, unit, player ... objects - as they are defined in the API.
But the popup is a PY object isn't it? So it isn't described there in the API ... Anywhere else?
 
Look for the classes CyPopup, CyPopupInfo and CyPopupReturn in the API. I'm a novice on the subject though and have only used the PyPopup class of the Popup module myself, which wraps up the CyPopup class. (You can find it in the \Assets\Python\pyHelper folder of the Vanilla game.)
 
Thanks for showing this to me.
Anyways, as I'm getting more familiar with python, I have a question. What if we could make the "rebel" units independent, or even better, make them the nationality of the foreigners causing disorder? Basically like in RFC, when you take out a newly spawned civ and it spawns units for them to help them take back the city.

So I would change these values, but I don't know what to change them to...
from CvPythonExtensions import *
from PyHelpers import *
from Popup import PyPopup

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

iPropability = 5
eBarbarian = gc.getBARBARIAN_PLAYER()
pyBarbarian = PyPlayer(eBarbarian)

popupHeader = "Rebels mod"
popupMessage = "This mod includes the Rebels Python mod. It spawns barbarian units around cities \
with foreign citizens that are currently in disorder. The unit type and the number of units \
adapts to the makeup of the city garrison and the outcome will vary with the circumstances.\n\n\
You have been warned!"

def showPopup():
"""Displays the welcome message on game start"""
modPopup = PyPopup()
modPopup.setHeaderString(popupHeader)
modPopup.setBodyString(popupMessage)
modPopup.launch()

def checkTurn():
"""Checks all players and cities every turn, and executes the rebels event when applicable."""
lPlayers = pyGame.getCivPlayerList()
for pyPlayer in lPlayers:
lCities = pyPlayer.getCityList()
for pyCity in lCities:
cyCity = pyCity.GetCy()
if checkCity(cyCity):
lPlots = getPlotList(cyCity)
spawnRebels(cyCity, lPlots)

def checkCity(cyCity):
"""Checks if the CyCity instance is valid for the rebels event."""
bDisorder = cyCity.isDisorder()
if bDisorder == True:
iForeigners = cyCity.getCulturePercentAnger()
bNeverLost = cyCity.isNeverLost()
if iForeigners > 0 and bNeverLost == False:
iRandNum = cyGame.getSorenRandNum(100, "rebels")
if iRandNum < iPropability:
return True
return False

def getPlotList(cyCity):
"""Checks all adjacent plots and returns a list of CyPlot instances."""
lPlots = list()
iCityX = cyCity.getX()
iCityY = cyCity.getY()
lXCoordinates = range(iCityX - 1, iCityX + 2)
lYCoordinates = range(iCityY - 1, iCityY + 2)
for iX in lXCoordinates:
for iY in lYCoordinates:
cyPlot = cyMap.plot(iX, iY)
bWater = cyPlot.isWater()
bPeak = cyPlot.isPeak()
bCity = cyPlot.isCity()
if bWater == False and bPeak == False and bCity == False:
lPlots.append(cyPlot)
return lPlots

def spawnRebels(cyCity, lPlots):
"""Spawns rebel units matching the defenses of the CyCity instance on surrounding random plots."""
iNumPlots = len(lPlots)
if iNumPlots > 0:
iMilitaryUnits = cyCity.getMilitaryHappinessUnits()
iNumRebels = max(1, iMilitaryUnits / 4)
eUnitType = cyCity.getConscriptUnit()
while iNumRebels > 0:
iRandPlot = cyGame.getSorenRandNum(iNumPlots, "spawn")
cyPlot = lPlots[iRandPlot]
iX = cyPlot.getX()
iY = cyPlot.getY()
pyBarbarian.initUnit(eUnitType, iX, iY)
iNumRebels = iNumRebels - 1

EDIT*** Wouldn't I have to establish a check to make sure the two civs were still at war in order for the rebels to spawn?


as for finding the nationality, I have no idea what I could do.

oh, and by the way, where is the CvPythonExtensions? If its in the API, I can't find it.
this may be why I can't seem to locate the values above.
 
You can basically change the eBarbarian constant to any integer value. So if, say, you want the tenth player to be the "independent/rebel" Civ you change it to:
Code:
eBarbarian = 9
The rest of the code will adapt dynamically, because the pyBarbarian constant is based on the first one.

The default line looks like this:
Code:
eBarbarian = gc.getBARBARIAN_PLAYER()
The gc part is explained by this line:
Code:
gc = CyGlobalContext()
So, if you look at the API section for the CyGlobalContext class you can find this entry (#10):
INT getBARBARIAN_PLAYER ()
The INT bit means that the method returns a integer value. And this is why you can simply substitute the method invocation with a integer value - any integer value. The upside of using the method is however that the barbarian player will vary with context and the circumstances. But in a scenario type mod (like RFC) it really doesn't add anything.

CvPythonExtensions is part of the SDK - but there is no one single place to look at it.

Lets just start with that and take it further once you've established a bridgehead - a working variant of the Rebels mod-comp.
 
Ok, I think I'm starting to understand this. (Correct me if I'm wrong)

eBarbarian = gc.getBARBARIAN_PLAYER() this tells the game what eBarbarian is
pyBarbarian = PyPlayer(eBarbarian) this is what tells the game what pyBarbarian is

So if I changed it to

eBarbarian = 9
pyBarbarian = PyPlayer(eBarbarian)

it would directly get the team/player 9 and understand what civ the rebels belong to?
 
Yeah, something like that. You're basically assigning values to names, very basic Python programming procedures.

There is a hard way and a easy way to learn programming. You can spend months "figuring out" how to do programming, or you can invest a few days on reading up on it and learning all the basics. Your choice.
 
Yeah, something like that. You're basically assigning values to names, very basic Python programming procedures.

There is a hard way and a easy way to learn programming. You can spend months "figuring out" how to do programming, or you can invest a few days on reading up on it and learning all the basics. Your choice.
I'm reading that textbook that you suggested.
 
Good for you! :goodjob: You'll be up and running with these particular pairs of scissors in no time at all! :D

So, what we've discussed is basically how variable assignment can be implemented with CivIV modding. The actual variable names are of course arbitrary, and you could as well change the names if you like. As long as the internal logic of the program is intact you get away with any (valid) names.

Tell me once you're ready to move on to the next phase of your program and I'll supply you with the API tools you need. But you need to be able to write your own logic - assignment and conditional statements, basically. Also be able to handle indentation issues and use blocks of code. So please do keep up with the studying.
 
Ok, so I've developed a "code". It's designed to spawn a city.

from PyHelpers import *

eBarbarian = 0
pyBarbarian = PyPlayer(eBarbarian)

pyBarbarian.initCity(67, 35) (Not sure about that part...)

Would that work if I put it into the in-game python console?
and can I change the "eBarbarian" to something else? (I think so, just making sure;))

edit*** I'm not to far into the text book, so I don't know much about indention if the factor comes into play
 
Ok, so I've developed a "code". It's designed to spawn a city.
That is called a method invocation. The difference between a function and a method is that a method belongs to a class - and is invoked on an instance of that class. It will all make perfect sense once you hit the Object -Oriented Programming chapter(s).

Would that work if I put it into the in-game python console?
You actually need a CyPlayer instance for the initCity() method instead of a PyPlayer instance. But you can get one from PyPlayer with getPlayer().
Code:
pPlayer = pyBarbarian.getPlayer()
PyHelpers is a bit difficult to grasp but its worth the extra effort because, well, its called PyHelpers. You don't need it though.

Regarding the console - you add the lines one by one. Note that CvPythonExtensions is available by default, so you don't need to import that.

and can I change the "eBarbarian" to something else? (I think so, just making sure;))
Yeah, I already explained that. :D

edit*** I'm not to far into the text book, so I don't know much about indention if the factor comes into play
Indentation is not a factor in the built-in Python console, because it doesn't handle blocks of code.
 
Ok, correctly me if I'm wrong (again).

I would need to create a CyPlayer instance for the initCity to work.

from CvPythonExtensions import *
from PyHelpers import *
iPlayer = 0

iPlayer.initCity(67, 35)
 
Simply giving you the solution doesn't make you understand much, but this is one way of doing it:
Code:
eBarbarian = 9
pBarbarian = gc.getPlayer(eBarbarian)
pBarbarian.initCity(23, 34)
Or just:
Code:
gc.getPlayer(9).initCity(23, 34)
But since the existing code already had a PyPlayer instance for the Barbarian player I suggested this approach:
Code:
eBarbarian = 9
pyBarbarian = PyPlayer(eBarbarian)
pBarbarian = pyBarbarian.getPlayer()
pPlayer.initCity(23, 34)
I suggest you read up on what "players" are and how a player ID/PlayerType is different from a CyPlayer object. Holler if you can't figure it out and I'll explain.
 
Simply giving you the solution doesn't make you understand much, but this is one way of doing it:
Code:
eBarbarian = 9
pBarbarian = gc.getPlayer(eBarbarian)
pBarbarian.initCity(23, 34)
Or just:
Code:
gc.getPlayer(9).initCity(23, 34)
But since the existing code already had a PyPlayer instance for the Barbarian player I suggested this approach:
Code:
eBarbarian = 9
pyBarbarian = PyPlayer(eBarbarian)
pBarbarian = pyBarbarian.getPlayer()
pPlayer.initCity(23, 34)
I suggest you read up on what "players" are and how a player ID/PlayerType is different from a CyPlayer object. Holler if you can't figure it out and I'll explain.
Ok, in this case, giving me the solution was the best way for me to get it, because it makes since now.

As for reading up on what "players" are, where could I find this in the text book.

This is what I'm interpreting the player ID and the CyPlayer object as right now.

pPlayer
Identifies what player the object belongs to

initCity(23, 34)
spawns a city on those coordinates.

If I wanted to go the CyPlayer route, would it look something like this

from CvPythonExtensions import *

# constants
gc = CyGlobalContext()
(would I need to set a CyPlayer constannt?)

eBarbarian = 9
pBarbarian = gc.getPlayer(eBarbarian)
pBarbarian.initCity(23, 34)

also, how would I go about making the code happen on a certain turn, I can't seem to find anything in the API.
 
As for reading up on what "players" are, where could I find this in the text book.
I was hoping I covered this in my own tutorial. The textbook of course doesn't cover anything CivIV specific.

This is what I'm interpreting the player ID and the CyPlayer object as right now.
Every player (Civilization) is an instance of the CyPlayer class, at least as far as Python i concerned. An instance is an "object" - basically a data type that can hold many things, like all the attributes of a player/Civ.

These objects are enumerated by the game, from zero and up. These integer index numbers are the player IDs - also known as the PlayerType. So the first player is identified by the number 0 (zero), and in our examples the Barbarian player is the the tenth player, thus indexed as 9.

also, how would I go about making the code happen on a certain turn, I can't seem to find anything in the API.
This is a matter of hooking up to a "game event" callback in the CvEventManager module. Once you understand the whole tutorial you should be able to figure out how to get it done. (There are callbacks like beginGameTurn, endGameTurn, beginPlayerTurn and the like.)
 
Back
Top Bottom