Playable Civ List

Tholish

Emperor
Joined
Jul 5, 2002
Messages
1,344
Location
Japan
I made a modcomp that adds two integer variables "minera" and "maxera" to CivilizationInfos. Whenever there is a check for whether a civ is playable or ai playable, I just made these part of the process so that for instance, if the start era of the game is Ancient, only civs with appropriate minera and maxera XML values can appear. I wanted to use CurrentEra, but it doesn't work, (I guess because at game start it is as of yet No Era? )

This gets the right results, partly. I set civs up with settings for the era they can appear, so that Rome can only appear in the Ancient or Classical eras; the HRE can only appear in gamees that start in the Medieval and Rennaissance eras and so forth.
What you really get is the list of available civs is always from the last game you played until you add a few leaders and the Custom Game setup gets the idea. Normally Start Now gets you the ancient era, but if the last game you played was another era, the human player can select from that former era and all the ai civs will be from the ancient era.

However, what I want is to have Barbarian Civ (which I currently have in the standalone python form for 3.19) and Revolution (which I mean to add soon from the new multiplayer standalone 3.19 version that is being made (!)) mods also in my mod, so that appropriate civs emerge for the current era. Thus you could start as Rome in the Ancient Era, limited to only real ancient era civs, but when you get to the Middle Ages the Byzantine Empire might emerge from a Revolution, or a barbarian civ might become the Holy Roman Empire.

I have modified the modcomp in my own mod to open it all up wide once the start of the game is over

