I want to help this mod

Hi, I write C++ and Python. I am willing to (attempt to) help with optimization for both of these, and maybe other coding tasks as required. I actually already did submit a tiny optimization patch that I think Thunderbird is testing. I work in the games industry and have been writing C++ for >20 years and Python for a couple. I'm not sure what the experience level of other C++ coders on this project is, but if useful I can help with code review, design advice, debugging etc.

That's really good! I am going to learn a lot of skills from you too. As a novice to everything, I have never touched the computation/AI part of this mod since I joined 2 months ago, and you showed a good example to single out the time-consuming processes through a profiler. I'll need to learn this method to have a quick overview on what the game is doing too, but that's too much for me to chew now, at this stage. :lol:
 
Greetings. GreatCoffers is my git name. I mostly modded techs, specifically their names, but I have many ideas for possible techs. I have them typed out, but I have yet to implement them in any sort of way. I did do buildings once, so I have that going for me. I'm mostly a brainstormer, designer, and writer.

My post that consolidates my ideas
 
Code:
    iTotalSupport += getDefenderFirstFrontSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondFrontSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderFirstShortRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondShortRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderFirstShortRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondShortRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderFirstMediumRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondMediumRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderFirstLongRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondLongRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderFirstFlankSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondFlankSupportValue(pAttacker, pPlot);
 
Code:
    iTotalSupport += getDefenderFirstFrontSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondFrontSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderFirstShortRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondShortRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderFirstShortRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondShortRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderFirstMediumRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondMediumRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderFirstLongRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondLongRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderFirstFlankSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondFlankSupportValue(pAttacker, pPlot);
All part of the work in progress option for strength in numbers. or maybe combined arms...

Question?
 
a repeat bit in there man. I don't think it was intentional.
its even had to see in those dozen lines. ha

maybe questions later. I go look around some more.

Code:
   iTotalSupport += getDefenderFirstFrontSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondFrontSupportValue(pAttacker, pPlot);

    iTotalSupport += getDefenderFirstShortRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondShortRangeSupportValue(pAttacker, pPlot);

    iTotalSupport += getDefenderFirstShortRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondShortRangeSupportValue(pAttacker, pPlot);

    iTotalSupport += getDefenderFirstMediumRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondMediumRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderFirstLongRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondLongRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderFirstFlankSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondFlankSupportValue(pAttacker, pPlot);
 
a repeat bit in there man. I don't think it was intentional.
its even had to see in those dozen lines. ha

maybe questions later. I go look around some more.

Code:
   iTotalSupport += getDefenderFirstFrontSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondFrontSupportValue(pAttacker, pPlot);

    iTotalSupport += getDefenderFirstShortRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondShortRangeSupportValue(pAttacker, pPlot);

    iTotalSupport += getDefenderFirstShortRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondShortRangeSupportValue(pAttacker, pPlot);

    iTotalSupport += getDefenderFirstMediumRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondMediumRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderFirstLongRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondLongRangeSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderFirstFlankSupportValue(pAttacker, pPlot);
    iTotalSupport += getDefenderSecondFlankSupportValue(pAttacker, pPlot);
I see. 2 Short Range Support sums being added in. Got it. Good catch!
 
From Github:
Often only some resource labels are shown #146

Here's a few things from http://civ4bug.sourceforge.net/PythonAPI/ that may be useful
Theres a set at the very bottom.
Spoiler :
Code:
class CyGlobeLayer:

    def getButtonStyle(self):
        """ STRING () """
        return CyGlobeLayer().getButtonStyle()

    def getCurrentOption(self):
        """ INT () """
        return CyGlobeLayer().getCurrentOption()

    def getName(self):
        """ STRING () """
        return CyGlobeLayer().getName()

    def getNumOptions(self):
        """ INT () """
        return CyGlobeLayer().getNumLayers()

    def getOptionButton(self, iOptionID):
        """ STRING (INT) """
        return CyGlobeLayer().getOptionButton(iOptionID)

    def getOptionName(self, iOptionID):
        """ STRING (INT) """
        return CyGlobeLayer().getOptionName(iOptionID)

    def isGlobeviewRequired(self):
        """ BOOL () """
        return CyGlobeLayer().isGlobeviewRequired()

    def isNone(self):
        """ BOOL () """
        return CyGlobeLayer().isNone()

    def needsPlayerFilter(self):
        """ BOOL () """
        return CyGlobeLayer().needsPlayerFilter()

    def registerGlobeLayer(self):
        """ VOID () """
        CyGlobeLayer().registerGlobeLayer()

    def shouldCitiesZoom(self):
        """ BOOL () """
        return CyGlobeLayer().shouldCitiesZoom()


