Unique Victory Conditions

topsecret

Believer
Supporter
Joined
Feb 11, 2010
Messages
6,008
Location
At the Foot of the Cross
Hey, I finally decided to try to write a little python!
I want to make Unique Victory Conditions for each Civilization.
How do I start?
 
How much do you know about Python and Python modding?

The first thing to realize is that you can fire scripts at very specific game events, and only then. These are listed at the beginning of the CvEventManager.py file. So you could basically check if the conditions are met any time some events occurs, like when a player builds its 30th city, or something (if that was a condition). Then you'd need to check if the player has at least 30 cities - and fire the actual code that makes that player win.

But it would also be possible to check the conditions for victory at some set game turn(s). Then its either the beginGameTurn event or the beginPlayerturn event you need to hook up to. The principle is to firstly check the game turn/date, then check the actual victory condition, and only then fire the victory code.

If you want more specific help you might wanna draft a list of the various victory conditions, so that we have some idea what you want/need. But different conditions will probably require vastly different approaches, so there will most likely be a lot to take in.
 
How much do you know about Python and Python modding?

A little, I know about triggers, varibles etc.

The first thing to realize is that you can fire scripts at very specific game events, and only then. These are listed at the beginning of the CvEventManager.py file. So you could basically check if the conditions are met any time some events occurs, like when a player builds its 30th city, or something (if that was a condition). Then you'd need to check if the player has at least 30 cities - and fire the actual code that makes that player win.

I knew about those, thanks to merging :D

But it would also be possible to check the conditions for victory at some set game turn(s). Then its either the beginGameTurn event or the beginPlayerturn event you need to hook up to. The principle is to firstly check the game turn/date, then check the actual victory condition, and only then fire the victory code.

Mainly, i want to know what the code is to make the player win.

If you want more specific help you might wanna draft a list of the various victory conditions, so that we have some idea what you want/need. But different conditions will probably require vastly different approaches, so there will most likely be a lot to take in.

That could take a long time, but I think I want this to be my python mod, (with help, of course) so I learn python; So, I need some help with code explanation... Yippie! :rolleyes: :eek:
 
Do you want a completed sample victory condition, then? (The code for it.) Because it would be about 20 more time efficient to just make it than to try and explain everything, one step at a time... In that case, reveal one of the conditions you have in mind. But know that it won't simply be a matter of copy-pasting the same code over and over again to do all conditions. You need to learn programming for that.

Regarding the actual victory, I believe that the method CyGame.setWinner() is part of the solution. But it takes a VictoryType as a parameter, so I guess you need to make one of those (something like "Unique Victory" or something, probably defined in the XML, or something).

If nothing above made any sense to you, then you might wanna look into my own Python modding tutorial. It explains many of the key things involved. (Well, not victory conditions but anyways.)
 
I know I can't just copy & paste, that's why I'm learning ;)
I want a sample one, yes.
Anyway, One of the victory conditions for Rome is to defeat these 3 civs (or be left standing after they are defeated): Carthage, Greece, and Persia.
I guess I need to check if they are alive with "if pPersia is alive" or something like that ?
 
First step: Think about what victory conditions you want to have ;).


@above: There is an event in the CvEventManager, onCityAcquired, which should be the starting point here. A civ is destroyed when the last city is captured. So you can check after this event if the owner of the city was one of these civs, then check, if this civ is still alive, and then check, if the others are still alive.
But a small problem: What if another civ destroyes one of these 3 civs?
 
Well, he does state that Rome would be given the victory if left standing after the others fall.
 
Since we're looking at four different Civs here; Rome, Carthage, Greece and Persia, it would be convenient to know their respective PlayerType values, their IDs (index numbers). But, since this is a mod and not a scenario (one single WBS) - there could potentially be several players of any of these CivilizationTypes? In that case you need to define the condition so that all PlayerTypes with these CivilizationTypes have to be dead. But then we're stuck with potentially several Roman winners, right? Which player wins if there are two or more Rome?

