I know of modders that use Visual Studio 2017 (or maybe even newer versions).As far as compiling the DLL goes the hardest part is getting your hands on an ancient copy of visual studio.
I thought so too, but after finally taking the time to investigate that is where I ended up. In this part:I don't really see what could go wrong in CvPlayerAI::AI_unitUpdate itself.
while (tempGroupCycle.getLength() > 0)
{
pCurrUnitNode = tempGroupCycle.head();
while (pCurrUnitNode != NULL)
{
pLoopSelectionGroup = getSelectionGroup(pCurrUnitNode->m_data);
FAssertMsg(pLoopSelectionGroup != NULL, "selection group node with NULL selection group");
if (AI_movementPriority(pLoopSelectionGroup) <= iValue)
{
finalGroupCycle.insertAtEnd(pCurrUnitNode->m_data);
pCurrUnitNode = tempGroupCycle.deleteNode(pCurrUnitNode);
if (pLoopSelectionGroup->getID() == 1499136)
{
iNodeID = pCurrUnitNode->m_data;
log("inserted into list");
}
}
else
{
pCurrUnitNode = tempGroupCycle.next(pCurrUnitNode);
}
}
iValue++;
}
pCurrUnitNode = finalGroupCycle.head();
while (pCurrUnitNode != NULL)
{
if (pCurrUnitNode->m_data == iNodeID)
{
log("node ID found");
}
pLoopSelectionGroup = getSelectionGroup(pCurrUnitNode->m_data);
if (pLoopSelectionGroup->getID() == 1499136)
{
log("player update group AI");
}
if (NULL != pLoopSelectionGroup) // group might have been killed by a previous group update
{
if (pLoopSelectionGroup->AI_update())
{
break; // pointers could become invalid...
}
}
pCurrUnitNode = finalGroupCycle.next(pCurrUnitNode);
}
## The Great Bath Start ##
pPlayer = gc.getPlayer(iPlayer)
if pPlayer.getBuildingClassCount(gc.getInfoTypeForString("BUILDINGCLASS_GREAT_BATH")) == 1:
obsoleteTech = gc.getBuildingInfo(gc.getInfoTypeForString("BUILDINGCLASS_GREAT_BATH")).getObsoleteTech()
if gc.getTeam(pPlayer.getTeam()).isHasTech(obsoleteTech) == false or obsoleteTech == -1:
pCity.changeFood(pCity.growthThreshold() /10)
## The Great Bath End ##
## The Great Bath Start ##
pPlayer = gc.getPlayer(iPlayer)
if pPlayer.getBuildingClassCount(gc.getInfoTypeForString("BUILDINGCLASS_GREAT_BATH")) == 1:
#obsoleteTech = gc.getBuildingInfo(gc.getInfoTypeForString("BUILDINGCLASS_GREAT_BATH")).getObsoleteTech()
#if gc.getTeam(pPlayer.getTeam()).isHasTech(obsoleteTech) == false or obsoleteTech == -1:
pCity.changeFood(pCity.growthThreshold() /10)
## The Great Bath End ##
Just for fun I tried that and I still get no effect. As in I don't get crashes but even if I change true to false (which should make all techs unresearchable) nothing happens.May need to enable the respective Python callbacks in PythonCallbackDefines.xml. Vanished interface: Likely a Python exception, stack trace should be in PythonErr.log if logging is enabled in CivilizationIV.ini (can also get a popup through HidePythonExceptions=0). But imo implementing this rule change (partly) in Python only complicates matters, considering that you'll mod the DLL in any case.
I've just tried it with a BUG-based mod and also ran into some trouble. BUG overrides CvGameUtils: CvGameInterfaceFile.py#L11Just for fun I tried that and I still get no effect. As in I don't get crashes but even if I change true to false (which should make all techs unresearchable) nothing happens.
self._setDefault("cannotResearch", False)
Perhaps a typo somewhere? Like not obsoleteTech but ObsoleteTech? Just guessing.Yesterday, I began having strange errors in onCityGrowth. I have found out exactly what causes it, but I have no idea why, or how to properly fix it. For years, I have had the Great Bath wonder in my game, which uses the following code in CvEventManager.py:
XML:## The Great Bath Start ## pPlayer = gc.getPlayer(iPlayer) if pPlayer.getBuildingClassCount(gc.getInfoTypeForString("BUILDINGCLASS_GREAT_BATH")) == 1: obsoleteTech = gc.getBuildingInfo(gc.getInfoTypeForString("BUILDINGCLASS_GREAT_BATH")).getObsoleteTech() if gc.getTeam(pPlayer.getTeam()).isHasTech(obsoleteTech) == false or obsoleteTech == -1: pCity.changeFood(pCity.growthThreshold() /10) ## The Great Bath End ##
I.e., on city growth, it maintains 10% of a city's food. This has worked for years, but now, once a civilisation builds the Great Bath, and once a city of said civilisation grows, I get an error pointing at line 1224 in CvEventManager.py, which is the line of 'obsoleteTech = ...':
View attachment 641308
View attachment 641309
View attachment 641310
Fiddling around with <ObsoleteTech> in the XML file doesn't matter; all my wonders have <ObsoleteTech>NONE</ObsoleteTech>, but changing this to <ObsoleteTech/> or even <ObsoleteTech>TECH_RIFLING</ObsoleteTech> (a random choice) doesn't matter.
However, if I comment out the obsolete tech part, like below, it magically works fine again:
XML:## The Great Bath Start ## pPlayer = gc.getPlayer(iPlayer) if pPlayer.getBuildingClassCount(gc.getInfoTypeForString("BUILDINGCLASS_GREAT_BATH")) == 1: #obsoleteTech = gc.getBuildingInfo(gc.getInfoTypeForString("BUILDINGCLASS_GREAT_BATH")).getObsoleteTech() #if gc.getTeam(pPlayer.getTeam()).isHasTech(obsoleteTech) == false or obsoleteTech == -1: pCity.changeFood(pCity.growthThreshold() /10) ## The Great Bath End ##
As far as I can see, the obsolete tech part should work. And it has worked, for years.
The only thing I changed recently, was adding seven new civilisations, none of which use anything but ordinary XML code.
The last thing I did involving Python and/or callbacks, was setting USE_CANNOT_FOUND_CITY_CALLBACK to 1 or 0 (you can find this in my post history in this very thread - again, this was months or a year ago or so).
Does anyone have any idea what is going on here?
Thanks. I just want a quick and dirty way to make a couple techs completely impossible to research.I've just tried it with a BUG-based mod and also ran into some trouble. BUG overrides CvGameUtils: CvGameInterfaceFile.py#L11
BugGameUtils#L364 may still call the CvGameUtils module, but, it turns out that it won't do so when a default return value is set in BugGameUtils.py for the given callback function. If I comment out the line
(and enable USE_CANNOT_RESEARCH_CALLBACK in XML), then returning True from the cannotResearch function in CvGameUtils does make all tech unresearchable. I don't know if that's how it's supposed to be done. There's documentation at the start of BugGameUtils.py, but I've never had the patience to study that. Because I don't think DLL modders should make use of Python callbacks anyway. Elegant as the BUG software design may be, I think it's a bit of a failure (as far as the handling of calls from the DLL is concerned) because it's too complex to understand for those modders that need to rely on it.self._setDefault("cannotResearch", False)
Edit: Case in point, the PIE modders ran into the same issue with BUG. (The request to "explain [...] the BUG python hook system" remained unanswered.)
But 'obsoleteTech' is a variable that is declared right there, used only there, and could as well have been 'heylookatmyprettyname' or anything you'd like. Right?Perhaps a typo somewhere? Like not obsoleteTech but ObsoleteTech? Just guessing.
Just based on looking at the code: The second loop terminates when CvSelectionGroupAI::AI_update returns true. That happens when the group "becomes busy," a comment says, but, actually, it also returns true when a group dies or splits during an attack. CvPlayerAI::AI_unitUpdate then gets another call from CvGame::update via CvGame::updateMoves – unless m_bAutoMoves has been set for the current player, which should happen when all units have spent their moves (or died) and none are waiting for combat or mission animations to finish:I only hit "inserted into list" in the first loop, but none of the breakpoints in the second loop. I also checked that the second loop gets iterated at all, and it does.
if (!kPlayer.isAutoMoves())
{
kPlayer.AI_unitUpdate();
if (!kPlayer.isHuman() &&
!kPlayer.hasBusyUnit() && !kPlayer.hasReadyUnit(true))
{
kPlayer.setAutoMoves(true);
}
}
return true
line in CvUnit::canMove keeps getting reached during the infinite loop. That function should get called by kPlayer.hasReadyUnit(true)
. Maybe you've already checked - and maybe that's how you've figured out that group ID. Well, in that case, the group clearly isn't getting killed. CvBuildingInfo* CyGlobalContext::getBuildingInfo(int i) const
{
return (i>=0 && i<GC.getNumBuildingInfos()) ? &GC.getInfo((BuildingTypes)i) : NULL;
}
Oh true, thanks. If AI_update() returns true for any selection group it breaks the loop - my mind somehow made the incorrect assumption that it's going to abort for the current selection group only (i.e. essentially assumed it's "continue"). That's definitely worth investigating. Maybe the issue is with another group that reports updating but not properly takes care of itself, like you mention, which then keeps repeating without giving the group I am looking at a chance to take its turn.Just based on looking at the code: The second loop terminates when CvSelectionGroupAI::AI_update returns true. That happens when the group "becomes busy," a comment says, but, actually, it also returns true when a group dies or splits during an attack. CvPlayerAI::AI_unitUpdate then gets another call from CvGame::update via CvGame::updateMoves – unless m_bAutoMoves has been set for the current player, which should happen when all units have spent their moves (or died) and none are waiting for combat or mission animations to finish:So I guess you're getting only one call to CvPlayerAI::AI_unitUpdate that inserts 1499136 into the group cycle. And then the cycle isn't fully processed (could verify that by inserting a breakpoint at the break statement once the inserted-into-list line has been hit), and, during the next CvPlayerAI::AI_unitUpdate call (is there one?) ... the group no longer exists, apparently. Might be worth checking if the ID comes up in CvPlayer::deleteSelectionGroup. But that should not cause an infinite loop – if some group absorbs 1499136 (not sure if that can happen), then, fine, group 1499136 no longer needs to be moved.Spoiler :(This is slightly edited BtS code from CvGame::updateMoves; I haven't checked whether it's any different in DoC.)Code:if (!kPlayer.isAutoMoves()) { kPlayer.AI_unitUpdate(); if (!kPlayer.isHuman() && !kPlayer.hasBusyUnit() && !kPlayer.hasReadyUnit(true)) { kPlayer.setAutoMoves(true); } }
Looking at the code above, I wonder if thereturn true
line in CvUnit::canMove keeps getting reached during the infinite loop. That function should get called bykPlayer.hasReadyUnit(true)
. Maybe you've already checked - and maybe that's how you've figured out that group ID. Well, in that case, the group clearly isn't getting killed.![]()
Yep, simply changing the buildingclass to the building worked, thank you very much!@need my speed: You're calling getBuildingInfo with a BuildingClass ID as the argument. You need a Building ID. So, I guess, simply using "BUILDING_GREAT_BATH" instead of "BUILDINGCLASS_GREAT_BATH" should fix the problem, assuming that the Great Bath building has that type string. I'm guessing that the BuildingClass ID of the Great Bath exceeds the range of Building IDs, and then the DLL will return NULL, which, I guess, becomes None in Python:So, if you've added more building classes recently, that could explain why the mixup is no longer going unnoticed. Although it's strange that there should be fewer buildings than building classes. So maybe this explanation isn't quite right.Code:CvBuildingInfo* CyGlobalContext::getBuildingInfo(int i) const { return (i>=0 && i<GC.getNumBuildingInfos()) ? &GC.getInfo((BuildingTypes)i) : NULL; }
Oh! Thank you!
I was looking in the wrong file, then.
No wonder I was getting frustrated.
Here is the proof it worked!
View attachment 546389
I've attached the two files as zip-files. [Python\Screens\CvMainInterface] and [Python\BUG\Scoreboard].
All of my changes are marked with #kjotleik, so a search should be easy.
Thanks again. I would probably have spent way too long on this on my own.
I have not tested this in a full game, yet. So things may happen. But at turn zero, it looks good.
# Increase Specialists...
i = 0
for i in range( gc.getNumSpecialistInfos() ):
if (gc.getSpecialistInfo(i).isVisible()):
szName = "IncreaseSpecialist" + str(i)
screen.setButtonGFC( szName, u"", "", xResolution - 46, (yResolution - 450 - (26 * iCount)), 20, 20, WidgetTypes.WIDGET_CHANGE_SPECIALIST, i, 1, ButtonStyles.BUTTON_STYLE_CITY_PLUS )
screen.hide( szName )
iCount = iCount + 1
iCount = 0
# Decrease specialists
i = 0
for i in range( gc.getNumSpecialistInfos() ):
if (gc.getSpecialistInfo(i).isVisible()):
szName = "DecreaseSpecialist" + str(i)
screen.setButtonGFC( szName, u"", "", xResolution - 24, (yResolution - 450 - (26 * iCount)), 20, 20, WidgetTypes.WIDGET_CHANGE_SPECIALIST, i, -1, ButtonStyles.BUTTON_STYLE_CITY_MINUS )
screen.hide( szName )
# Citizen Buttons
i = 0
for i in range( gc.getNumSpecialistInfos() ):
if (gc.getSpecialistInfo(i).isVisible()):
szName = "CitizenDisabledButton" + str(i)
screen.setImageButton( szName, gc.getSpecialistInfo(i).getTexture(), xResolution - 74, (yResolution - 452 - (26 * i)), 24, 24, WidgetTypes.WIDGET_DISABLED_CITIZEN, i, -1 )
screen.enable( szName, False )
screen.hide( szName )
for j in range(MAX_CITIZEN_BUTTONS):
szName = "CitizenButton" + str((i * 100) + j)
screen.addCheckBoxGFC( szName, gc.getSpecialistInfo(i).getTexture(), "", xResolution - 74 - (26 * j), (yResolution - 452 - (26 * i)), 24, 24, WidgetTypes.WIDGET_CITIZEN, i, j, ButtonStyles.BUTTON_STYLE_LABEL )
screen.hide( szName )
for j in range( iCount ):
bHandled = True
szName = "CitizenButton" + str((i * 100) + j)
screen.addCheckBoxGFC( szName, gc.getSpecialistInfo(i).getTexture(), "", xResolution - 74 - (26 * j), (yResolution - 452 - (26 * i)), 24, 24, WidgetTypes.WIDGET_CITIZEN, i, j, ButtonStyles.BUTTON_STYLE_LABEL )
screen.show( szName )
szName = "CitizenButtonHighlight" + str((i * 100) + j)
screen.addDDSGFC( szName, ArtFileMgr.getInterfaceArtInfo("BUTTON_HILITE_SQUARE").getPath(), xResolution - 74 - (26 * j), (yResolution - 452 - (26 * i)), 24, 24, WidgetTypes.WIDGET_CITIZEN, i, j )
if ( pHeadSelectedCity.getForceSpecialistCount(i) > j ):
screen.show( szName )
else:
screen.hide( szName )
while (iCount > 0):
bHandled = True
xCoord = xResolution - 74 - (26 * j)
yCoord = yResolution - 452 - (26 * i)
Don’t think there’s a direct xml edit to achieve that.Can techs add bonus strength to units? As in tech X adds 5 points of damage to tanks or something. I am asking specifically about the unmodified game. I know I can mod it in but don't want to.
Maybe the resource list (or some invisible or transparent container around it) is overlapping the buttons and intercepts clicks? But I guess, if the specialists can be moused over (displaying hover text), then that can't be the issue. There's a lot of code in CvMainInterface.py for dealing with specialists because BUG adds two alternative display methods – "Chevron" and "Stacker" – with a lot of redundant code between those two and the original display method. Looks like you use the original one and have changed only the non-BUG code – which should suffice.Edit: Looks like I spoke too soon. The specialist section on the city screen needs to be adjusted. However, after doing so the increase and decrease buttons do not work. I have run out of ideas since all y values that corrospond with those buttons have been changed going by both the edit in this thread, as well as the default settings. [...] No idea why the increase and decrease buttons for the specialists are unclickable. The only way to have it work is to have all the specialist values for the city screen set back to BUG's default and accept the overlap with the minimap. If I tackle this issue again and find a solution I shall post it here. If someone with more knowledge of how the interface is coded could point me in the right direction I would also greatly appreciate that!
Yes, hover effect works. I'll just have to try tackle the issue again at a later date, but I ran out of ideas this round. As you say, the hover effect means that its not 'under' some other element, but I haven't found anything related to a z-index, so I'll start with that approach next time I get to this and report back when I have found a solution.Maybe the resource list (or some invisible or transparent container around it) is overlapping the buttons and intercepts clicks? But I guess, if the specialists can be moused over (displaying hover text), then that can't be the issue. There's a lot of code in CvMainInterface.py for dealing with specialists because BUG adds two alternative display methods – "Chevron" and "Stacker" – with a lot of redundant code between those two and the original display method. Looks like you use the original one and have changed only the non-BUG code – which should suffice.