class CyGlobeLayerManager:

    def getCurrentLayer(self):
        """ CyGlobeLayer OBJ () """
        return CyGlobeLayerManager().getCurrentLayer()

    def getCurrentLayerID(self):
        """ INT () """
        return CyGlobeLayerManager().getCurrentLayerID()

    def getCurrentLayerName(self):
        """ STRING () """
        return CyGlobeLayerManager().getCurrentLayerName()

    def getLayer(self, i):
        """ CyGlobeLayer OBJ () """
        return CyGlobeLayerManager().getLayer(i)

    def getLayerID(self, layer):
        """ INT (STRING) """
        return CyGlobeLayerManager().getLayerID(layer)

    def getNumLayers(self):
        """ INT () """
        return CyGlobeLayerManager().getNumLayers()

    def setCurrentLayer(self):
        """ VOID () """
        CyGlobeLayerManager().setCurrentLayer()

No unit is selected on new game start #32

I added between isHuman(): and else:
I put the code there (CvEventManager.py line 636) to share a few bits but then it's behind that bPrehistoricStart... So it might be better to make a separate loop. idk
Spoiler :

Code:
        if bPrehistoricStart:
            for iPlayer in xrange(self.MAX_PC_PLAYERS):
                CyPlayer = GC.getPlayer(iPlayer)
                CyUnit, i = CyPlayer.firstUnit(False)
                if CyPlayer.isHuman():
                    while CyUnit:
                        if CyUnit.isFound():
                            CyUnit.setHasPromotion(self.PROMO_GUARDIAN_TRIBAL, True)
                        CyInterface().insertIntoSelectionList(CyUnit, False, False, False, False)
                        CyUnit, i = CyPlayer.nextUnit(i, False)
                else:
                    while CyUnit:
                        if CyUnit.isFound():
                            CyUnit.setHasPromotion(self.PROMO_GUARDIAN_TRIBAL, True)
                            break
                        CyUnit, i = CyPlayer.nextUnit(i, False)
 
From Github:
No unit is selected on new game start #32

I added between isHuman(): and else:
I put the code there (CvEventManager.py line 636) to share a few bits but then it's behind that bPrehistoricStart... So it might be better to make a separate loop. idk
Spoiler :

Code:
        if bPrehistoricStart:
            for iPlayer in xrange(self.MAX_PC_PLAYERS):
                CyPlayer = GC.getPlayer(iPlayer)
                CyUnit, i = CyPlayer.firstUnit(False)
                if CyPlayer.isHuman():
                    while CyUnit:
                        if CyUnit.isFound():
                            CyUnit.setHasPromotion(self.PROMO_GUARDIAN_TRIBAL, True)
                        CyInterface().insertIntoSelectionList(CyUnit, False, False, False, False)
                        CyUnit, i = CyPlayer.nextUnit(i, False)
                else:
                    while CyUnit:
                        if CyUnit.isFound():
                            CyUnit.setHasPromotion(self.PROMO_GUARDIAN_TRIBAL, True)
                            break
                        CyUnit, i = CyPlayer.nextUnit(i, False)
I feel a dll solution would be more appropriate in this case, more direct
Vanilla didn't use python to select a unit after loading/starting a game, which indicates that our dll probably has a bug that is causing this behaviour and it would be better to fix that bug than to hide it behind a python workaround.
From Github:
Often only some resource labels are shown #146

Here's a few things from http://civ4bug.sourceforge.net/PythonAPI/ that may be useful
Theres a set at the very bottom.
Spoiler :
Code:
class CyGlobeLayer:

    def getButtonStyle(self):
        """ STRING () """
        return CyGlobeLayer().getButtonStyle()

    def getCurrentOption(self):
        """ INT () """
        return CyGlobeLayer().getCurrentOption()

    def getName(self):
        """ STRING () """
        return CyGlobeLayer().getName()

    def getNumOptions(self):
        """ INT () """
        return CyGlobeLayer().getNumLayers()

    def getOptionButton(self, iOptionID):
        """ STRING (INT) """
        return CyGlobeLayer().getOptionButton(iOptionID)

    def getOptionName(self, iOptionID):
        """ STRING (INT) """
        return CyGlobeLayer().getOptionName(iOptionID)

    def isGlobeviewRequired(self):
        """ BOOL () """
        return CyGlobeLayer().isGlobeviewRequired()

    def isNone(self):
        """ BOOL () """
        return CyGlobeLayer().isNone()

    def needsPlayerFilter(self):
        """ BOOL () """
        return CyGlobeLayer().needsPlayerFilter()

    def registerGlobeLayer(self):
        """ VOID () """
        CyGlobeLayer().registerGlobeLayer()

    def shouldCitiesZoom(self):
        """ BOOL () """
        return CyGlobeLayer().shouldCitiesZoom()