Anyway, making all these conditions calls for you to define all the players/civilization with constants. This is not only convenient but will also speed things up, since you're not looking up these values multiple times every given game turn. But firstly we need to establish if this is for a scenario or for a mod.
 
Since we're looking at four different Civs here; Rome, Carthage, Greece and Persia, it would be convenient to know their respective PlayerType values, their IDs (index numbers). But, since this is a mod and not a scenario (one single WBS) - there could potentially be several players of any of these CivilizationTypes? In that case you need to define the condition so that all PlayerTypes with these CivilizationTypes have to be dead. But then we're stuck with potentially several Roman winners, right? Which player wins if there are two or more Rome?

Anyway, making all these conditions calls for you to define all the players/civilization with constants. This is not only convenient but will also speed things up, since you're not looking up these values multiple times every given game turn. But firstly we need to establish if this is for a scenario or for a mod.

This is for my mod which plays on the same map every time, so there is only 1 of each civilization.
So I can do player index, like in Rhye's code, which I looked at.
 
First step: Think about what victory conditions you want to have ;).


@above: There is an event in the CvEventManager, onCityAcquired, which should be the starting point here. A civ is destroyed when the last city is captured. So you can check after this event if the owner of the city was one of these civs, then check, if this civ is still alive, and then check, if the others are still alive.
But a small problem: What if another civ destroyes one of these 3 civs?

Well, he does state that Rome would be given the victory if left standing after the others fall.

Yes, any civ can kill them, Rome just has to survive!
 
Ok, you have some options to consider. Do you wanna build the Python in your scenario around the BUG infrastructure? You probably wanna do this, but note that modding with BUG is not like regular modding. And you should decide this before any actual code is written.

Also, I made a module called CivPlayer for Jamie's Rome mod. It handles all manner of player/team values but requires some effort to get into (like anything). I could get you started though and that would probably save you a lot of time and code (as a large part of Python modding revolves around fetching player/team values) in the long run. Because it gives you an easy-access interface without having to define any constants, just import the module and everything is already setup and ready to go. Automatically.

What do you think? Note that CivPlayers isn't even tested with BUG, so you can choose either option, or none - but probably not both.

Your suggestion about a global player index is of course a valid one, but note that then you also end up either defining/fetching all the associated values. :p But I guess it could be a healthy thing to do for your first outing? :dunno:
 
Ok, this is how a "regular" way of doing the Roman condition could look like:
Code:
eRome = 0
eCarthage = 1
eGreece = 2
ePersia = 3

tRomeOutliveConditionPlayers = (eCarthage, eGreece, ePersia)

def onSetPlayerAlive(ePlayer, bDead):
	if bDead:
		pRome = gc.getPlayer(eRome)
		if pRome.isAlive()
			if ePlayer in tRomeOutliveConditionPlayers:
				for eRival in tRomeOutLiveConditionPlayers:
					if gc.getPlayer(eRival).isAlive():
						return
				CyGame().setWinner(pRome.getTeam(), eUniqueVictory)
This code would be located in a file like UniqueVictories.py and there would be a couple of import lines and a function call statement in the CvEventManager.py file hooking it up with the rest of the game.

This is what the equivalent code would look without the player index definitions - and without ever using any getPlayer() or getTeam() method - with the aid of CivPlayer (which I mentioned above):
Code:
from CivPlayer import *

tRomeOutliveConditionPlayers = ("Carthage", "Greece", "Persia")

def onSetPlayerAlive(ePlayer, bDead):
	if bDead:
		if Civ("Rome").isAlive():
			if instance(ePlayer).getName(False) in tRomeOutliveConditionPlayers:
				for rival in tRomeOutLiveConditionPlayers:
					if Civ(rival).isAlive():
						return
				CyGame().setWinner(pointer("Rome", teamID), eUniqueVictory)
And finally, the second set of code could be formatted in a slightly different way while remaining in essence exactly the same, just to point out that there is no one way of doing things:
Code:
from CivPlayer import *

tRomeOutliveConditionPlayers = ("Carthage", "Greece", "Persia")

