Quick Modding Questions Thread

Can someone help me out with modding the diplo AI? I am wondering how feasible what I am thinking about is, and where I would best need to make those changes.
It seems that the trade screen in the EXE doesn't allow both sides of a trade to add items to the table while at war; screenshot attached. At first I thought the EXE might show this popup based on CvDeal::isEndWar, but, in a test, modifying that function didn't help. Might still be possible to work around the problem by feigning peace at just the right moment, but that'll require some open-ended experimentation.
[...] CvPlayer::canTradeItem would need to be modified to limit the cities that can be offered in this specific situation [...], but how can I tell what the context of the trade is? Do I need to add another custom boolean argument?
Maybe this question is moot, but I still want to mention CvPlayer::updateTradeList as a function where context could be stored (at the CvPlayer object I guess) for the duration of some trade interaction. updateTradeList gets called whenever an item is added to or removed from the table.

The AI could always make a capitulation offer through a non-diplo popup, but that wouldn't allow for negotiation.
Or perhaps collapse could be suspended somehow, for a while, when a civ is close to capitulation?
 

Attachments

  • no-trade-during-war.jpg
    no-trade-during-war.jpg
    466.2 KB · Views: 55
It seems that the trade screen in the EXE doesn't allow both sides of a trade to add items to the table while at war
Once again there is an issue where the thinking heads in direction of a new diplo GUI. The question is when we become tired enough of all the issues that we decide it's worth the effort.
 
It seems that the trade screen in the EXE doesn't allow both sides of a trade to add items to the table while at war; screenshot attached. At first I thought the EXE might show this popup based on CvDeal::isEndWar, but, in a test, modifying that function didn't help. Might still be possible to work around the problem by feigning peace at just the right moment, but that'll require some open-ended experimentation.
Maybe this question is moot, but I still want to mention CvPlayer::updateTradeList as a function where context could be stored (at the CvPlayer object I guess) for the duration of some trade interaction. updateTradeList gets called whenever an item is added to or removed from the table.

The AI could always make a capitulation offer through a non-diplo popup, but that wouldn't allow for negotiation.
Or perhaps collapse could be suspended somehow, for a while, when a civ is close to capitulation?
Thanks for looking into this. The EXE getting in the way is not fair, but at least it kills a lot of open questions that would have to be answered otherwise. I don't think it's worth trying to work around this. Finding ways that help against collapse instead might be the better solution then.
 
How would I go about making it so that you can't settle on top of resources? Preferably by some means I can merge into mods that I play. So like ideally XML.
 
@PPQ_Purple: Looking at CvPlayer::canFound in the DLL, it seems pretty clear that this can't be done in XML. There is a Python callback though: cannotFoundCity in CvGameInterface, to be enabled through USE_CANNOT_FOUND_CITY_CALLBACK in PythonCallbackDefines.xml.
 
Hi,

Can anyone tell me what is wrong with this, specifically the "if (pLoopPlot->getOwnerINLINE() != getOwner)" line?
Spoiler :
Code:
       for (int iI = 0; iI < iBorderRange; iI++)
       {
           CvPlot* pLoopPlot = getCityIndexPlot(iI);

           if (pLoopPlot != NULL)
           {
               if (pLoopPlot->isCity())
               {
                   if (pLoopPlot->getOwnerINLINE() != getOwner)
                   {
                       bFound = true;
                   }
               }
           }
       }
       if (!(bFound))
       {
           return false;
       }
I'm using this in CvCity.cpp and the intention is to check that the owner of pLoopPlot is not the same as the owner of the City.
 
Hi,

Can anyone tell me what is wrong with this, specifically the "if (pLoopPlot->getOwnerINLINE() != getOwner)" line?
Spoiler :
Code:
       for (int iI = 0; iI < iBorderRange; iI++)
       {
           CvPlot* pLoopPlot = getCityIndexPlot(iI);

           if (pLoopPlot != NULL)
           {
               if (pLoopPlot->isCity())
               {
                   if (pLoopPlot->getOwnerINLINE() != getOwner)
                   {
                       bFound = true;
                   }
               }
           }
       }
       if (!(bFound))
       {
           return false;
       }