class CyGlobeLayerManager:

    def getCurrentLayer(self):
        """ CyGlobeLayer OBJ () """
        return CyGlobeLayerManager().getCurrentLayer()

    def getCurrentLayerID(self):
        """ INT () """
        return CyGlobeLayerManager().getCurrentLayerID()

    def getCurrentLayerName(self):
        """ STRING () """
        return CyGlobeLayerManager().getCurrentLayerName()

    def getLayer(self, i):
        """ CyGlobeLayer OBJ () """
        return CyGlobeLayerManager().getLayer(i)

    def getLayerID(self, layer):
        """ INT (STRING) """
        return CyGlobeLayerManager().getLayerID(layer)

    def getNumLayers(self):
        """ INT () """
        return CyGlobeLayerManager().getNumLayers()

    def setCurrentLayer(self):
        """ VOID () """
        CyGlobeLayerManager().setCurrentLayer()
I'm aware of these functions, unfortunately there is no method for setting what mode/option is selected for each of the different globe layers.
 
Last edited:
It's not a python workaround for a hidden dll bug. The selection list is an engine thing.
Code:
DllInterfacebase.h:
    virtual void selectUnit(CvUnit* pUnit, bool bClear, bool bToggle = false, bool bSound = false) = 0;
    virtual void selectGroup(CvUnit* pUnit, bool bShift, bool bCtrl, bool bAlt) = 0;
    virtual void clearSelectionList() = 0;
    virtual void insertIntoSelectionList(CvUnit* pUnit, bool bClear, bool bToggle, bool bGroup = false, bool bSound = false, bool bMinimalChange = false) = 0;

CyInterface():
82.VOID selectUnit (CyUnit pUnit, BOOL bClear, BOOL bToggle, BOOL bSound)
 void (CyUnit* pUnit, bool bClear, bool bToggle, bool bSound)
78.VOID selectAll (CyPlot pPlot)
 void (CyPlot* pPlot)
    def insertIntoSelectionList (CyUnit pUnit, BOOL bClear, BOOL bToggle, BOOL bGroup, BOOL bSound)
 void (CyUnit* pUnit, bool bClear, bool bToggle, bool bGroup, bool bSound)
 
It's not a python workaround for a hidden dll bug. The selection list is an engine thing.
It is an exe thing, but the dll used to make, or let, the exe do these things on it's own accord without python ordering it.
Correct me if I'm wrong but doesn't vanilla BtS, and didn't C2C once upon a time, auto-select a unit when starting a new game and when loading a save?
It was never done through python, I'm pretty sure about that.

The bug isn't hidden now, as the bug can easily be experienced, and it is probably not a python bug as python never handled this aspect before, but this python workaround will hide it. I don't like fixing bugs indirectly like that.
Adding this new autoselect feature because there is a dll bug that stops a unit from being auto-selected may hide a potentially bigger bug where this behaviour was the only easily visible symptom of something being wrong.
I'm not saying the bug cause more issues than just units not being autoselected, but we don't know that, it could have other, difficult to spot, symptoms that this python workaround will not fix.
I'm just saying it's imo bad practice to fix this bug the way you suggest.

A direct bug fix is one where you can point at the root of the bug and why the fix address it.
An indirect bug fix is when the fix apparently doesn't address the cause of the bug and is therefore pretty much per definition a workaround.
 
Last edited:
Correct me if I'm wrong but doesn't vanilla BtS, and didn't C2C once upon a time, auto-select a unit when starting a new game and when loading a save?
Yes and I have no idea why it doesn't now.
 
Yes and I have no idea why it doesn't now.
Yeah, I think it's best to spend more time figuring out the problem rather than fixing it with a workaround and then forgetting all about the problem that would still be there underneath.

It isn't necessarily a dll issue, could be some python code that confuses the exe into not selecting a unit automatically, if that's the case then I would suspect the BUG mod python code the most as the issue is quite old and because most python code processed early enough to be suspected is the BUG mod python code or unaltered firaxis code. PPIO did change a lot about "early processed" python code, but the problem existed before PPIO, didn't it?
 