def onSetPlayerAlive(ePlayer, bDead):
	if ( bDead
	     and Civ("Rome").isAlive()
	     and instance(ePlayer).getName(False) in tRomeOutliveConditionPlayers ):
		for bAlive in (Civ(rival).isAlive() for rival in tRomeOutLiveConditionPlayers):
			if bAlive: return
		CyGame().setWinner(pointer("Rome", teamID), eUniqueVictory)
And finally, this example relies on the setPlayerAlive game event callback (from the SDK to the Event Manager) which seems to be done any time a player either dies or is revived.

So the principle would then be that the first condition checks whether or not this is a death or a rebirth event. If its a death, then the next check is whether not the Roman player is still alive. (No victory for dead Romans.) If this also clears, then the next condition is whether or not the dying player is one of the Roman prerequisite rival Civs. And finally, all the rival Civs are iterated to make sure none of them is still alive. Because if one such player is found, then the function exits without setting Rome as the winner. (Otherwise Rome, of course, is the winner.)

It would be possible to fire something similar at cityAcquired as The_J suggested, but I can think of a couple of drawbacks with this approach. And it would also be possible to check this condition every single turn with BeginPlayerTurn. (But that seems wasteful.) And finally, it would be possible to create custom values for each of the sub-conditions for victory. (Really easy to do with CivPlayer, by the way.) So that any time any of the associated Civs dies its player gets flagged for the Roman victory condition. And once all rivals are cleared this way, then the victory code is fired.

Just to point out some of the options. Do we need to figure out how to do this with a BUG setup? :p
 
And I guess this is how you would define the actual VictoryType. There would be no settings enabling any kind of victory, because the victory is granted by the power of Python.

And you'd define the eUniqueVictory constant found in my sample Python code with something like:
Code:
eUniqueVictory = gc.getInfoTypeForString("VICTORY_UNIQUE")
Important to know about defining these types of constants is that XML is loaded only after the Python modules, so the XML tag wouldn't yet be available at the time of the call from Python at initialization.

The solution is to either put the assignment statement inside the function definition - or you import the module only at the actual start of the game - not on initialization. (I'll come back to this later though. You should probably get the XML for the new VictoryType in order before trying any of this.)
 
No BUG!
I think using what you did for Jamie might be Useful ;)
And XML is no problem at all, I know a fair amont about XML
And one more thing, I have Spawning Civilizations in my Mod (Thanks to The_J! ) So, the constants might not work with a rigid order...
 
Ah, CivPlayer also works with a constant set of players. :p But it should be as easy as to call CivPlayer.setup() each time something changes and everything gets updated instantly. :king:

Do you have any questions at this point? Is everything crystal clear about the code and so on?
 
Ah, CivPlayer also works with a constant set of players. :p But it should be as easy as to call CivPlayer.setup() each time something changes and everything gets updated instantly. :king:

Do you have any questions at this point? Is everything crystal clear about the code and so on?

I need to know where civplayer is! :p :lol:
 
Yeah, about that. I thought I'd publish it, actually. But I'm not sure if I have the time, because I'd like to document everything properly. On the other hand, the code itself is documented. Look for yourself (pre-release version only):
Spoiler :
PHP:
### CivPlayer player reference wrapper, by Baldyr
### For use only with scenarios with a set array of players and only one single instance of each Civilization type

from CvPythonExtensions import *
import PyHelpers
from DataStorage import * # comment out to disable global data storage

# constants

gc = CyGlobalContext()
Game = CyGame()
Map = CyMap()
Interface = CyInterface()
Translator = CyTranslator()

playerID, CyPlayer, teamID, CyTeam, PyPlayer = range(5)
iNumPlayers = Game.countCivPlayersEverAlive()
lPlayers = range(iNumPlayers)
bDataStorage = "DataStorage" in globals()

### Quick access interface:
### The functions access a global dictionary of CivPlayer instances - without the need to ever create one.

def Civ(name):
        """
        Returns a ready-to-use CivPlayer class instance for use with class methods. The name argument
        (string) refers to the short form av the Civilization name of the player associated with it.
        """
        return CivPlayer.Civilizations[name]

