Well, I suppose this is a sort of a work in progress. I decided to do some experimenting, and tried to compile(release) a .dll with the pTheirNearestCity initialised, and it still crashes.
Gonna have a break from it for a bit and will upload some data related to the crash later on today.
Edit: With dave uk's dll it doesn't crash, and that's with the initialisation mentioned above,
And the crash with the release DLL is consistently reproducible while the debug DLL never crashes (on that particular turn)? That could be, or maybe it's just crashing randomly with both builds.
And which team is that? Does it have cities? Assuming that each team has only one member, no player has been defeated and it's not a scenario, the positioning on the Foreign Advisor should correspond to the ID, i.e. the human civ in the first slot (ID 0), then increasing IDs from left to right. That's eTeam, I assume. May also need to know
to really figure out what's going on, i.e. why that latter team is apparently unable to locate cities of team 11 (not even when cheating with map knowledge).
Hovering over the text you mentioned revealed nothing. Looking in the tabs under (locals, auto, etc) provided a lot of info, but as you thought this was possibly not the cause of the crash, I left it and waited for another break.
I guess that makes sense; the debugger doesn't keep track of the node returned by the headMissionQueueNode() function. Could insert a variable above the switch block MissionTypes eTmp = headMissionQueueNode()->m_data.eMissionType;
then recompile, choose Debug when the assertion fails and then hover over eTmp (or find it in the Locals window). That would be just a temporary change to diagnose the problem. But I'd agree that it's not a priority.
Though the convention of declaring all variables at the start of a function – surely already widely discouraged at the time – is really Firaxis's fault. So, bShareValid gets declared at the start of CvTeamAI::AI_doWar, but is only initialized and supposed to be used in this if block:
Spoiler:
Code:
if (AI_getWarPlanStateCounter((TeamTypes)iI) > ((10 * iTimeModifier) / (bEnemyVictoryLevel4 ? 400 : 100)))
{
bAreaValid = false;
bShareValid = false;
for(pLoopArea = GC.getMapINLINE().firstArea(&iLoop); pLoopArea != NULL; pLoopArea = GC.getMapINLINE().nextArea(&iLoop))
{
if (AI_isPrimaryArea(pLoopArea))
{
if (GET_TEAM((TeamTypes)iI).countNumCitiesByArea(pLoopArea) > 0)
{
bShareValid = true;
AreaAITypes eAreaAI = AI_calculateAreaAIType(pLoopArea, true);
if ( eAreaAI == AREAAI_DEFENSIVE)
{
bAreaValid = false;
}
else if( eAreaAI == AREAAI_OFFENSIVE )
{
bAreaValid = true;
}
}
}
}
if ( (bAreaValid && (iEnemyPowerPercent < 140)) || (!bShareValid && (iEnemyPowerPercent < 110)) || (GET_TEAM((TeamTypes)iI).AI_getLowestVictoryCountdown() >= 0) )
{
if( gTeamLogLevel >= 1 )
{
logBBAI(" Team %d (%S) switching WARPLANS against team %d (%S) from PREPARING_TOTAL to TOTAL after %d turns with enemy power percent %d", getID(), GET_PLAYER(getLeaderID()).getCivilizationDescription(0), iI, GET_PLAYER(GET_TEAM((TeamTypes)iI).getLeaderID()).getCivilizationDescription(0), AI_getWarPlanStateCounter((TeamTypes)iI), iEnemyPowerPercent );
}
AI_setWarPlan(((TeamTypes)iI), WARPLAN_TOTAL);
}
else if (AI_getWarPlanStateCounter((TeamTypes)iI) > ((20 * iAbandonTimeModifier) / 100))
{
if( gTeamLogLevel >= 1 )
{
logBBAI(" Team %d (%S) abandoning WARPLAN_TOTAL_PREPARING against team %d (%S) after %d turns with enemy power percent %d", getID(), GET_PLAYER(getLeaderID()).getCivilizationDescription(0), iI, GET_PLAYER(GET_TEAM((TeamTypes)iI).getLeaderID()).getCivilizationDescription(0), AI_getWarPlanStateCounter((TeamTypes)iI), iEnemyPowerPercent );
}
AI_setWarPlan(((TeamTypes)iI), NO_WARPLAN);
}
}
dave has added an else block that uses the undefined variable:
Code:
//CD Tweaks - opportunistic convert to dogpile to take advantage of a powerful target being attacked by someone else
else if (bShareValid && iEnemyPowerPercent >= 140 && GET_TEAM((TeamTypes)iI).getAtWarCount(true) > 0)
above this line: if (AI_getWarPlanStateCounter((TeamTypes)iI) > ((10 * iTimeModifier) / (bEnemyVictoryLevel4 ? 400 : 100)))
This bug is not going to cause a crash on its own, I also doubt that it'll hurt the AI much, but it can prevent other problems from being reproduced reliably (because the value of bShareValid will be unpredictable if it's not initialized) and can cause out-of-sync errors in multiplayer (resulting from different values of bShareValid on two players' PCs).
No, still just code for AI war planning. I've just seen a dmp posted in the FfH forums. I'm curious what, if anything, lfgr will be able to learn from that.
Could be a different cause maybe. But if you compile a debug DLL from the same source (i.e. with pNearestCity initialized), there is no crash?
And the crash with the release DLL is consistently reproducible while the debug DLL never crashes (on that particular turn)? That could be, or maybe it's just crashing randomly with both builds.
But that DLL had been crashing before, and you haven't changed anything about it? Or ... I guess we're talking about a different turn/ savegame now.
I thought dave's DLL, just with pTheirNearestCity=NULL (and re-compiled for the change to take effect) is what crashes. 🙃
And which team is that? Does it have cities? Assuming that each team has only one member, no player has been defeated and it's not a scenario, the positioning on the Foreign Advisor should correspond to the ID, i.e. the human civ in the first slot (ID 0), then increasing IDs from left to right. That's eTeam, I assume. May also need to knowto really figure out what's going on, i.e. why that latter team is apparently unable to locate cities of team 11 (not even when cheating with map knowledge).I guess that makes sense; the debugger doesn't keep track of the node returned by the headMissionQueueNode() function. Could insert a variable above the switch block MissionTypes eTmp = headMissionQueueNode()->m_data.eMissionType;
then recompile, choose Debug when the assertion fails and then hover over eTmp (or find it in the Locals window). That would be just a temporary change to diagnose the problem. But I'd agree that it's not a priority.Sheesh, dave has introduced a lot of those errors. Also the one I came across in my brief debugging session:
Though the convention of declaring all variables at the start of a function – surely already widely discouraged at the time – is really Firaxis's fault. So, bShareValid gets declared at the start of CvTeamAI::AI_doWar, but is only initialized and supposed to be used in this if block:
Spoiler:
Code:
if (AI_getWarPlanStateCounter((TeamTypes)iI) > ((10 * iTimeModifier) / (bEnemyVictoryLevel4 ? 400 : 100)))
{
bAreaValid = false;
bShareValid = false;
for(pLoopArea = GC.getMapINLINE().firstArea(&iLoop); pLoopArea != NULL; pLoopArea = GC.getMapINLINE().nextArea(&iLoop))
{
if (AI_isPrimaryArea(pLoopArea))
{
if (GET_TEAM((TeamTypes)iI).countNumCitiesByArea(pLoopArea) > 0)
{
bShareValid = true;
AreaAITypes eAreaAI = AI_calculateAreaAIType(pLoopArea, true);
if ( eAreaAI == AREAAI_DEFENSIVE)
{
bAreaValid = false;
}
else if( eAreaAI == AREAAI_OFFENSIVE )
{
bAreaValid = true;
}
}
}
}
if ( (bAreaValid && (iEnemyPowerPercent < 140)) || (!bShareValid && (iEnemyPowerPercent < 110)) || (GET_TEAM((TeamTypes)iI).AI_getLowestVictoryCountdown() >= 0) )
{
if( gTeamLogLevel >= 1 )
{
logBBAI(" Team %d (%S) switching WARPLANS against team %d (%S) from PREPARING_TOTAL to TOTAL after %d turns with enemy power percent %d", getID(), GET_PLAYER(getLeaderID()).getCivilizationDescription(0), iI, GET_PLAYER(GET_TEAM((TeamTypes)iI).getLeaderID()).getCivilizationDescription(0), AI_getWarPlanStateCounter((TeamTypes)iI), iEnemyPowerPercent );
}
AI_setWarPlan(((TeamTypes)iI), WARPLAN_TOTAL);
}
else if (AI_getWarPlanStateCounter((TeamTypes)iI) > ((20 * iAbandonTimeModifier) / 100))
{
if( gTeamLogLevel >= 1 )
{
logBBAI(" Team %d (%S) abandoning WARPLAN_TOTAL_PREPARING against team %d (%S) after %d turns with enemy power percent %d", getID(), GET_PLAYER(getLeaderID()).getCivilizationDescription(0), iI, GET_PLAYER(GET_TEAM((TeamTypes)iI).getLeaderID()).getCivilizationDescription(0), AI_getWarPlanStateCounter((TeamTypes)iI), iEnemyPowerPercent );
}
AI_setWarPlan(((TeamTypes)iI), NO_WARPLAN);
}
}
dave has added an else block that uses the undefined variable:
Code:
//CD Tweaks - opportunistic convert to dogpile to take advantage of a powerful target being attacked by someone else
else if (bShareValid && iEnemyPowerPercent >= 140 && GET_TEAM((TeamTypes)iI).getAtWarCount(true) > 0)
above this line: if (AI_getWarPlanStateCounter((TeamTypes)iI) > ((10 * iTimeModifier) / (bEnemyVictoryLevel4 ? 400 : 100)))
This bug is not going to cause a crash on its own, I also doubt that it'll hurt the AI much, but it can prevent other problems from being reproduced reliably (because the value of bShareValid will be unpredictable if it's not initialized) and can cause out-of-sync errors in multiplayer (resulting from different values of bShareValid on two players' PCs).No, still just code for AI war planning. I've just seen a dmp posted in the FfH forums. I'm curious what, if anything, lfgr will be able to learn from that.
I had a 2nd attempt at a Release dll and the crash has not occurred. So I must have done something wrong. Probably because the release dll was not in the correct place, I found it and put it in the right place. No crashes as yet. Previously, with debugger attached it was not crashing, but I'm more confident, and a little bit more competent with all this now.
So teamid does not correspond to the team number in wbs file?
I will make those adjustments and compile another release dll.
Hi f1rpo, I am sorry to trouble you again. I have a debug problem again!
Since we were last in this thread, I've played a couple of 1000 turns with no CTD.
I'm playing on a different map, but same mod(dave uk modmod).
The only modification I made to the cpp files was the initialisation that was causing a crash, you mentioned some other code to change, but I was too eager to keep playing.
I performed a debug, it requested a break at:
CvTeam.cpp
Line 2592 Fassert (iCount >= getAtWarCount(bIgnoreMinors));
Prior to the crash/break, once the game had loaded, this was displayed in VStudio ouput:
Code:
The thread 'win32 thread' 0xd48 exited with code 0
(repeated x2 with different hexadecimal)
Upon my second examination, the hexadecimal was different. I suspect it is unrelated to the CTD, but related to a different issue I am having with MAF. due to having a new laptop.
Good call, probably, to play rather than grind on with minor bugfixes. But this one is another showstopper, I guess(?). (I assume that there is a crash also when ignoring the exception.)
The assertion seems to say that some team is in a war without having set a war plan type. The war plan type (limited, total, defensive, recently attacked, dogpile) is relevant for the AI. Here's the surrounding source code, for reference:
Spoiler:
Code:
int CvTeam::getAnyWarPlanCount(bool bIgnoreMinors) const
{
int iCount;
int iI;
iCount = 0;
for (iI = 0; iI < MAX_CIV_TEAMS; iI++)
{
if (GET_TEAM((TeamTypes)iI).isAlive())
{
//CD Tweaks - ignore minors now also ignores civs with no cities (rebels, not quite killed when requireCompleteKills is on)
if (!bIgnoreMinors || (!(GET_TEAM((TeamTypes)iI).isMinorCiv()) && GET_TEAM((TeamTypes)iI).getNumCities() > 0))
//CD Tweaks End
{
if (AI_getWarPlan((TeamTypes)iI) != NO_WARPLAN)
{
FAssert(iI != getID());
iCount++;
}
}
}
}
FAssert(iCount >= getAtWarCount(bIgnoreMinors));
return iCount;
}
int CvTeam::getAtWarCount(bool bIgnoreMinors, bool bIgnoreVassals) const
{
int iCount;
int iI;
iCount = 0;
for (iI = 0; iI < MAX_CIV_TEAMS; iI++)
{
if (GET_TEAM((TeamTypes)iI).isAlive())
{
//CD Tweaks - ignore minors now also ignores civs with no cities (rebels, not quite killed when requireCompleteKills is on)
if (!bIgnoreMinors || (!(GET_TEAM((TeamTypes)iI).isMinorCiv()) && GET_TEAM((TeamTypes)iI).getNumCities() > 0))
//CD Tweaks End
{
if( !bIgnoreVassals || !(GET_TEAM((TeamTypes)iI).isAVassal()))
{
if (isAtWar((TeamTypes)iI))
{
FAssert(iI != getID());
FAssert(!(AI_isSneakAttackPreparing((TeamTypes)iI)));
iCount++;
}
}
}
}
}
return iCount;
}
There are changes by Dave in there, but those should only make a difference when playing with Complete Kills (and even then, no error in the code is apparent to me). To find out more about the cause of the failed assertion, the debugger would be needed; for a start, the call stack at the time that the assertion fails. However, the crash could be entirely unrelated, so it would better – and should not be any more difficult – to debug the crash directly and then to inspect the call stack and other relevant context. As in these screenshots, for example:
Spoiler:
Unhandled exception (popup):
Spoiler:
Call stack, local data at the location of the crash:
Spoiler:
Having moved down a few frames on the stack (through double-click):
Prior to the crash/break, once the game had loaded, this was displayed in VStudio ouput:
Code:
The thread 'win32 thread' 0xd48 exited with code 0
(repeated x2 with different hexadecimal)
Upon my second examination, the hexadecimal was different. I suspect it is unrelated to the CTD, but related to a different issue I am having with MAF. due to having a new laptop.
Those hexadecimals are thread IDs, it seems. I don't know how those get assigned, but I don't think it's worrisome to get different ones each time that the game exits.
Good call, probably, to play rather than grind on with minor bugfixes. But this one is another showstopper, I guess(?). (I assume that there is a crash also when ignoring the exception.)
The assertion seems to say that some team is in a war without having set a war plan type. The war plan type (limited, total, defensive, recently attacked, dogpile) is relevant for the AI. Here's the surrounding source code, for reference:
Spoiler:
Code:
int CvTeam::getAnyWarPlanCount(bool bIgnoreMinors) const
{
int iCount;
int iI;
iCount = 0;
for (iI = 0; iI < MAX_CIV_TEAMS; iI++)
{
if (GET_TEAM((TeamTypes)iI).isAlive())
{
//CD Tweaks - ignore minors now also ignores civs with no cities (rebels, not quite killed when requireCompleteKills is on)
if (!bIgnoreMinors || (!(GET_TEAM((TeamTypes)iI).isMinorCiv()) && GET_TEAM((TeamTypes)iI).getNumCities() > 0))
//CD Tweaks End
{
if (AI_getWarPlan((TeamTypes)iI) != NO_WARPLAN)
{
FAssert(iI != getID());
iCount++;
}
}
}
}
FAssert(iCount >= getAtWarCount(bIgnoreMinors));
return iCount;
}
int CvTeam::getAtWarCount(bool bIgnoreMinors, bool bIgnoreVassals) const
{
int iCount;
int iI;
iCount = 0;
for (iI = 0; iI < MAX_CIV_TEAMS; iI++)
{
if (GET_TEAM((TeamTypes)iI).isAlive())
{
//CD Tweaks - ignore minors now also ignores civs with no cities (rebels, not quite killed when requireCompleteKills is on)
if (!bIgnoreMinors || (!(GET_TEAM((TeamTypes)iI).isMinorCiv()) && GET_TEAM((TeamTypes)iI).getNumCities() > 0))
//CD Tweaks End
{
if( !bIgnoreVassals || !(GET_TEAM((TeamTypes)iI).isAVassal()))
{
if (isAtWar((TeamTypes)iI))
{
FAssert(iI != getID());
FAssert(!(AI_isSneakAttackPreparing((TeamTypes)iI)));
iCount++;
}
}
}
}
}
return iCount;
}
There are changes by Dave in there, but those should only make a difference when playing with Complete Kills (and even then, no error in the code is apparent to me). To find out more about the cause of the failed assertion, the debugger would be needed; for a start, the call stack at the time that the assertion fails. However, the crash could be entirely unrelated, so it would better – and should not be any more difficult – to debug the crash directly and then to inspect the call stack and other relevant context. As in these screenshots, for example:
Those hexadecimals are thread IDs, it seems. I don't know how those get assigned, but I don't think it's worrisome to get different ones each time that the game exits.
Good call, probably, to play rather than grind on with minor bugfixes. But this one is another showstopper, I guess(?). (I assume that there is a crash also when ignoring the exception.)
Seems that the declaration of war comes from Python. I guess that means a Random Event (Wedding Feud) or, more likely, something to do with Revolutions, Barbarian Civs or Assimilation. (I've searched the mod's Python folder for declareWar calls.) Double-clicking the CvTeam::declareWar frames on the stack and, for each of the two frames, hovering over the eTeam variable in the code editor should reveal the IDs of the teams that are going to war. Then, if you can figure out which civs those IDs correspond to (through the scenario file?), perhaps then you can also guess why this war is being declared, and maybe that'll give one of us an idea why the at-war counter is inconsistent with the war-plan counter.
I still don't really think that this inconsistency is directly related to the crash. So it would imo be better to ignore the failed assertion and look at the call stack at the time of the actual crash. But, if you can easily find out just what sort of war this is - by debugging the failed assertion -, then that could be a good idea too – before moving on to the crash.
I could only find 1 eTeam variable in the stack. It referred to a team who is a vassal. They are already at war.
I reloaded with debugger, bribed the master of the vassal to make peace.
A different crash occurred, one that I have already encountered, but was able to work around. I attempted to range bombard a tile, it had an enemy worker on the tile. The game crashed when I clicked the bombard icon.
This crash is similar, but with an AI team attempting the same thing. A variable is NULL and it is causing a freak out.
Call stack below.
Oh, I see, looking at the actual line numbers, there's a recursive declareWar call. I had assumed that A declares on B (eTeam) and B on A, but it's actually A on B (apparently triggered from Python) and then a vassal of A also declaring on B. And B, if I interpret your post correctly, is a vassal as well. I have a vague suspicion that the Python code should only cause free teams to declare, i.e. that it might lack an isVassal check somewhere. Or, if you say they're "already at war," maybe an isAtWar check is missing in Python. The debugger can't inspect the Python context, but one could search for all declareWar calls in Python and insert print statements to identify the one that causes this particular war.
The bombardment crash happens here, apparently (and, for once, a failed assertion should occur right before the crash):
Spoiler:
Code:
bool CvUnitAI::AI_bombardCity()
{
PROFILE_FUNC();
CvCity* pBombardCity;
if (canBombard(plot()) || canRBombard(plot()))
{
pBombardCity = bombardTarget(plot(),true);
FAssertMsg(pBombardCity != NULL, "BombardCity is not assigned a valid value");
// do not bombard cities with no defenders
int iDefenderStrength = pBombardCity->plot()->AI_sumStrength(...
So either canBombard or (more likely) the DCM function canRBombard returns true, but bombardTarget (with bIncludeRBombard=true, a parameter added by Dave) fails to find a city to bombard. These are all CvUnit functions. I don't see anything in canRBombard that guarantees that there will be a city to bombard; the function only seems to check whether the unit has a capacity to execute ranged bombardments in general. A little strange to me that this doesn't crash more frequently. Anyway, I don't know who added that alternative canRBombard check or to what end; can't see any good that it might do. So I'd recommend just commenting that out:
Code:
if (canBombard(plot()) /*|| canRBombard(plot())*/)
Edit: I'm guessing that the intention was to allow the AI to use ranged bombardment (though I don't even know the specific rules for that DCM mechanism). The important part to change was CvUnit::bombardTarget; that's where Dave added the bIncludeRBombard parameter. Perhaps those changes are correctly implemented. He probably also added the canRBombard check, and that's not needed and, in fact, harmful.
Oh, I see, looking at the actual line numbers, there's a recursive declareWar call. I had assumed that A declares on B (eTeam) and B on A, but it's actually A on B (apparently triggered from Python) and then a vassal of A also declaring on B. And B, if I interpret your post correctly, is a vassal as well. I have a vague suspicion that the Python code should only cause free teams to declare, i.e. that it might lack an isVassal check somewhere. Or, if you say they're "already at war," maybe an isAtWar check is missing in Python. The debugger can't inspect the Python context, but one could search for all declareWar calls in Python and insert print statements to identify the one that causes this particular war.
That's a pragmatic solution.
The bombardment crash happens here, apparently (and, for once, a failed assertion should occur right before the crash):
Spoiler:
Code:
bool CvUnitAI::AI_bombardCity()
{
PROFILE_FUNC();
CvCity* pBombardCity;
if (canBombard(plot()) || canRBombard(plot()))
{
pBombardCity = bombardTarget(plot(),true);
FAssertMsg(pBombardCity != NULL, "BombardCity is not assigned a valid value");
// do not bombard cities with no defenders
int iDefenderStrength = pBombardCity->plot()->AI_sumStrength(...
So either canBombard or (more likely) the DCM function canRBombard returns true, but bombardTarget (with bIncludeRBombard=true, a parameter added by Dave) fails to find a city to bombard. These are all CvUnit functions. I don't see anything in canRBombard that guarantees that there will be a city to bombard; the function only seems to check whether the unit has a capacity to execute ranged bombardments in general. A little strange to me that this doesn't crash more frequently. Anyway, I don't know who added that alternative canRBombard check or to what end; can't see any good that it might do. So I'd recommend just commenting that out:
Code:
if (canBombard(plot()) /*|| canRBombard(plot())*/)
Edit: I'm guessing that the intention was to allow the AI to use ranged bombardment (though I don't even know the specific rules for that DCM mechanism). The important part to change was CvUnit::bombardTarget; that's where Dave added the bIncludeRBombard parameter. Perhaps those changes are correctly implemented. He probably also added the canRBombard check, and that's not needed and, in fact, harmful.
It's just this one line if (canBombard(plot()) /*|| canRBombard(plot())*/)
and I've already inserted comment tokens. C++ uses // for single-line or end-of-line comments and /*...*/ for multi-line or in-line comments. Hopefully, that'll avoid the crash upon ranged AI units attempting to bombard cities. Might want to compile a Debug build first in case that the crash (or a related one) still occurs. For continuing your game, you'll probably want a Release build for greater speed and to avoid seeing (non-critical) failed assertions.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.