there's an identical call that can be made from the dll. Doing it with python wasn't supposed to mean anything. Ill post that again next year if the situation is the same.
 
void CvPlayer::setTurnActive(bool bNewValue, bool bDoTurn)
Might be a good place to look for the bug, specifically the context it is called from when loading or starting a game (I assume it's called in this context).

I think this part is what does the autoselection at the start of each turn, and that it also is supposed to do the same after loading a save or starting a new game.
Comparing the calls to setTurnActive between vanilla and C2C dll may be prudent, perhaps the timing is off in when C2C calls this when loading/starting a game.

if (getID() == GC.getGameINLINE().getActivePlayer())
{
if (gDLL->getInterfaceIFace()->getLengthSelectionList() == 0)
{
gDLL->getInterfaceIFace()->setCycleSelectionCounter(1);​
}
gDLL->getInterfaceIFace()->setDirty(SelectionCamera_DIRTY_BIT, true);​
}
 
Last edited:
Yes but I'm not sure when it started.
Using regenerate map makes the game autoselect a unit due to this line I think
gDLL->getInterfaceIFace()->setCycleSelectionCounter(1); // in CvGame::regenerateMap()

Not all that useful but its a hint nontheless, with VS I'm sure you could get insight at what dll methods are called when loading a save, and then work from that comparing those function with vanilla dll.
Not trying to pressure you to look into this TB, I'm just talking about it in general.
 
Using regenerate map makes the game autoselect a unit due to this line I think
gDLL->getInterfaceIFace()->setCycleSelectionCounter(1); // in CvGame::regenerateMap()

Not all that useful but its a hint nontheless, with VS I'm sure you could get insight at what dll methods are called when loading a save, and then work from that comparing those function with vanilla dll.
Not trying to pressure you to look into this TB, I'm just talking about it in general.
It's good information and interesting for looking into it. If this gets buried because it will be a while before I can look at it, remind me to look into it later. Or start a project on Git.
 
Using regenerate map makes the game autoselect a unit due to this line I think
gDLL->getInterfaceIFace()->setCycleSelectionCounter(1); // in CvGame::regenerateMap()

Not all that useful but its a hint nontheless, with VS I'm sure you could get insight at what dll methods are called when loading a save, and then work from that comparing those function with vanilla dll.
Not trying to pressure you to look into this TB, I'm just talking about it in general.
The problem for me is having any idea as to when this should fire...
 
from Github:
Assert: route cost is 0 #121
Spoiler Github discussion :

alberts2 commented Sep 7, 2019
A zero or negative route movement cost is definitely invalid. Some random events can lead to negative values from getRouteChange.


billw2012 commented Sep 7, 2019
The only way I see to reconcile those two statements is to simply cap the route cost at a minimum of 1. Is that correct?

alberts2 commented Sep 7, 2019
Yes and find out why these random events don't work correctly.

billw2012 commented Sep 7, 2019

Ah I thought you meant the events were intended to do that. If they are not meant to drop getRouteChange below zero, then I shouldn't cap the route cost (thus hiding the problem even though it is still there), but instead leave the assert and fix the underlying bug.

alberts2 commented Sep 7, 2019
For some reason some events get executed multiple times and that leads to things like negative movement costs.


Here's the bts quest that is mentioned
Spoiler :

Code:
######## INTERSTATE ###########

def canTriggerInterstate(argsList):

  kTriggeredData = argsList[0]
  trigger = GC.getEventTriggerInfo(kTriggeredData.eTrigger)
  player = GC.getPlayer(kTriggeredData.ePlayer)

  if not player.isCivic(GC.getInfoTypeForString("CIVIC_LIBERAL")):
    return False

  return True

def getHelpInterstate(argsList):
  iEvent = argsList[0]
  kTriggeredData = argsList[1]

  szHelp = TRNSLTR.getText("TXT_KEY_UNIT_MOVEMENT", (1, GC.getRouteInfo(GC.getInfoTypeForString("ROUTE_MODERN_ROAD")).getTextKey()))

  return szHelp

def applyInterstate(argsList):
  iEvent = argsList[0]
  kTriggeredData = argsList[1]
  player = GC.getPlayer(kTriggeredData.ePlayer)
  team = GC.getTeam(player.getTeam())

  iRoad = GC.getInfoTypeForString("ROUTE_MODERN_ROAD")

  team.changeRouteChange(iRoad, -5)

Looks like there is probably no a bug (last line). This quest just needs a tweak.
 
Top Bottom