Quick Modding Questions Thread

I know :(

Honestly part of the reason is that using VS2017 still feels like using a ten year old IDE so with VS2010 I'm at least emotionally prepared for things being so terrible.
 
More questions! I am currently trying to fix an issue with the espionage advisor that I originally got from History Rewritten:

Spoiler :
Civ4ScreenShot0400.JPG


The issue is with the city selection list titled "known cities". You can click the individual rows and the row is highlighted (here with Saint-Louis du Sénegal) but the selected city isn't updated in the backend. This is indicated by the cyan coloured text highlighting, which should have updated from Port-au-Prince to Saint-Louis along with the selection.

Here's the code that populates the table:
Code:
       if self.iTargetPlayer > -1:
           pTargetPlayer = gc.getPlayer(self.iTargetPlayer)
           iEspionage = self.pActiveTeam.getEspionagePointsAgainstTeam(pTargetPlayer.getTeam())

           # City Table
           screen.addTableControlGFC("CityTable", 3, self.X_CITY_LIST, self.Y_CITY_LIST, self.W_CITY_LIST, self.H_CITY_LIST, True, False, 24, 24, TableStyles.TABLE_STYLE_STANDARD)
           screen.setTableColumnHeader("CityTable", 0, u"<font=2>Known Cities</font>", self.W_CITY_LIST - 102)
           screen.setTableColumnHeader("CityTable", 1, u"<font=2>Spies</font>", 50)
           screen.setTableColumnHeader("CityTable", 2, u"<font=2>Cost</font>", 50)
           screen.setStyle("CityTable", "Table_StandardCiv_Style")
           screen.enableSelect("CityTable", True)

           (loopCity, iter) = pTargetPlayer.firstCity(False)
           while(loopCity):
               if loopCity.isRevealed(self.iActiveTeam, False):
                   iRow = screen.appendTableRow("CityTable")

                   if self.iTargetCity == -1:
                       self.iTargetCity = loopCity.getID()

                   if self.iTargetCity == loopCity.getID():
                       screen.selectRow("CityTable", iRow, True)

                   # Status
                   if loopCity.isCapital():
                       szStatus = u"%c" % CyGame().getSymbolID(FontSymbols.STAR_CHAR)
                   elif loopCity.isGovernmentCenter():
                       szStatus = u"%c" % CyGame().getSymbolID(FontSymbols.SILVER_STAR_CHAR)
                   else:
                       szStatus = u"%c" % CyGame().getSymbolID(FontSymbols.BULLET_CHAR)

                   # Name
                   szName = loopCity.getName()
                   if self.iTargetCity == loopCity.getID():
                       szName = CyTranslator().changeTextColor(szName, gc.getInfoTypeForString('COLOR_HIGHLIGHT_TEXT'))

                   screen.setTableText("CityTable", 0, iRow, szStatus + szName, "", WidgetTypes.WIDGET_ESPIONAGE_SELECT_CITY, loopCity.getID(), 0, CvUtil.FONT_LEFT_JUSTIFY)

                   # Spies
                   if self.iTargetCity == loopCity.getID():
                       del self.CitySpies[:]

                   iNumSpies = 0
                   loopPlot = loopCity.plot()
                   for iUnit in xrange(loopPlot.getNumUnits()):
                       pUnit = loopPlot.getUnit(iUnit)
                       if not pUnit.isNone():
                           if pUnit.isCounterSpy() and pUnit.getOwner() == self.iActivePlayer:
                               iNumSpies += 1
                               if self.iTargetCity == loopCity.getID():
                                   self.CitySpies.append(pUnit)

                   if iNumSpies > 0:
                       screen.setTableInt("CityTable", 1, iRow, str(iNumSpies), "", WidgetTypes.WIDGET_ESPIONAGE_SELECT_CITY, loopCity.getID(), 0, CvUtil.FONT_RIGHT_JUSTIFY)

                   # Mission Cost
                   if self.iMission > -1:
                       iCost = self.pActivePlayer.getEspionageMissionCost(self.iMission, self.iTargetPlayer, loopPlot, -1)
                       if iCost > -1:
                           if iEspionage < iCost:
                               szCost = CyTranslator().changeTextColor(str(iCost), gc.getInfoTypeForString('COLOR_NEGATIVE_TEXT'))
                           else:
                               szCost = CyTranslator().changeTextColor(str(iCost), gc.getInfoTypeForString('COLOR_POSITIVE_TEXT'))

                           screen.setTableInt("CityTable", 2, iRow, szCost, "", WidgetTypes.WIDGET_ESPIONAGE_SELECT_CITY, loopCity.getID(), 0, CvUtil.FONT_RIGHT_JUSTIFY)

                   iRow += 1
               (loopCity, iter) = pTargetPlayer.nextCity(iter, False)

Here is the handleInput function:
Code:
    def handleInput(self, inputClass):
       print "handleInput: button type: %d, data 1: %d, widget_espionage_select_city: %d" % (inputClass.getButtonType(), inputClass.getData1(), WidgetTypes.WIDGET_ESPIONAGE_SELECT_CITY)
   
       screen = self.getScreen()
       if self.iTargetPlayer > -1:
           if inputClass.getButtonType() == WidgetTypes.WIDGET_ESPIONAGE_SELECT_PLAYER:
               screen.setState("LeaderIcon" + str(self.iTargetPlayer), False)
               self.iTargetPlayer = inputClass.getData1()
               screen.setState("LeaderIcon" + str(self.iTargetPlayer), True)
               self.iTargetCity = -1
               self.updateRightPanel()

           elif inputClass.getFunctionName().startswith("WeightIncrease"):
               self.changeEspionageWeight(inputClass.getData1() - 1, self.iIncrement)

           elif inputClass.getFunctionName().startswith("WeightDecrease"):
               self.changeEspionageWeight(inputClass.getData1() - 1, -self.iIncrement)

       if inputClass.getFunctionName() == "EspionagePlus" or inputClass.getFunctionName() == "EspionageMinus":
           iChange = inputClass.getData2()
           if CyInterface().shiftKey():
               if iChange > 0:
                   iChange = 100
               elif iChange < 0:
                   iChange = -100

           CyMessageControl().sendModNetMessage(lNetworkEvents['CHANGE_COMMERCE_PERCENT'],  self.iActivePlayer, inputClass.getData1(), iChange, -1)

       elif inputClass.getButtonType() == WidgetTypes.WIDGET_ESPIONAGE_SELECT_CITY:
           self.iTargetCity = inputClass.getData1()
           self.updateRightPanel()

       elif inputClass.getButtonType() == WidgetTypes.WIDGET_ESPIONAGE_SELECT_MISSION:
           iSelectedMission = inputClass.getData1()
           if self.iMission == iSelectedMission:
               self.iMission = -1
           else:
               self.iMission = iSelectedMission

           self.updateRightPanel()

       elif inputClass.getButtonType() == WidgetTypes.WIDGET_GO_TO_CITY:
           screen.hideScreen()
           pCity = gc.getPlayer(inputClass.getData1()).getCity(inputClass.getData2())
           CyCamera().JustLookAtPlot(pCity.plot())
           if len(self.CitySpies) > 0:
               pSelectedUnit = self.CitySpies[0]
               for pSpy in self.CitySpies:
                   if not pSpy.hasMoved():
                       pSelectedUnit = pSpy
                       break
               CyInterface().selectUnit(pSelectedUnit, True, True, False)

       elif inputClass.getButtonType() == WidgetTypes.WIDGET_ZOOM_CITY:
           screen.hideScreen()
           CyInterface().selectCity(gc.getPlayer(inputClass.getData1()).getCity(inputClass.getData2()), True)

       if inputClass.getFunctionName() == "WeightIncrementSelect":
           iIndex = screen.getSelectedPullDownID("WeightIncrementSelect")
           self.iIncrement = screen.getPullDownData("WeightIncrementSelect", iIndex)

       elif inputClass.getFunctionName() == "WeightResetButton":
           self.resetEspionageWeights()

       elif inputClass.getFunctionName() == "DebugPlayerSelect":
           iIndex = screen.getSelectedPullDownID("DebugPlayerSelect")
           self.iActivePlayer = screen.getPullDownData("DebugPlayerSelect", iIndex)
           self.iTargetPlayer = -1
           self.iTargetCity = -1
           self.updateLeftPanel()
           self.updateRightPanel()

       return 0

As you can see, I have logged the metadata passed to the function when something is clicked. However, when I click around in the table, this is what I get:
Code:
handleInput: button type: 0, data 1: 0, widget_espionage_select_city: 189

handleInput: button type: 0, data 1: 0, widget_espionage_select_city: 189

handleInput: button type: 0, data 1: 0, widget_espionage_select_city: 189

handleInput: button type: 0, data 1: 0, widget_espionage_select_city: 189

handleInput: button type: 0, data 1: 0, widget_espionage_select_city: 189

handleInput: button type: 0, data 1: 0, widget_espionage_select_city: 189

handleInput: button type: 0, data 1: 0, widget_espionage_select_city: 189

handleInput: button type: 0, data 1: 0, widget_espionage_select_city: 189

handleInput: button type: 0, data 1: 0, widget_espionage_select_city: 189
This suggests that no button type is associated with the rows in the table. However, in setTableText it is clearly set to WidgetTypes.WIDGET_ESPIONAGE_SELECT_CITY, so I'd expect that to be passed as the button type (and also, the data1 field should be set to a city ID, not 0). I included this much information because I am not sure what is going on, because from my understanding everything should work. Am I missing something? The same issue exists in History Rewritten too by the way.
 
The problem is related to BUG. The getWidgetHelp() in WidgetUtil.py (assets/python/BUG in DoC, assets/python/Utilities in HR) enables you to give custom hoover text for screen functions. But somehow, it eats up the input data when it returns an empty string.

By adding the code below to that function, it will return a non-empty string. This fixes the behaviour when clicking in the table, but it also forces you to use a hoover text.

Code:
    elif eWidgetType == WidgetTypes.WIDGET_ESPIONAGE_SELECT_CITY:
        return u"x"
 
That's interesting, maybe there is another mapping in the call chain that uses the hover text for some reason? I'll look into it more.
 
Yeah, that fixed it, thanks. But I would like to find a way that also gets rid of the empty tooltip.
 
Last edited:
Hi! Is there an easy way ( or hard way ) to edit open borders and defensive pacts?
I have noticed that you can pass through yours vassal terrain ( without open borders ) , and i want to do the same in defensive pact.

And if it is possible - open borders leave with only trade character? ( no crossing border )
 
Welcome to the forums! There is no easy way, but there is a hard(er) way depending on your knowledge. It is possible to edit the rules of how diplomatic agreements and territorial access rules work, but you need to be able to compile a new DLL and probably edit the source code in many places.
 
Thank you for respond. Could you show me those places of code which should i edit? Is Microsoft Visual C++ 2010 Express a good program for that purpose?
 
You don't just have to be able to edit the files, you also need to be able to compile them to create a new CvGameCoreDLL file, so it's necessary to start with that. I have a created a step by step guide for it here.
 
Could it be
PHP:
void CvSelectionGroup::autoMission()
{
    FAssert(getOwnerINLINE() != NO_PLAYER);

    if (getNumUnits() > 0)
    {
        if (headMissionQueueNode() != NULL)
        {
            if (!isBusy())
            {
                bool bVisibleHuman = false;
                if (isHuman())
                {
                    for (CLLNode<IDInfo>* pUnitNode = headUnitNode(); pUnitNode != NULL; pUnitNode = nextUnitNode(pUnitNode))
                    {
                        CvUnit* pLoopUnit = ::getUnit(pUnitNode->m_data);
                        if (!pLoopUnit->alwaysInvisible())
                        {
                            bVisibleHuman = true;
                            break;
                        }
                    }
                }

                if (bVisibleHuman && GET_PLAYER(getOwnerINLINE()).AI_getPlotDanger(plot(), 1) > 0)
                {
                    clearMissionQueue();
                }
                else
                {
                    if (getActivityType() == ACTIVITY_MISSION)
                    {
                        continueMission();
                    }
                    else
                    {
                        startMission();
                    }
                }
            }
        }
    }

    doDelayedDeath();
}


if (bVisibleHuman && GET_PLAYER(getOwnerINLINE()).AI_getPlotDanger(plot(), 1) > 0)
Another question around the AI of waking up units building improvements. The code above only affects human units, but aren't AI workers also canceling their improvement building when they are threatened by an enemy unit moving close to them? I've looked into CvUnitAI::AI_update() and it is only called if the unit is not currently performing a mission (including building), so I assume this has to be the case. But where does it happpen?

For a bit of context, I have given a combat unit the ability to build roads, but they are not woken up by nearby enemies, which I assume is because they can theoretically defend themselves. I would still like to wake them up because they should reconsider their purpose if enemies are nearby and start defending cities or attacking enemies.
 
[...] The code above only affects human units, but aren't AI workers also canceling their improvement building when they are threatened by an enemy unit moving close to them? I've looked into CvUnitAI::AI_update() and it is only called if the unit is not currently performing a mission (including building), so I assume this has to be the case. But where does it happpen?
That should be in CvSelectionGroup::doTurn:
Code:
if(AI_isControlled()) {
  if(getActivityType() != ACTIVITY_MISSION ||
      (!canFight() && GET_PLAYER(getOwner()).AI_getPlotDanger(plot(), 2) > 0))
    setForceUpdate(true);
}
You could try checking if getMissionType(0) equals MISSION_ROUTE_TO or MISSION_BUILD.
 
Ah, nice. Thanks a lot!
 
How I can check if events have text in them?

For example sometimes event appears but has missing text - sometimes main part and sometimes options in them.
blablabla

Option 1: blablabla
Option 2: blablabla
.......
Option X: TXT_KEY missing.

It seems like only few events suffers from this in huge mod that is Caveman2Cosmos.
 
You mean find text keys that aren't defined yet? I think event infos are exposed to Python, I suggest you go into the Python console and do something like (note: I didn't test this):
Code:
for i in range(gc.getNumEventInfos()): print gc.getEventInfo(i).getText()
And then see which undefined text keys are shown (EventInfo are the event options while EventTriggerInfos are the events themselves, in case you need both).
 