Code:
bool CvCivilizationInfo::isAIPlayable() const
{   //tholish added the next six lines
	if (GC.getGame().getCurrentEra() > GC.getGame().getStartEra())
		return true;
	if (GC.getGame().getStartEra() < getMinEra())
		return false;
	if (GC.getGame().getStartEra()> getMaxEra())
		return false;
	return m_bAIPlayable;

but no civ ever emerges that is not from the original start era. I have experimented with other changes to try to "reset" start era, to no avail. I have searched for a list being made somewhere that decides once per game what civs are playable, but I have not found it. In fact, why does Civ remember the era of the last game played? I have had two versionf of my mod in the mod folder and renamed them and started, and the last era started is still remembered. Any suggestions or enlightenment?
 
Maybe this will work?

Code:
bool CvCivilizationInfo::isAIPlayable() const
{   //tholish added the next six lines
	if (GC.getGame().getCurrentEra() > GC.getGame().getStartEra())
	{
		if (GC.getGame().getCurrentEra() < getMinEra())
			return false;
		if (GC.getGame().getCurrentEra()> getMaxEra())
			return false;
		return m_bAIPlayable;
	}
	if (GC.getGame().getStartEra() < getMinEra())
		return false;
	if (GC.getGame().getStartEra()> getMaxEra())
		return false;
	return m_bAIPlayable;
 
Ah. Thank you.

EDIT: actually, return mb_AI_Playable rather than "true" was what I probably needed, the rest just got me no civs at all. However, it still doesn't work because there are other pieces involved.
 
Hey guys,

Sorry to re-up the thread (and this one https://forums.civfanatics.com/threads/era-limited-civs.325547/page-2),
I've been using this for years in my MOD, it's very very useful for starts in mid/later eras and multiplayer teamers especially.


Now I would like to have the option to exclude some LEADERS rather than civilizations, depending on factors,
Maybe era starts like this ModComp, or perhaps an option to check to exclude all my new leaders (agricultural) or an option to exclude aggressive leader (to check when map is too small) etc.


Technically I would consider myself very advanced with the C++ & XML now, absolutely no problem to develop the CivEraModComp further,
This is my current logic for the civilization for example :


Code:
bool CvCivilizationInfo::isPlayable() const
{
    // BTP 2.00 MPOPTION_ERA_SPECIFIC
    if (GC.getGame().getCurrentEra()== NO_ERA && GC.getGameINLINE().isMPOption(MPOPTION_ERA_SPECIFIC))
       {
        if (GC.getGame().getStartEra() < getMinEra())
            return false;
        if (GC.getGame().getStartEra()> getMaxEra())
            return false;
        if (GC.getGame().getStartEra() >= getSkipEra() && GC.getGame().getStartEra() <= getSkipEraEnd() && GC.getGame().getStartEra() != 0) //2.05
            return false;
       }
    //BTP 2.01 new option
    if (GC.getGameINLINE().isMPOption(MPOPTION_BAN_SPECIFIC) && m_bBanEra)
       {
            return false;
       }
    //End BTP 2.01
    //BTP 2.02 new option
    if (!GC.getGameINLINE().isMPOption(MPOPTION_SPECATOR_ALLOWED) && m_bSpectator)
       {
            return false;
       }
    //End BTP 2.02
 
    return m_bPlayable;
}


But how would you go around excluding leaders ?
- C++ CvPlayer doesn't have a isPlayableLeader thing, it's onlyt isPlayableCiv
- C++ CvInitCore does refer to ::getLeader just like ::getCiv, but there is a getPlayableCiv, I suppose what I don't understand is where is "return m_abPlayableCiv[eID];" ever used
- For that matter, am surprise how little code use "GAMEOPTION_LEAD_ANY_CIV", what happens when the option is checked? there is no code using it? Where is this done?
- Also don't understand : Say am inventing a civ that is not playable (Spectator) and give it a leader (SidMeier), just like the Barbarian Civ & Barbarian Leader. If I launch a game with option unrestricted in Random, I cannot fall on the Barbarian Civ, nor the Spectator leader, nor the Barbarian leader.... but I can fall on the "SidMeier" leader... why ? There is no XML difference and no C++ hardcoding of LEADER_BARBARIAN exclusion


Any hint welcome !
I realize there might be quite a bit of code to do, I just don't know which angle to start with
 
My assumption would be that most of this is implicitly being done by the menu, i.e. if you can select a civ you can always select all of its leaders, unless the lead any civ option is checked, when you can select a leader associated with any civilization. Likewise it probably has hardcoded exceptions for the barbarian player. Since the main menu is coded inaccessibly in the exe, there's not much we can do to interfere with that.

I can't think of any good solutions here except splitting all civilisations into one leader civs (i.e. ENGLAND_ELIZABETH and ENGLAND_VICTORIA etc) and using your existing logic for civilisations, which of course is less than ideal.
 
Thanks for the quick answer.

So you confirm there is a higher level where other coding/hardcoding is done and might do that.
I'd except so since its quite clear nothing relates to GAMEOPTION_LEAD_ANY_CIV's Civ Selection in the code.
I was told indeed most of the "randomization" of items gets done there.

I'll still try to find a workaround this, I'd really like to be able to exclude some Civs.

Thanks a ton for the help
 
So does your CvCivilizationInfo::isPlayable function work as intended? Is it supposed to exclude civs from the Custom Game screen depending on the selected start era? As far as I can tell, the leader and civ dropdowns on the Custom Game (and Staging Room) screen aren't updated when the start era is changed, so ...
Anyway, if it does work, then similar code in CvCivilizationInfo::isLeaders could do the trick for disallowing leaders.
 
So I had the idea of turning <bLeaderAvailability> to 0 for a few leaders :
- To my good surprise in normal (=restricted) mode then it works very well, those leader cannot be selected; and better if you get a civilization with all "0" leaders you'll get another (as if in "unrestricted"), no crash.
- But in "unrestricted" leader mode, it doesnt work, you can get the leader assigned

Code:
<Leaders>
             <Leader>
                    <LeaderName>LEADER_BRENNUS</LeaderName>
                    <bLeaderAvailability>0</bLeaderAvailability>
                </Leader>
                <Leader>
                    <LeaderName>LEADER_BOUDICA</LeaderName>
                    <bLeaderAvailability>0</bLeaderAvailability>
                </Leader>
            </Leaders>


So does your CvCivilizationInfo::isPlayable function work as intended? Is it supposed to exclude civs from the Custom Game screen depending on the selected start era? As far as I can tell, the leader and civ dropdowns on the Custom Game (and Staging Room) screen aren't updated when the start era is changed, so ...
Anyway, if it does work, then similar code in CvCivilizationInfo::isLeaders could do the trick for disallowing leaders.
Yeah works very well.
In fact in order not to be too glitchy, I've put it on an MULTIPLAYER option (mostly because they don't store the fact that they're checked in),
It still glitches in the sense that you need to click TWICE on the civ list to get it properly displayed.
But once you launch the game, no problem, everyone gets a civ from the selected scope.


So on your suggestion :
Code:
bool CvCivilizationInfo::isLeaders(int i) const
{
    FAssertMsg(i < GC.getNumLeaderHeadInfos(), "Index out of bounds");
    FAssertMsg(i > -1, "Index out of bounds");
    return m_pbLeaders ? m_pbLeaders[i] : false;
}

Calls this I can only imagine... :
Code:
pXML->SetVariableListTagPair(&m_pbLeaders, "Leaders", sizeof(GC.getLeaderHeadInfo((LeaderHeadTypes)0)), GC.getNumLeaderHeadInfos());
...and I still don't see "bLeaderAvaibility" anywhere in C++
I presume this just gets the variable, maybe I do not understand what "LeaderHeadTypes)0" and particulary it's 0 does

Much appreciate feedback guys
 
But once you launch the game, no problem, everyone gets a civ from the selected scope.
Ah, it's enforced at game start. I see.
So on your suggestion :
bool CvCivilizationInfo::isLeaders(int i) const { ... }
Calls this I can only imagine... :
pXML->SetVariableListTagPair(&m_pbLeaders, "Leaders", sizeof(GC.getLeaderHeadInfo((LeaderHeadTypes)0)), GC.getNumLeaderHeadInfos());
...and I still don't see "bLeaderAvaibility" anywhere in C++
I presume this just gets the variable, maybe I do not understand what "LeaderHeadTypes)0" and particulary it's 0 does
Hm. Looks like the DLL really doesn't care what the "bLeaderAvailability" element is called; only Civ4CivilizationsSchema.xml enforces that name. The SetVariableListTagPair function is implemented in the EXE, but we do have the declaration in CvXMLLoadUtility.h:
Code:
// allocate and initialize a list from a tag pair in the xml
void SetVariableListTagPair(bool **ppbList, const TCHAR* szRootTagName,
    int iInfoBaseSize, int iInfoBaseLength, bool bDefaultListVal = false);
The (LeaderHeadTypes)0 is only used to get an arbitrary CvLeaderHeadInfo reference for the sizeof operator. Probably for allocating memory for m_pbLeaders. Edit (October): The implementation is in CvXMLLoadUtilitySet.cpp and iInfoBaseSize is actually unused. I've removed that parameter from my own mod in the meantime.)

So it looks like we had the same idea: Making leaders unavailable through CvCivilizationInfo. But that won't work with Unrestricted Leaders. What else could you try? I don't think masquerading a leader as Barbarian is going to work. The Barbarian leader is probably identified through a getType()=="LEADER_BARBARIAN" check in the EXE. A quick test suggests that this happens already when the game is launched: no getType call on any CvLeaderHeadInfo object upon entering the Custom Game screen. How about CvGlobals::getLeaderHeadInfo? Check whether eLeaderHeadNum is a leader that you want to exclude and, if so, change eLeaderHeadNum to a leader that you don't want to exclude before returning *(m_paLeaderHeadInfo[eLeaderHeadNum]) as in BtS.

And you could always change leaders through CvInitCore::setLeader, but that's probably not going to help on the Custom Game or Staging Room screen.
 
Last edited:
Now I would like to have the option to exclude some LEADERS rather than civilizations, depending on factors,
Maybe era starts like this ModComp, or perhaps an option to check to exclude all my new leaders (agricultural) or an option to exclude aggressive leader (to check when map is too small) etc.
As already mentioned, it seems building the leader list is done in the game exe, meaning it's out of reach for modders. However if we aim for less clean code, we can mod it indirectly. That is the exe will ask for xml data and we can fake xml data to say not include leader X if a certain variable isn't set. isGameInitializing() can also be used to tell if reading a certain xml data is done from the menu or during the game, though you should watch out for post menu game setup.

I'm not sure I recommend this though because I fear it can quickly become messy, unreadable and buggy. Also don't you set leader and then era? If anything, you should mod the map options to exclude certain eras if you want a leader to be restricted to certain eras. This can be done in python in each map script (I think).

You should also be aware that custom games are set up differently. It sets each menu and then the player can select. It doesn't have any "if option A is this, then option B can't be that". We could add that to the GUI if we had the source, but this screen is in the exe.
 
Appreciate this a long time... but I did pretty much everything else I could think of on my/the MOD before this... Had to improve at Python a bit and stop doing everything in C++ to get it done

And also had to make a concession :
The goal was to not have leaders with some traits playable (the new ones),
Instead you will be able to play those leaders themselves, but at game launch they take another trait that is acceptable depending on options you've checked

I did 4 options, 2 to pick new traits, 2 to exclude aggressive and financial trait (my team asked for duel games, too violent on tiny/duel map).
Obviously you can be flexible with this.

Only had to declare in C++ the options themselves obviously,
And 3 functions - only first one is key, other 2 to be clean
.def("setHasTrait", &CvLeaderHeadInfo::setHasTrait, "bool (int i)")
.def("hasOriginalTrait", &CvLeaderHeadInfo::hasOriginalTrait, "bool (int i)")
.def("resetHasTrait", &CvLeaderHeadInfo::resetHasTrait, "bool (int i)")

C++ part :
Code:
//2.08t
void CvLeaderHeadInfo::setHasTrait(int i, bool bNewValue)
{
    if (hasTrait(i) != bNewValue)
    {
        m_pbTraits[i] = bNewValue;
    }
}

bool CvLeaderHeadInfo::hasOriginalTrait(int i) const
{
    FAssertMsg(i < GC.getNumTraitInfos(), "Index out of bounds");
    FAssertMsg(i > -1, "Index out of bounds");

    // Only do vanilla check if in game - so civilopedia works normally from main menu
    if (GC.getInitCore().getType() != GAME_NONE)
    {
        if (GC.getGameINLINE().isOption(GAMEOPTION_VANILLA_MOD))
        {
            return false;
        }
    }
    return m_pbTraitsOriginal ? m_pbTraitsOriginal[i] : false;
}

void CvLeaderHeadInfo::resetHasTrait(int i)
{
    m_pbTraits[i] = m_pbTraitsOriginal[i];
}
// end 2.08t



Python part - It's done in CvEventManager in def onGameStart(self, argsList):
Code:
#Penny change trait for Options - 2.08t   
       
        #First need to reset from game to game, otherwise game keeps on memory and all leaders are changing !
        for i in range(gc.getNumLeaderHeadInfos()):
            for j in range(gc.getNumTraitInfos()):
                gc.getLeaderHeadInfo(i).resetHasTrait(j)
        #Define Stuff
        iTraitAgr = CvUtil.findInfoTypeNum(gc.getTraitInfo, gc.getNumTraitInfos(),'TRAIT_AGGRESSIVE')
        iTraitConq = CvUtil.findInfoTypeNum(gc.getTraitInfo, gc.getNumTraitInfos(),'TRAIT_CONQUEROR')
        iTraitAgri = CvUtil.findInfoTypeNum(gc.getTraitInfo, gc.getNumTraitInfos(),'TRAIT_AGRICULTURAL')
        iTraitFin = CvUtil.findInfoTypeNum(gc.getTraitInfo, gc.getNumTraitInfos(),'TRAIT_FINANCIAL')
        bOptionAgriOn = gc.getGame().isOption(GameOptionTypes.GAMEOPTION_EXPANSION_LEADERS)
        bOptionConqOn = gc.getGame().isOption(GameOptionTypes.GAMEOPTION_EXPANSION_LEADERS_CONQUEROR)           
        bOptionAgresfOn = not gc.getGame().isOption(GameOptionTypes.GAMEOPTION_DISABLE_TRAIT_AGGRESSIVE)
        bOptionFinOn = not gc.getGame().isOption(GameOptionTypes.GAMEOPTION_DISABLE_TRAIT_FINANCIAL)           
           
           
       
        #Create General Lists
        listAllTraits = []
        for numTrait in range(gc.getNumTraitInfos()):
            listAllTraits.append(numTrait)
       
        listTraitsAvailable = listAllTraits[:]
        if not bOptionAgriOn :
            listTraitsAvailable.remove(iTraitAgri)
        if not bOptionConqOn :
            listTraitsAvailable.remove(iTraitConq)
        if not bOptionAgresfOn :
            listTraitsAvailable.remove(iTraitAgr)
        if not bOptionAgresfOn :
            listTraitsAvailable.remove(iTraitFin)
       
        #The player bit starts here
        for iPlayer in range(gc.getMAX_PLAYERS()):
           
            iLeader = gc.getPlayer(iPlayer).getLeaderType()
            listTraitsPlayer = listTraitsAvailable[:]
           
            #This bit important, you don't want to get twice the trait you happen to have !
            for numTraitTwo in range(gc.getNumTraitInfos()):
                if gc.getLeaderHeadInfo(iLeader).hasTrait(numTraitTwo):
                    if listTraitsPlayer.count(numTraitTwo) > 0:#error protection (always errors 1 of the trait)
                        listTraitsPlayer.remove(numTraitTwo)                   
           
            if (not bOptionAgriOn):
                if gc.getLeaderHeadInfo(iLeader).hasTrait(iTraitAgri):
                    iRandomTrait = CyGame().getSorenRandNum(len(listTraitsPlayer),"New Trait")
                    gc.getLeaderHeadInfo(iLeader).setHasTrait(iTraitAgri,False)
                    #gc.getLeaderHeadInfo(iLeader).setHasTrait(iRandomTrait,True)                   
                    gc.getLeaderHeadInfo(iLeader).setHasTrait(listTraitsPlayer[iRandomTrait],True)
           
            #in Fact you need to repeat this loop each time because you could be Agri+Conq and be assigned twice same trait at random
            for numTraitTwo in range(gc.getNumTraitInfos()):
                if gc.getLeaderHeadInfo(iLeader).hasTrait(numTraitTwo):
                    if listTraitsPlayer.count(numTraitTwo) > 0:#error protection (always errors 1 of the trait)
                        listTraitsPlayer.remove(numTraitTwo)           
           
            if (not bOptionConqOn):
                if gc.getLeaderHeadInfo(iLeader).hasTrait(iTraitConq):
                    iRandomTrait = CyGame().getSorenRandNum(len(listTraitsPlayer),"New Trait")
                    gc.getLeaderHeadInfo(iLeader).setHasTrait(iTraitConq,False)
                    #gc.getLeaderHeadInfo(iLeader).setHasTrait(iRandomTrait,True)                   
                    gc.getLeaderHeadInfo(iLeader).setHasTrait(listTraitsPlayer[iRandomTrait],True)
            for numTraitTwo in range(gc.getNumTraitInfos()):
                if gc.getLeaderHeadInfo(iLeader).hasTrait(numTraitTwo):
                    if listTraitsPlayer.count(numTraitTwo) > 0:#error protection (always errors 1 of the trait)
                        listTraitsPlayer.remove(numTraitTwo)   
                   
            if (not bOptionAgresfOn):
                if gc.getLeaderHeadInfo(iLeader).hasTrait(iTraitAgr):
                    iRandomTrait = CyGame().getSorenRandNum(len(listTraitsPlayer),"New Trait")
                    gc.getLeaderHeadInfo(iLeader).setHasTrait(iTraitAgr,False)
                    #gc.getLeaderHeadInfo(iLeader).setHasTrait(iRandomTrait,True)                   
                    gc.getLeaderHeadInfo(iLeader).setHasTrait(listTraitsPlayer[iRandomTrait],True)
           
            ### Financial
            for numTraitTwo in range(gc.getNumTraitInfos()):
                if gc.getLeaderHeadInfo(iLeader).hasTrait(numTraitTwo):
                    if listTraitsPlayer.count(numTraitTwo) > 0:#error protection (always errors 1 of the trait)
                        listTraitsPlayer.remove(numTraitTwo)   
                   
            if (not bOptionAgresfOn):
                if gc.getLeaderHeadInfo(iLeader).hasTrait(iTraitFin):
                    iRandomTrait = CyGame().getSorenRandNum(len(listTraitsPlayer),"New Trait")
                    gc.getLeaderHeadInfo(iLeader).setHasTrait(iTraitFin,False)
                    #gc.getLeaderHeadInfo(iLeader).setHasTrait(iRandomTrait,True)                   
                    gc.getLeaderHeadInfo(iLeader).setHasTrait(listTraitsPlayer[iRandomTrait],True)
#Penny end Trait change
 
Last edited:
Back
Top Bottom