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?
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.
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.
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.
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)
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)
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)
eUniqueVictory = gc.getInfoTypeForString("VICTORY_UNIQUE")
Ah, CivPlayer also works with a constant set of players.But it should be as easy as to call CivPlayer.setup() each time something changes and everything gets updated instantly.
Do you have any questions at this point? Is everything crystal clear about the code and so on?
### 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)?
Does this mean it works?...the code itself is documented...