I'm using this in CvCity.cpp and the intention is to check that the owner of pLoopPlot is not the same as the owner of the City.

"getOwner" should be "getOwner()". So with the brackets.
 
Arrrgh.:wallbash: I had changed the line since. However, I think I've found the true cause of my problems was that I wasn't looping around the city as I thought. I've changed the
Code:
      for (int iI = 0; iI < iBorderRange; iI++)
       {
           CvPlot* pLoopPlot = getCityIndexPlot(iI);
for
Code:
       for (int iDX = -iBorderRange; iDX <= iBorderRange; iDX++)
       {
           for (int iDY = -iBorderRange; iDY <= iBorderRange; iDY++)
           {
               CvPlot* pLoopPlot = plotXY(getX_INLINE(), getY_INLINE(), iDX, iDY);
. I've also amended the line I originally queried to
Code:
                       if ((GET_PLAYER(pLoopPlot->getOwner()).getID() != GET_PLAYER(getOwner()).getID()))
.

It's now working as it should.
 
Hey guys, a quick one.

It's to do with a logic where everything works well but I have to make it 'OOS compatible' when a player leaves the software and comes back.
I assign a "UUResource" for each Civ, but I also have an option to randomize this UUResource through a logic in python, everything scales well.

How to make it that the savegame keeps the info though ?
I'm sure there is a nice way... perhaps someone can point me towards the best way

In the mean time I have my own way, but I have 1 line I don't know what to do :

I created a variable list in CvGame for each unit to remember its resource
Code:
int* m_paiUnitUUMResource;//2.21m

And the functions to handle this
Code:
int CvGame::getUnitInfoUUMResource(UnitTypes eIndex)//2.21m                                                     
{
    return m_paiUnitUUMResource[eIndex];
}
void CvGame::setUnitInfoUUMResouce(UnitTypes eIndex, BonusTypes eBonus)//2.21m
{
    m_paiUnitUUMResource[eIndex] = eBonus;
}

All I need to do is when I execute (via Python) my logic to decide the new Bonus, I force a new function to go and store the info in the CvGame/saved variable

Code:
void CvUnitInfo::setUUMBonus(int iBonus)//2.21k
{
    m_iUUMBonus = iBonus;//This is the 'normal part' works well
 
    //This is just for the savegame part
    //GC.getGameINLINE().setUnitInfoUUMResouce((UnitTypes)this->getType(),(BonusTypes)iBonus);//2.21m
    GC.getGameINLINE().setUnitInfoUUMResouce((UnitTypes)41,(BonusTypes)iBonus);//2.21m
}

And if it was working, I would just call this on CvEventManager onLoadGame

Code:
void CvUnitInfo::reloadUUMBonusSaved()//2.21m
{
    m_iUUMBonus = GC.getGameINLINE().getUnitInfoUUMResource((UnitTypes)41);//2.21m
}

---> This works well, but as you can see I used the arbitrary number 41 to test it
I don't know how to say in the function, use the very number of the unit itself (in the list of UnitType)
As you can see in my commented line, I though I could do ((UnitTypes)this->getType().... but it doesn't work because you need a number.
I don't know how to 'self-refer' the number of unit in CvInfo.cpp itself...
 
[...] I don't know how to 'self-refer' the number of unit in CvInfo.cpp itself...
The CvInfo classes don't know their own enum id. Here's a recent discussion about that: SDK/Python forum

A simple solution would be:
UnitTypes eUnit = (UnitTypes)GC.getInfoTypeForString(getType());

A cleaner approach might be to move your CvUnitInfo functions to CvGame so that the UnitTypes-to-BonusTypes mapping is handled entirely by CvGame. A bit like how CvGame already keeps track of which "special" units are valid in m_pabSpecialUnitValid. (Actually, it seems that this array is unused and kind of obsolete since BtS, but, still, it could serve as a guide.)
 
Cool thanks a ton ! Was hoping you would be able to see and answer this easily ;) I'll try tonight !

Happy to see the question is relevant, I wondered if I missed something obvious.
Agree this feels hack-ish, especially having a python function called on onLoadGame, if it doesn't work cleanly, I'll redo it from CvGame (was hoping to avoid, quite a bit of related code to migrate)

EDIT : Actually works well, was 2min job. Thanks a ton !
 
Last edited:
I'm trying to get something like a Civ3 style cultural victory into a mod. A player wins when they have a certain amount of culture that is also at least double that of their nearest rival. I've run into hiccups pretty early though, can't even get victory to trigger from just total amount of culture. I've looked at the Crossroads of the World mod which comes with BTS and have based my python on that.

Inside of CvEventManager.py I have this:
Code:
def onEndGameTurn(self, argsList):
        'Called at the end of the end of each turn'
        iGameTurn = argsList[0]
        iCultureVictoryReq = 50000
        for iLoopTeam in range(gc.getMAX_CIV_TEAMS()):
            pPlayer = gc.getTeam(iLoopTeam)
            iPlayerCulture = pPlayer.countTotalCulture()
            if (iPlayerCulture >= iCultureVictoryReq):
                CyGame().setWinner(pPlayer.getTeam(), 7)
What I want from the code is it to loop through all teams in the game and check their culture. If their culture is greater than or equal to 50000 then a new culture victory is triggered. I've added an entry in VictoryInfos.xml for new culture victory modeled after the entry for commerce victory in Crossroads of the World:
Code:
<VictoryInfo>
            <Type>VICTORY_NEW_CULTURE</Type>
            <Description>TXT_KEY_VICTORY_NEW_CULTURE</Description>
            <Civilopedia>TXT_KEY_VICTORY_NEW_CULTURE_PEDIA</Civilopedia>
            <bTargetScore>1</bTargetScore>
            <bEndScore>0</bEndScore>
            <bConquest>0</bConquest>
            <bDiploVote>0</bDiploVote>
            <bPermanent>0</bPermanent>
            <iPopulationPercentLead>0</iPopulationPercentLead>
            <iLandPercent>0</iLandPercent>
            <iMinLandPercent>0</iMinLandPercent>
            <iReligionPercent>0</iReligionPercent>
            <CityCulture>NONE</CityCulture>
            <iNumCultureCities>0</iNumCultureCities>
            <iTotalCultureRatio>0</iTotalCultureRatio>
            <iVictoryDelayTurns>0</iVictoryDelayTurns>
            <VictoryMovie>ART_DEF_MOVIE_VICTORY_CULTURAL</VictoryMovie>
        </VictoryInfo>
Victory doesn't trigger though, and I'm not sure what's going on. I don't really know how the code for victory conditions works in Civ 4. Where does the game check for non-modded victory conditions? I'd appreciate it if anyone could quickly layout what is needed for the game to register a new victory condition/any glaring issues in my code.
 
I see you check the teams, but IIRC you should check the players. (At least, triggering UHV (unique historical victory) in RFC mods does this) You could try this:

Code:
def onEndGameTurn(self, argsList):
        'Called at the end of the end of each turn'
        iGameTurn = argsList[0]
        iCultureVictoryReq = 50000
        for iLoopPlayer in range(gc.getMAX_CIV_PLAYERS()):
            pPlayer = gc.getPlayer(iLoopPlayer)
            iPlayerCulture = pPlayer.countTotalCulture()
            if (iPlayerCulture >= iCultureVictoryReq):
                CyGame().setWinner(iLoopPlayer, 7)

I haven't tested it or anything. It is just a first glance review/fix. (And maybe I'm completely wrong)
 
I tried it out and no luck. But the Crossroads of the World code also uses players not teams so you could very well be right that players should be used! I used teams because when countTotalCulture() is used elsewhere (CvVictoryScreen.py) teams are used not players but I'm not actually sure if this matters.
 
It only matters if you allow more players in a single team. Then it is the difference between the culture of a single member or the whole team. Most games consist of only single player teams though, so it likely doesn't matter.

Small thing. I looked up the setWinner function in the API it it does require the team number as input argument. So your original code:
CyGame().setWinner(pPlayer.getTeam(), 7)
was actually correct.
This likely doesn't fix your problem, but it might prevent future errors in the code. (I based my fix on the code from RFC DoC. There each player ID and team ID are the same, so their the result is the same)

But I can't see what is wrong with the code TBH.
 
There is another way to loop surrounding plots.
PHP:
for (int i = 0; i < NUM_DIRECTION_TYPES; ++i)
{
    CvPlot* pLoopPlot = ::plotDirection(plot->getX_INLINE(), plot->getY_INLINE(), (DirectionTypes) i);
    if (pLoopPlot != NULL)
    {

    }
}
It's part of vanilla and it will check for map edges and return NULL if the plot doesn't exist. It only checks the 8 neighboring plots. If you want to include the plot itself, then let i start as NO_DIRECTION (-1).

This is vanilla code and it has builtin checks for maps wrapping around and stuff like that meaning it will likely be more stable in edge cases. It will also make the code cleaner as in you will use the same coding style as vanilla is already using.

Is there a version of this to check in expanding rings around a plot, similar to the rings of cultural influence for a city as it expands?
 
There is another way to loop surrounding plots. [...]
Is there a version of this to check in expanding rings around a plot, similar to the rings of cultural influence for a city as it expands?
For a range that approximates a circle with a radius greater than 2 tiles, the BtS code uses the plotXY approach that you posted earlier, resulting in a square, and makes a plotDistance check to skip the corners of that square. One of the few examples: CvUnitAI::AI_carrierSeaTransport
For a radius of 2, there's getCityIndexPlot, and for radius 1, there's plot[Cardinal]Direction.
 
Messing around some more with the cultural victory code and seeing some strange behavior. The victory triggers properly, but only if I edit the python while the game is running and then swap back in. And the victory doesn't trigger at the end of the turn but immediately. ie. if I have a total culture of 5000 and the requirement is set to 1000 the victory won't trigger. But if change the requirement to 3000 and then swap back into civ the victory is immediately triggered. The victory won't trigger if I don't have the required amount of culture.

This tells me my code for victory checking at least is sound. But something wonky is going on somewhere else that I can't even begin to figure out. Is this behavior, code only working while hot swapping, a symptom of something specific?

I've put the whole (slightly updated per merjin's suggestions) code below:

Code:
    def onEndGameTurn(self, argsList):
        'Called at the end of the end of each turn'
        iGameTurn = argsList[0]
        iCultureVictoryReq = 1000
        for iLoopPlayer in range(gc.getMAX_CIV_PLAYERS()):
            pPlayer = gc.getPlayer(iLoopPlayer)
            iPlayerCulture = pPlayer.countTotalCulture()
            if (iPlayerCulture >= iCultureVictoryReq):
                CyGame().setWinner(pPlayer.getTeam(), gc.getInfoTypeForString("VICTORY_NEW_CULTURE"))
 
How can I make the civilizations I play with change its capital automatically after a certain event or condition (specifically after a certain year, for example I want the Arabs to change the Capital from Mecca to Damascus automatically after the year 660).
 
How can I make the civilizations I play with change its capital automatically after a certain event or condition (specifically after a certain year, for example I want the Arabs to change the Capital from Mecca to Damascus automatically after the year 660).

An event to build the palace in Damascus might do it.
 
Back
Top Bottom