def pointer(name, iIndex):
        """
        Wraps up the Civ() function and the CivPlayer.get() method into a coherent, easy-to-use
        multipurpose function. There are two arguments; name (string) and iIndex (integer).
        They refer to the Civilization name short form and the type of reference being requested.
        The valid iIndex values are playerID, CyPlayer, teamID, CyTeam and PyPlayer.
        """
        return Civ(name).get(iIndex)

def instance(ePlayer):
        """
        Returns ready-to-use CivPlayer class instances from a index. The ePlayer argument (integer) is
        used for indexing.
        """
        if not ePlayer in lPlayers:
                return CivPlayer.Civilizations["Barbarians"]
        return CivPlayer.lPlayerIndex[ePlayer]

def human():
        """
        Returns the CivPlayer instance of the (current) human player.
        """
        return CivPlayer.lPlayerIndex[Game.getActivePlayer()]

### Main class definition

class CivPlayer:

        """
        Each instance of the class represents one player and one Civilization type. The instance
        is a wrapper for all the common player/team references, including PyPlayer. The instance
        also supports storage of arbitrary custom data with the DataStorage class/module.
        """

        Civilizations = dict()
        lPlayerIndex = list()

        def __init__(self, ePlayer):
                """
                A new instance of the class in created with the constructor CivPlayer(ePlayer)
                where ePlayer is a valid player ID (integer).
                """
                pPlayer = gc.getPlayer(ePlayer)
                eTeam = pPlayer.getTeam()
                pTeam = gc.getTeam(eTeam)
                player = PyHelpers.PyPlayer(ePlayer)
                self.tIndex = ePlayer, pPlayer, eTeam, pTeam, player                        

        def get(self, iIndex):
                """
                This method is used internally with valid CivPlayer instances to fetch player
                references. The valid iIndex values 0 - 4 are themselves referenced by the names
                playerID, CyPlayer, teamID, CyTeam and PyPlayer.
                """
                return self.tIndex[iIndex]

        def getName(self, bLong=None):
                """
                Returns the Civilization name (string) in its adjective form by default. By setting
                the optional bLong parameter to True the method will instead return the full
                civilization name, and a False value will in turn return the short form.
                """
                if bLong == None:
                        name = self.get(CyPlayer).getCivilizationAdjective(0)
                if bLong:
                        name = self.get(CyPlayer).getCivilizationDescription(0)
                else:
                        name = self.get(CyPlayer).getCivilizationShortDescription(0)
                return str(name)

        def isAlive(self):
                """
                Wraps up the CyPlayer.isAlive() method for convenience.
                """
                return self.get(CyPlayer).isAlive()

### Player reference interface:
### The methods wrap up a global dictionary of CivPlayer instances created on initialization.
### They are invoked directly on the class - not on instances of the class.
### The parameter name (string) must be the short form of the Civilizations name of a valid scenario player.

        @classmethod
        def getPlayers(cls, bMajor=False, index=None):
                """
                Used for looping CivPlayer instances from the global index. Dead players and
                the Barbarian player are always excluded, but setting the optional bMajor
                parameter to True will also exclude any Minor players. The method can also be
                used for automatically accessing the CivPlayer.get() method with the second
                index parameter. (See the CivPlayer.get() method for further details.)
                """
                for pCivPlayer in cls.lPlayerIndex:
                        pPlayer = pCivPlayer.get(CyPlayer)
                        if ( not pPlayer.isAlive()
                             or pPlayer.isBarbarian()
                             or (bMajor and pPlayer.isMinorCiv()) ):
                                continue
                        if index == None:
                                yield pCivPlayer
                        else:
                                yield pCivPlayer.get(index)

        @classmethod
        def playerID(cls, name):
                """
                Returns the enumerated player index number (integer) belonging to the name
                (string) Civilization, as defined in the World Builder Save file.
                """
                return cls.Civilizations[name].get(playerID)
        
        @classmethod
        def CyPlayer(cls, name):
                """
                Returns a CyPlayer class instance of the Civilization connected to the name
                (string) Civilization.
                """
                return cls.Civilizations[name].get(pPlayer)

        @classmethod
        def eTeam(cls, name):
                """
                Returns the enumerated team index number (integer) that the name (string) player is
                associated with in the World Builder Save file.
                """
                return cls.Civilizations[name].get(teamID)

        @classmethod
        def CyTeam(cls, name):
                """
                Returns the CyTeam class instance of the Civilization connected to the name (string)
                Civilization.
                """
                return cls.Civilizations[name].get(CyTeam)

        @classmethod
        def PyPlayer(cls, name):
                """
                Returns a PyPlayer class instance as defined in the PyHelpers module. The whole range
                of PyPlayer methods are available through this reference!
                """
                return cls.Civilizations[name].get(PyPlayer)