You mean find text keys that aren't defined yet? I think event infos are exposed to Python, I suggest you go into the Python console and do something like (note: I didn't test this):
Code:
for i in range(gc.getNumEventInfos()): print gc.getEventInfo(i).getText()
And then see which undefined text keys are shown (EventInfo are the event options while EventTriggerInfos are the events themselves, in case you need both).
Yep, it worked with "shift+~" to enter python console.

It doesn't work with events themselves that is:
Code:
for i in range(gc.getNumEventTriggerInfos()): print gc.getEventTriggerInfo(i).getText()
That is it only displays single event and that's all.
 
Last edited:
Are you sure? If you have logging enabled, the output should also show up in PythonDbg.log. Or something is different in C2C, it works in my mod at least.
 
Are you sure? If you have logging enabled, the output should also show up in PythonDbg.log. Or something is different in C2C, it works in my mod at least.
Yeah first one displayed all entries and second one displayed only one entry.
 
How does iTotalCultureRatio in Civ4VictoryInfo work?

In CvGame.cpp, it seems to indicate that it works if you have more culture than a specific threshold, based off of iTotalCultureRatio and how much culture other civs have. But CvVictoryScreen.py seems to be written to require you to get X times the best other player's culture.


Spoiler CvGame.cpp :
Code:
if (GC.getVictoryInfo(eVictory).getTotalCultureRatio() > 0)
        {
            int iThreshold = ((GET_TEAM(eTeam).countTotalCulture() * 100) / GC.getVictoryInfo(eVictory).getTotalCultureRatio());

            bool bFound = false;

            for (int iK = 0; iK < MAX_CIV_TEAMS; iK++)
            {
                if (GET_TEAM((TeamTypes)iK).isAlive())
                {
                    if (iK != eTeam)
                    {
                        if (GET_TEAM((TeamTypes)iK).countTotalCulture() > iThreshold)
                        {
                            bFound = true;
                            break;
                        }
                    }
                }
            }

            if (bFound)
            {
                bValid = false;
            }
        }

I'm not quite sure what's going on here.
The threshold is based on a given team's culture, but which given team? How does this victory condition trigger, and what's it checking against?
 
[...] CvVictoryScreen.py seems to be written to require you to get X times the best other player's culture.
That's what I'm reading in the C++ code. Let's say iTotalCultureRatio in Civ4VictoryInfos.xml is 500. Then eTeam has not won the game (bValid=false) so long as any other team has a greater total city culture than eTeam's total city culture divided by 5 (bFound=true). In other words, eTeam wins once its total city culture is at least 5 times the total city culture of every other team. (The two booleans are really superfluous – might as well return false right away instead of setting bFound=true.) I'm not sure if this had been intended as a standalone victory condition or perhaps an extra condition for the 3-city culture victory.
On the victory screen I get e.g. "7944% of culture
firpo: 20693 Suryavarman: 39724"
The 7944 is Sury's culture divided by 5. I don't see how that's helpful. Someone needs to have that much culture to prevent Sury's victory? But then it shouldn't be presented as a percentage. Instead, I guess it should show Sury's culture divided by the next best, which would not be me but Elizabeth, who has about 24000, so 166%. Or that divided by the target ratio. That would be 33%.
 
Back
Top Bottom