1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

[SDK] Problem with enabling interface buttons after SDK change

Discussion in 'Civ4 - SDK/Python' started by Zaragon, Jan 18, 2018.

  1. Zaragon

    Zaragon Chieftain

    Joined:
    Sep 4, 2013
    Messages:
    3
    Hi all,

    I've done a lot of SDK modding (I'm far more comfortable with C++ than I am with application-level python code). I've recently made an SDK change that seems to work, but the interface buttons don't seem to have gotten the message.

    I'm trying to modify the way corporation interact with the Mercantilism Civc (specifically, the part that sets the player so CvPlayer::isNoForeignCorporations() returns True). I'd like it so that players running Mercantilism can still have corporations spread to them by teammates (but still prohibit foreign corporations. I've modified CvPlayer::isActiveCorporation to make that work.

    Original code:
    Code:
    bool CvPlayer::isActiveCorporation(CorporationTypes eIndex) const
    {
        if (isNoCorporations())
        {
            return false;
        }
    
        if (isNoForeignCorporations() && !hasHeadquarters(eIndex))
        {
            return false;
        }
    
        return true;
    }
    
    To change the behavior, I first set a define in GlobalDefines.xml, FOUND_CORPORATION_CITY_TEAM, which controls it (set to 1 for the new behavior and to 0 for the original behavior). The new function uses CvTeam::hasHeadquarters() instead of CvPlayer::hasHeadquearters():
    Code:
    
    bool CvPlayer::isActiveCorporation(CorporationTypes eIndex) const
    {
       if (isNoCorporations())
       {
           return false;
       }
    
       if (isNoForeignCorporations())
       {
           if (GC.getDefineINT("FOUND_CORPORATION_CITY_TEAM") > 0)
           {
               if (!GET_TEAM(getTeam()).hasHeadquarters(eIndex))
               {
                   return false;
               }
           }
           else
           {
               if (!hasHeadquarters(eIndex))
               {
                   return false;
               }
           }
       }
    
       return true;
    }
    
    This works, partly. Team corporations no longer get disabled when you switch to Mercantilism, while Foreign corporations still do. If your team has an AI on it, it can spread corporations that it has the headquarters for to you. And your executives will spread team corporations if you set them on automatic spread. And on the city screen, it disables any benefit from true foreign corporations (hammers/food/culture) as well as disabling the ability to build their executives, while allowing team corporations to work normally.

    My problem is the button for the executive is still being enabled/disabled based on the old logic, and I haven't the slightest idea where that is. If you hover over the button, you no longer get the message that you can't spread in the other civ's lands, but it remains dimmed out and unclickable.

    Commenting out these lines in CvInterfaceManager.py causes the button to stop dimming out, which lead me to look into canHandleAction(), but I can't make heads or tails of how it works.
    Code:
                           if ( not CyInterface().canHandleAction(i, False) ):
                               screen.disableMultiListButton( "BottomButtonContainer", 0, iCount, gc.getActionInfo(i).getButton() )
    
    Can anyone out there who has a better understanding of how the Python and SDK code are intertwined help me out, or at least give me a suggestion as to where to look? I've been stuck on this for a couple of days. I can usually modify the standalone Python but this seens messy.
     
  2. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,226
    I thought this would be piece of cake considering my experience with the SDK. However it turns out that understanding CvGame::canHandleAction is quite confusing and close to a worst case scenario for reading the source code. First step is to realize that ActionInfo is not from an xml file. Instead it's created at startup based on context from other files. This takes place in XcXMLLoadUtilitySet.xml. Each CvCorporationInfo (from xml) is converted into an ActionInfo with mission type MISSION_SPREAD_CORPORATION.

    It looks like it goes CvGame::canHandleAction->CvSelectionGroup::canStartMission() and then for each unit in the group it calls CvUnit::canSpreadCorporation(). It seems like you need to review the last one of that chain and see if you can figure out what goes on. I find the bTestVisible part weird. Either you can spread or you can't. It shouldn't be allowed in GUI, but not the action or vice versa.

    Another thing:
    PHP:
    if (GC.getDefineINT("FOUND_CORPORATION_CITY_TEAM") > 0)
    GC.getDefineINT is rather slow. It uses an implementation of a hash table, which was completely replaced more than 10 years ago due to performance issues. We can't really replace the implementation, but we can avoid the calls to getDefineINT. The easiest way is this:
    PHP:
    static const bool bFOUND_CORPORATION_CITY_TEAM GC.getDefineINT("FOUND_CORPORATION_CITY_TEAM"); // first line in function
    if (bFOUND_CORPORATION_CITY_TEAM)
    This way bFOUND_CORPORATION_CITY_TEAM will be set the first time the function is called and since it's static, the bool will be kept between calls. It's the easiest way to implement a cache and it works because the xml values will not change during runtime.
     
  3. Zaragon

    Zaragon Chieftain

    Joined:
    Sep 4, 2013
    Messages:
    3
    Nightingale, thanks for the tip on GC.getDefineINT. I've got a few other places I've used that so I'll implement your suggestion there as well.

    Regarding the corporation spread issue, I've inspected the code in CvUnit::canSpreadCorporation() by attaching the debugger to the running process. canSpreadCorporation() ultimately calls CvPlayer::isActiveCorporation(), like so:

    Code:
        if (NO_CORPORATION == eCorporation)
       {
           return false;
       }
    
       if (!GET_PLAYER(getOwnerINLINE()).isActiveCorporation(eCorporation))
       {
           return false;
       }
    
       if (m_pUnitInfo->getCorporationSpreads(eCorporation) <= 0)
       {
           return false;
       }
    
    I've triggered the watchpoint in that function by selecting the unit, and then stepped through the resulting calls. It looks as though CvUnit::canSpreadCorporation() is called 7 times, with eCorporation get incremented once for each corporation. Six of them return false on the third condition in the above list. Whichever corporation the unit i'm selecting CAN spread does the isActiveCorporation(eCorporation) test, then falls through the rest of the code to return true at the very end (it skips all of the code wrapped in the bVisible check). Examining the calling stack shows that it's being called with bVisible set to true. Here's the calling stack (eCorporation in this case is the corp I'm testing, Creative Constructions):

    Code:
    CvGameCoreDLL.dll!CvUnit::canSpreadCorporation(const CvPlot * pPlot=0x5467454c, CorporationTypes eCorporation=3, bool bTestVisible=true)  Line 6037   C++
    CvGameCoreDLL.dll!CvSelectionGroup::canStartMission(int iMission=24, int iData1=3, int iData2=-1, CvPlot * pPlot=0x5467454c, bool bTestVisible=true, bool bUseCache=true)  Line 1118 + 0x14 bytes   C++
    CvGameCoreDLL.dll!CvGame::canHandleAction(int iAction=274, CvPlot * pPlot=0x00000000, bool bTestVisible=true, bool bUseCache=true)  Line 1520 + 0x49 bytes   C++
    
    iMission 24 is definitely MISSION_SPREAD_CORPORATION. I don't know what the iData1 and iData2 values are.

    It really seems like it should work, but it isn't. I feel like I'm missing something, but I don't know what it is.
     
  4. Zaragon

    Zaragon Chieftain

    Joined:
    Sep 4, 2013
    Messages:
    3
    I really thought I'd posted up here when I got it working, but apparently not. It turned out that something was corrupted--and now it's been almost 2 years and I don't remember what it was for sure, but I think it might have been the saved game I was using to test it.

    The final code which worked was:
    Code:
    bool CvPlayer::isActiveCorporation(CorporationTypes eIndex) const
    {
    /* Cache GC.getDefineINT() */
       static const bool bFOUND_CORPORATION_CITY_TEAM = GC.getDefineINT("FOUND_CORPORATION_CITY_TEAM");
    
       if (isNoCorporations())
       {
           return false;
       }
    
       if (isNoForeignCorporations())
       {
           if (bFOUND_CORPORATION_CITY_TEAM)
           {
               if (!GET_TEAM(getTeam()).hasHeadquarters(eIndex))
               {
                   return false;
               }
           }
           else
           {
               if (!hasHeadquarters(eIndex))
               {
                   return false;
               }
           }
       }
    
       return true;
    }
    
    It's almost identical to the code I originally posted. Turns out it really should have worked.
     

Share This Page