### Data Storage interface:
### The methods are invoked on CivPlayer instances and stores values into dictionaries handled by the DataStorage module.
### Refer to the documentation in the DataStorage module for specifics.
### If the DataStorage module isn't used then the player data will be saved with each CyPlayer object instead, which is somewhat suboptimal.

        def getScriptDict(self):
                """
                Fetches the entire player dictionary. No parameters are used.
                """
                if bDataStorage:
                        return sd.lPlayerData[self.get(playerID)]
                else:
                        return self.scriptDict

        def setData(self, key, value):
                """
                Used to store custom player data in a dictionary. The key parameter (string)
                is the identifier or the name of the value, and the value parameter is the value
                itself to be stored. Both are arNote that no Cy class instances can be stored as data!
                """
                if bDataStorage:
                        setPlayerData(self.get(playerID), key, value)
                else:
                        self.scriptDict[key] = value

        def getData(self, key):
                """
                Fetches the value corresponding to the key name (string).
                """
                if bDataStorage:
                        return getPlayerData(self.get(playerID), key)
                else:
                        return self.scriptDict[key]

### Setup - internal use only

def setup():
        iLanguage = setDefaultLanguage()
        for ePlayer in lPlayers:
                setupCivPlayer(ePlayer)
        addBarbarians()
        resetLanguage(iLanguage)
        print "CivPlayer.Civilizations", CivPlayer.Civilizations

def setupCivPlayer(ePlayer):
        pCivPlayer = CivPlayer(ePlayer)
        key = pCivPlayer.getName(False)
        name = Translator.getText(key, ())
        CivPlayer.Civilizations[name] = pCivPlayer
        CivPlayer.lPlayerIndex.append(pCivPlayer)
        setupScriptDict(pCivPlayer)
        
def setupScriptDict(pCivPlayer):
        if bDataStorage:
                sd.lPlayerData.append(DataStorage.sd())
                #pCivPlayer.scriptDict = None
        else:
                pCivPlayer.scriptDict = dict()

def setDefaultLanguage():
        iLanguage = Game.getCurrentLanguage()
        if iLanguage:
                Game.setCurrentLanguage(0)
        return iLanguage

def addBarbarians():
        CivPlayer.Civilizations["Barbarians"] = CivPlayer(gc.getBARBARIAN_PLAYER())

def resetLanguage(iLanguage):
        if iLanguage:
                Game.setCurrentLanguage(iLanguage)
Do you know the what the difference is between a PlayerType (integer value) and a CyPlayer object (instance of the CyPlayer class)? Because the same applies to TeamTypes and CyTeam instances. Handling these things is basically what CivPlayer does. It also gives you multiple options for fetching the values. But this is pretty much what needs to be documented proper. :p
 
The code is documented so that anyone interested in using it (or simply figuring out how it works) can see what each function/method does - without having to figure out the code for the whole thing. But I'd like to publish it as an Application Programming Interface in writing also, with examples of use. So I think I'll get to that, then.

Did you ever read my tutorial. Because knowing what Cy instances are is required knowledge for any Python modding. Otherwise you'll keep guessing what to do and that basically gets you nowhere. Get back to me if you can't figure the tutorial out, then.
 
So "Cy" is used when referring to the Normal Civ Python (hence, Cy!) and "Py" is used when referring to the PyHelpers Module!
I think I get it...
 
Back
Top Bottom