MNAI-U: unofficial build & bugfixes

What do you think about putting golems as henchman too ?
Not a bad idea as such, but I try to stay away from non-bugfixing stuff as much as possible, at least in MNAI-U.

The Armageddon Counter could also play a role in making more powerful enemies more likely to emerge from lairs... but I think that it already does? Would that factor would interact well with the proposed changes? I'd imagine so, but I don't know the specific details.
AC 40 is currently a requirement for Pit Beast, Death Knight and Balor to spawn.

Personally, I'd like for Open Borders not to allow it, but for hostile and Hidden Nationality units to still be able to do it.
Agreed. As for teams, I lean towards allowing only human players to explore lairs in teammates' territory.
The AI is currently the main problem. There is no dedicated lair exploration code, but instead a marker for certain improvements (lairs, but also e.g. cages) that the AI should visit with units. Units will explore lairs they're standing on with some different code.

@lfgr what direction are you planning to take this mod?
Nothing too ambitious from a player's perspective. Fix bugs, polish, quality of life improvements, basically continue what Tholal did. At some point I want to scavenge the "new" AI mods like AdvCivs for AI improvements. The only thing I might put a bit more creativity into (at some point) is the revolutions component, which is still quite rough and not well integrated into FfH lore.
The way I see the MNAI descendants is basically that MNAI-U is for stability, MagisterModMod is for new features, and EMM is for balance and a bit of both.
 
What I'd really like though is a python call I could use when units withdraw from combat, instead of only when combat results in the death of one unit or another.
There are actually two BUG events for this, combatRetreat (when the attacker flees) and combatWithdrawal (when the attacker withdraws after dealing maximum damage, like catapults); see BUG/BugEventManager.py. I've added combatDefenderRetreat (for when the defender flees) for the next release. Let me know if you need help setting up the BUG event handlers.

EDIT:
Could you let promotions change a unit's change of miscasting?
I have implemented a <iMiscastChance> tag for promotions. However, the way it currently works, if a unit has a positive overall miscasting chance, it can miscast every spell, including e.g. world spells. Is that what you want? If not, I'm thinking of making a <bCanMiscast> tag that would have to be added to all spells that can be miscast.
 
Last edited:
New release: mnai-2.8.1u. Should be compatible with 2.8.0u savegames.
Thanks to Devils_Advocate, MagisterCultuum, OKSleeper and westamastaflash for bug reports.

Download
Please extract into "Mods/Fall from Heaven 2" (not into the Assets folder!)

This release again contains a lot of smaller bug fixes. Other changes include:
  • As discussed earlier in this thread, I took some measures to reduce early civ deaths. Lair bosses are now not as strong early on, the mistform event requires sorcery, and barbarian spectres cannot upgrade to wraiths before barbarians have sorcery (i.e. many players have sorcery or a few have had it for some time).
  • The sevopedia and several advisor screens can now set to full-screen via the "Advisors" tab in the BUG option window. The other advisor screens will follow suit in the next few versions.
  • I overhauled the Dynamic civ names component. The way Tholal set up the system, it can generate a wide variety of interesting names, but also sometimes results in somewhat wacky ones. However, I think DNC is quite useful in any case to distinguish between multiple players of the same civilization. So I added two more conservative options. "Minimal" doesn't do anything except when multiple players share the same civ, where all get the name "Realm of <leader>". "Medium" tries to be a bit more creative. In particular, it uses the capital city in the name of vassal players that share a civilization with some other players. For example, a Bannor player and its Bannor vassal might get the names "Bannor Empire" and "Duchy of Vallus". You can switch between these options in the "Scoreboard" tab of the BUG option window (the names will be immediately updated).
  • The performance of the AI unit evaluation was improved via caching. The savings in my two test games were considerable, but I only tested the non-optimized version of the DLL, so the actual improvement might be less noticeable. Still, I'm hoping to make similar improvements in some other ares in the future.

See here or the file changelog.md included in the download for a full changelog.
 
Last edited:
Thanks for the update!

It is already possible to make spells so that should never miscast by giving them a <iMiscastChance>-1000</iMiscastChance> modifier, so a <bCanMiscast> tag is not really necessary but might be nice if only to reduce clutter in the spell mouseovers.

It might be easier though to only have a spell miscast if something is listed in its <PyMiscast> tag. You could just put something like <PyMiscast>False </PyMiscast> for spells that simply fail when miscast rather than doing anything special.

In my initial texts am noticing a lot more units than I would like miscasting currently, but I can probably fix that by adjusting some values with tags already in place.

I started to complain about several missing TXT_KEYs, but just before submitting I realized that when merging your mod with mine I'd forgotten to check "Show Left Unique Items: in Winmerge, which meant I had overlooked and forgot to include the new file Spells.xml.

I think I would like some pointers on setting up the BUG event handlers. I have never bothered with any BUG files before and don't want to mess things up.

Can I just treat BugEventManager.py like CvEventManager.py and put the code there directly, or do I need to put thins in other files.

What I want to do is basically just make this code from def onCombatResult(self, argsList): apply also when a unit manages to retreat (perhaps only if they land some hit to do a little damage.


Spoiler :
Code:
               iGodslayer = gc.getInfoTypeForString('PROMOTION_GODSLAYER')
               iAvatar = gc.getInfoTypeForString('PROMOTION_AVATAR')

               iCaveaAngelorum = gc.getInfoTypeForString('PROMOTION_ANGELORUM_CAVEA')
               iCaptusAngelorum = gc.getInfoTypeForString('PROMOTION_CAPTUS_CAVEAE_ANGELORUM')

               iCaveaSawol = gc.getInfoTypeForString('PROMOTION_SAWOL_CAVEA')
               iCaptusSawol = gc.getInfoTypeForString('PROMOTION_CAPTUS_CAVEAE_SAWOL')

               iNetherblade = gc.getInfoTypeForString('PROMOTION_NETHER_BLADE')
               iNBind = gc.getInfoTypeForString('PROMOTION_NETHERBIND')

               iCustos = gc.getInfoTypeForString('PROMOTION_CUSTOS_JUDICII')
               iCarcer = gc.getInfoTypeForString('PROMOTION_INCARCERATUS_JUDICII')

               if pWinner.isHasPromotion(iGodslayer):
                   cf.makeMortal(pLoser)
                   if pLoser.isHasPromotion(iAvatar):
                       pLoser.setHasPromotion(iAvatar, False)
                       gc.getPlayer(iPlayerL).AI_changeAttitudeExtra(iPlayerW,-12)
                       pLoser.kill(True, iPlayerW)
               elif pLoser.isHasPromotion(iGodslayer):
                   cf.makeMortal(pWinner)
                   if pWinner.isHasPromotion(iAvatar):
                       pWinner.setHasPromotion(iAvatar, False)
                       gc.getPlayer(iPlayerW).AI_changeAttitudeExtra(iPlayerL,-12)
                       pWinner.kill(True, iPlayerL)

               if pWinner.isHasPromotion(iNetherblade):
                   pLoser.setHasPromotion(iNBind, True)
                   cf.makeMortal(pLoser)
                   if isWorldUnitClass(pLoser.getUnitClassType()):
                       gc.getPlayer(iPlayerL).AI_changeAttitudeExtra(iPlayerW,-7)
                       pLoser.kill(True, iPlayerW)
               elif pLoser.isHasPromotion(iNetherblade):
                   cf.makeMortal(pWinner)
                   if isWorldUnitClass(pWinner.getUnitClassType()):
                       pWinner.setHasPromotion(iNBind, True)
                       gc.getPlayer(iPlayerW).AI_changeAttitudeExtra(iPlayerL,-7)
                       pWinner.kill(True, iPlayerL)



               if pWinner.isHasPromotion(iCaveaAngelorum):
                   if pLoser.getRace() in [gc.getInfoTypeForString('PROMOTION_DEMON'),gc.getInfoTypeForString('PROMOTION_ANGEL')]:

                       pLoser.setHasPromotion(iCaptusAngelorum, True)
                       cf.makeMortal(pLoser)
                       if isWorldUnitClass(pLoser.getUnitClassType()):
                           gc.getPlayer(iPlayerL).AI_changeAttitudeExtra(iPlayerW,-7)
                       pLoser.kill(True, iPlayerW)
                   elif pLoser.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DEMON_POSSESSED')):
                       pLoser.setHasPromotion(gc.getInfoTypeForString('PROMOTION_DEMON_POSSESSED'), False)


               elif pWinner.isHasPromotion(iCustos):
                   if pLoser.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DEMON')):
                       pLoser.setHasPromotion(iCarcer, True)
                       cf.makeMortal(pLoser)
                       if isWorldUnitClass(pLoser.getUnitClassType()):
                           gc.getPlayer(iPlayerL).AI_changeAttitudeExtra(iPlayerW,-7)
                       pLoser.kill(True, iPlayerW)
                   elif pLoser.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DEMON_POSSESSED')):
                       pLoser.setHasPromotion(gc.getInfoTypeForString('PROMOTION_DEMON_POSSESSED'), False)

               iX = pWinner.getX()
               iY = pWinner.getY()



               if pLoser.isAlive():

                   if pWinner.isHasPromotion(gc.getInfoTypeForString('PROMOTION_VILE_TOUCH')):
                       pLoser.setHasPromotion(gc.getInfoTypeForString('PROMOTION_WITHERED'), True)
                   if pWinner.getDamageTypeCombat(gc.getInfoTypeForString('DAMAGE_POISON')) > 0:
                       if pLoser.getDamage() > 0:
                           if not pLoser.isHasPromotion(gc.getInfoTypeForString('PROMOTION_POISONED')):
                               if pLoser.getDamageTypeResist(gc.getInfoTypeForString('DAMAGE_POISON')) < 100:
                                   if CyGame().getSorenRandNum(100,"Poisoned") >= pLoser.getDamageTypeResist(gc.getInfoTypeForString('DAMAGE_POISON')):
                                       pLoser.setHasPromotion(gc.getInfoTypeForString('PROMOTION_POISONED'), True)



                   if pWinner.isHasPromotion(iCaveaSawol):

                       pLoser.setHasPromotion(iCaptusSawol, True)
                       cf.makeMortal(pLoser)
                       if isWorldUnitClass(pLoser.getUnitClassType()):
                           gc.getPlayer(iPlayerL).AI_changeAttitudeExtra(iPlayerW,-7)
                       pLoser.kill(True, iPlayerW)
                   else:

                       iRavenous = gc.getInfoTypeForString('PROMOTION_RAVENOUS')
                       iWerewolf = gc.getInfoTypeForString('PROMOTION_WEREWOLF')
                       lImmuneLycanthropy = [   iWerewolf,
                               gc.getInfoTypeForString('PROMOTION_WEREWOLF_SLAYING'),
                               gc.getInfoTypeForString('PROMOTION_IMMUNE_DISEASE'),
                               gc.getInfoTypeForString('PROMOTION_SPIRIT3'),
                               gc.getInfoTypeForString('PROMOTION_AFFINITY_SPIRIT'),
                               gc.getInfoTypeForString('PROMOTION_ILLUSION'),
                               gc.getInfoTypeForString('PROMOTION_DARK_REFLECTION')
                               ]


                       if pWinner.isHasPromotion(iRavenous):
                           if not pLoser.isImmortal():
                               if not iUnitCombatLoser in lBeasts:
                                   pWinner.setHasPromotion(iRavenous, False)


                       if pLoser.isHasPromotion(iWerewolf):
                           if pWinner.isAlive() and pWinner.getDamage() > 0:
                               if not pWinner.getUnitCombatType() in lBeasts:
                                   for iProm in lImmuneLycanthropy:
                                       if pWinner.isHasPromotion(iProm):
                                           break
                                   else:
                                       iChance = pLoser.getLevel() - pWinner.getLevel()
                                       if iTypeLoser == gc.getInfoTypeForString('UNIT_DUIN'):
                                           iChance += 5
                                       elif iTypeLoser == gc.getInfoTypeForString('UNIT_GREATER_WEREWOLF'):
                                           iChance += 3
                                       elif iTypeLoser == gc.getInfoTypeForString('UNIT_WEREWOLF'):
                                           iChance += 2
                                       elif iTypeLoser == gc.getInfoTypeForString('UNIT_RAVENOUS_WEREWOLF'):
                                           iChance += 1
                                       if iChance > 0:
                                           if CyGame().getSorenRandNum(200, "Spread Lycanthropy"  + str(pWinner.getName()) + ' slayer of '+ str(pLoser.getName())) < iChance:
                                               sName = pWinner.getName()
                                               CyInterface().addMessage(iPlayerW,True,25,CyTranslator().getText("TXT_KEY_MESSAGE_SPREAD_LYCANTHROPY",(sName,)),'AS2D_FEATUREGROWTH',1,'Art/Interface/Buttons/Promotions/Werewolf.dds',ColorTypes(7),iX,iY,True,True)
                                               CyInterface().addMessage(iPlayerL,True,25,CyTranslator().getText("TXT_KEY_MESSAGE_SPREAD_LYCANTHROPY",(sName,)),'AS2D_FEATUREGROWTH',1,'Art/Interface/Buttons/Promotions/Werewolf.dds',ColorTypes(8),iX,iY,True,True)
                                               newUnit = pPlayerW.initUnit(gc.getInfoTypeForString('UNIT_RAVENOUS_WEREWOLF'), iX, iY, UnitAITypes.UNITAI_ATTACK, DirectionTypes.NO_DIRECTION)
                                               cf.makeMortal(pWinner)
                                               pWinner.setHasPromotion(gc.getInfoTypeForString('PROMOTION_DARK_REFLECTION'), True)
                                               newUnit.convert(pWinner)
                                               newUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_DARK_REFLECTION'), False)

                       if pWinner.isHasPromotion(iWerewolf):
                           if pLoser.isAlive():
                               if not iUnitCombatLoser in lBeasts:
                                   for iProm in lImmuneLycanthropy:
                                       if pLoser.isHasPromotion(iProm):
                                           break
                                   else:
                                       iChance = pWinner.getLevel() - pLoser.getLevel()
                                       sName = pLoser.getName()
                                       if iTypeWinner == gc.getInfoTypeForString('UNIT_DUIN'):
                                           iChance += 5
                                       elif iTypeWinner == gc.getInfoTypeForString('UNIT_GREATER_WEREWOLF'):
                                           iChance += 3
                                           if pWinner.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CANNOT_CAST')):
                                               pWinner.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CANNOT_CAST'), False)
                                               CyInterface().addMessage(iPlayerW,True,25,CyTranslator().getText("TXT_KEY_MESSAGE_WEREWOLF_CAN_CAST",(sName,)),'AS2D_FEATUREGROWTH',1,'Art/Interface/Buttons/Promotions/Werewolf.dds',ColorTypes(8),iX,iY,True,True)

                                       elif iTypeWinner == gc.getInfoTypeForString('UNIT_WEREWOLF'):
                                           iChance += 2
                                       elif iTypeWinner == gc.getInfoTypeForString('UNIT_RAVENOUS_WEREWOLF'):
                                           iChance += 1
                                       iChance *= 10
                                       if iChance > 0:
                                           if CyGame().getSorenRandNum(100, "Spread Lycanthropy "  + str(pWinner.getName()) + ' slayer of '+ str(pLoser.getName())) < iChance:
                                               sName = pLoser.getName()
                                               CyInterface().addMessage(iPlayerL,True,25,CyTranslator().getText("TXT_KEY_MESSAGE_SPREAD_LYCANTHROPY",(sName,)),'AS2D_FEATUREGROWTH',1,'Art/Interface/Buttons/Promotions/Werewolf.dds',ColorTypes(7),iX,iY,True,True)
                                               CyInterface().addMessage(iPlayerW,True,25,CyTranslator().getText("TXT_KEY_MESSAGE_SPREAD_LYCANTHROPY",(sName,)),'AS2D_FEATUREGROWTH',1,'Art/Interface/Buttons/Promotions/Werewolf.dds',ColorTypes(8),iX,iY,True,True)
                                               newUnit = pPlayerW.initUnit(gc.getInfoTypeForString('UNIT_RAVENOUS_WEREWOLF'), iX, iY, UnitAITypes.UNITAI_ATTACK, DirectionTypes.NO_DIRECTION)
                                               newUnit.setScenarioCounter(iTypeLoser)
                                               newUnit.setLevel(pLoser.getLevel())
                                               newUnit.setExperience(pLoser.getExperience(), -1)
                                               newUnit.setName(sName)
                                               newUnit.setReligion(pLoser.getReligion())
                                               newUnit.setDamage(pLoser.getDamage()/2, pWinner.getOwner())
                                               newUnit.finishMoves()
                                               for iCount in xrange(gc.getNumPromotionInfos()):
                                                   if pLoser.isHasPromotion(iCount) and not gc.getPromotionInfo(iCount).isEquipment():
                                                       newUnit.setHasPromotion(iCount, True)
                                               cf.makeMortal(pLoser)
                                               pLoser.setHasPromotion(gc.getInfoTypeForString('PROMOTION_DARK_REFLECTION'), True)

Could you make it so that the pedia entries for equipment show the date for the unit as well as the promotion? Or maybe just let promotions have Pedia entries that do not show up in mouseovers?

I added Kael's recently released story of Basium and the Boiling Sea to the pedia entry for the equipment he uses to trap demons, but found it did not show up anywhere when it is there for the unit. Using a 6 page story as a help string that appears in the mouseovers of a promotion would be insane.
 
Last edited:
...Kael's recently released story of Basium and the Boiling Sea...

WHERE DID YOU FIND THIS?... Ahem. Sorry. I mean, I wasn't aware Kael was still releasing FfH-related anything. Is there a forum or blog where he dumps this stuff?
 

WHERE DID YOU FIND THIS?... Ahem. Sorry. I mean, I wasn't aware Kael was still releasing FfH-related anything. Is there a forum or blog where he dumps this stuff?
Kael has been active on Reddit recently. Last month he released an entry on one character per day, some of which cover lore he had never revealed before. Some stuff was recently reconnected as only this year did he decide the course that history should take during the Age of Invention, after the Crucible robs the world of Magic and the worship of the old gods is replaced by the Matronae. Who the Matronae are was a secret he saved for the Halloween entry on Basium and the Boiling Sea.
 
I think I would like some pointers on setting up the BUG event handlers. I have never bothered with any BUG files before and don't want to mess things up.

Can I just treat BugEventManager.py like CvEventManager.py and put the code there directly, or do I need to put thins in other files.
You shouldn't put it into BugEventManager.py. The idea of the BUG architecture is that each component has their own files, which is nice to make them easier to merge. A component can register its functions as event handlers. I made a small example that should serve as a template for you:

In the Assets/Config directory, you put the file Magister.xml:
Code:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!--
   MagisterModMod EventHandlers
 
   DESCRIPTION HERE
-->
<mod id="MagisterEvents"
    name="Magister Events"
    author="Magister Cultuum"
    version="..."
    date="..."
    url="...">
   <event type="combatWithdrawal" module="MagisterEvents" function="onCombatWithdrawal"/>
   <event type="combatRetreat" module="MagisterEvents" function="onCombatRetreat"/>
   <event type="combatDefenderRetreat" module="MagisterEvents" function="onCombatDefenderRetreat"/>
</mod>

Most of that should be self-explanatory. The important part is the <event ... /> tags, where you bind e.g. the combatWithdrawal event to the onCombatWithdrawal function in the MagisterEvents module. Consequently, you need a MagisterEvents.py file in Assets/python that could look like this:
Code:
# MagisterModMod Events

from CvPythonExtensions import *


def onCombatWithdrawal(argsList):
   """Fired when an attacker withdraws from combat after doing maximum damage."""
   pAttacker, pDefender = argsList
   CyInterface().addImmediateMessage( str("TEST - %s withdraws from %s" % ( pAttacker.getName(), pDefender.getName() ) ), "" )

def onCombatRetreat(argsList):
   """Fired when an attacker retreats from combat, escaping death."""
   pAttacker, pDefender = argsList
   CyInterface().addImmediateMessage( str( "TEST - %s retreats from %s" % ( pAttacker.getName(), pDefender.getName() ) ), "" )

def onCombatDefenderRetreat(argsList):
   """Fired when a defender retreats from combat, escaping death."""
   pAttacker, pDefender = argsList
   CyInterface().addImmediateMessage( str( "TEST - defender %s retreats from %s" % ( pDefender.getName(), pAttacker.getName() ) ), "" )

Finally, you need to tell BUG to load your component by specifying
Code:
    <load mod="Magister"/>
in Assets/Config/init.xml, e.g. after the <load mod="BUFFY"/> line.
I attached the example files.

Could you make it so that the pedia entries for equipment show the date for the unit as well as the promotion? Or maybe just let promotions have Pedia entries that do not show up in mouseovers?
I'll look into it.
 

Attachments

  • BUGExample.zip
    2.9 KB · Views: 164
Thanks.

When I first tried using your template with a bit of my code it did not work, but then I realized it was because I had referenced another file I had not imported nor did I need.

I have tested it and found it working for the Runewyn's ability to remove magic with a single tough. I have not yet tested how well it works with the code to spread Lycanthropy or pass on poisoned or withered. I have not yet tested code that could kill a unit.


I have gotten these asserts after merging your mod with mine:
Spoiler :
Code:
Assert Failed

File:  CvUnit.cpp
Line:  3833
Expression:  canMoveInto(pPlot, true)
Message:

----------------------------------------------------------

Assert Failed

File:  CvCityAI.cpp
Line:  4463
Expression:  (MAX_INT / 1000) > iValue
Message:

----------------------------------------------------------
Assert Failed

File:  CvPlot.cpp
Line:  8644
Expression:  getStolenVisibilityCount(eTeam) >= 0
Message:

----------------------------------------------------------

Assert Failed

File:  CvUnit.cpp
Line:  14594
Expression:  eIndex >= 0
Message:  eIndex is expected to be non-negative (invalid Index)

----------------------------------------------------------

Assert Failed

File:  CvCity.cpp
Line:  7755
Expression:  getBonusGoodHealth() >= 0
Message:  getBonusGoodHealth is expected to be >= 0

----------------------------------------------------------
Assert Failed

File:  CvInfos.cpp
Line:  2674
Expression:  i > -1
Message:  Index out of bounds



----------------------------------------------------------
Assert Failed

File:  CvCity.cpp
Line:  8340
Expression:  getBonusBadHappiness() <= 0
Message:

----------------------------------------------------------



Assert Failed

File:  CvGlobals.cpp
Line:  1865
Expression:  eGoodyNum > -1
Message:

----------------------------------------------------------



Assert Failed

File:  CvCity.cpp
Line:  7753
Expression:  getBonusGoodHealth() >= 0
Message:


----------------------------------------------------------
(When miscasting Floating Eye)
Assert Failed

File:  CvUnit.cpp
Line:  3833
Expression:  canMoveInto(pPlot, true)
Message:

----------------------------------------------------------
(when attacking barbarian floating eye from miscasting
Assert Failed

File:  CvUnit.cpp
Line:  1093
Expression:  getAttackPlot() == NULL
Message:  The current unit instance's attack plot is expected to be NULL

----------------------------------------------------------Assert Failed

File:  CvUnit.cpp
Line:  1081
Expression:  !isCombat()
Message:  isCombat did not return false as expected

----------------------------------------------------------
Assert Failed

File:  CvUnit.cpp
Line:  12849
Expression:  getMoves() >= 0
Message:

----------------------------------------------------------
Assert Failed

File:  CvUnit.cpp
Line:  3833
Expression:  canMoveInto(pPlot, true)
Message:

----------------------------------------------------------
Assert Failed

File:  CvTeam.cpp
Line:  3153
Expression:  !(AI_isSneakAttackPreparing((TeamTypes)iI))
Message:

----------------------------------------------------------
Assert Failed

File:  d:\dev\civ\repos\mnai\cvgamecoredll\CvPlayerAI.h
Line:  25
Expression:  ePlayer != NO_PLAYER
Message:  Player is not assigned a valid value

----------------------------------------------------------
Assert Failed

File:  CvUnitAI.cpp
Line:  61
Expression:  AI_getUnitAIType() != NO_UNITAI
Message:  AI_getUnitAIType() is not expected to be equal with NO_UNITAI

----------------------------------------------------------
Assert Failed

File:  CvArea.cpp
Line:  861
Expression:  eIndex2 >= 0
Message:  eIndex2 is expected to be >= 0

----------------------------------------------------------
Assert Failed

File:  CvArea.cpp
Line:  851
Expression:  eIndex2 >= 0
Message:  eIndex2 is expected to be >= 0

----------------------------------------------------------
Assert Failed

File:  CvArea.cpp
Line:  864
Expression:  getNumAIUnits(eIndex1, eIndex2) >= 0
Message:

----------------------------------------------------------
Assert Failed

File:  CvPlayerAI.cpp
Line:  17653
Expression:  eIndex >= 0
Message:  eIndex is expected to be non-negative (invalid Index)

----------------------------------------------------------
Assert Failed

File:  CvPlayerAI.cpp
Line:  17645
Expression:  eIndex >= 0
Message:  eIndex is expected to be non-negative (invalid Index)

----------------------------------------------------------
Assert Failed

File:  CvPlayerAI.cpp
Line:  17656
Expression:  AI_getNumAIUnits(eIndex) >= 0
Message:

----------------------------------------------------------

Assert Failed

File:  CvCity.cpp
Line:  8328
Expression:  getBonusGoodHappiness() >= 0
Message:

----------------------------------------------------------

Assert Failed

File:  CvArea.cpp
Line:  864
Expression:  getNumAIUnits(eIndex1, eIndex2) >= 0
Message:

----------------------------------------------------------
Assert Failed

File:  CvPlayerAI.cpp
Line:  17653
Expression:  eIndex >= 0
Message:  eIndex is expected to be non-negative (invalid Index)

----------------------------------------------------------Assert Failed

File:  CvPlayerAI.cpp
Line:  17645
Expression:  eIndex >= 0
Message:  eIndex is expected to be non-negative (invalid Index)

----------------------------------------------------------
Assert Failed

File:  CvPlayerAI.cpp
Line:  17656
Expression:  AI_getNumAIUnits(eIndex) >= 0
Message:

----------------------------------------------------------
Assert Failed

File:  CvInfos.cpp
Line:  2674
Expression:  i > -1
Message:  Index out of bounds

----------------------------------------------------------Assert Failed

File:  CvPlayerAI.cpp
Line:  12973
Expression:  eUnitAI != NO_UNITAI
Message:  UnitAI is not assigned a valid value

----------------------------------------------------------Assert Failed

File:  CvPlayerAI.cpp
Line:  2374
Expression:  false
Message:

----------------------------------------------------------
Assert Failed

File:  CvCity.cpp
Line:  9180
Expression:  getOccupationTimer() >= 0
Message:

----------------------------------------------------------

Assert Failed

File:  CvCity.cpp
Line:  8468
Expression:  getHurryAngerTimer() >= 0
Message:

----------------------------------------------------------
Assert Failed

File:  CvTeam.cpp
Line:  7510
Expression:  eTeam < MAX_CIV_TEAMS
Message:  eTeam is expected to be within maximum bounds (invalid Index)

----------------------------------------------------------
Assert Failed

File:  CvUnit.cpp
Line:  12849
Expression:  getMoves() >= 0
Message:

----------------------------------------------------------
Assert Failed

File:  CvGlobals.cpp
Line:  3764
Expression:  strcmp(szType, "NONE")==0 || strcmp(szType, "")==0
Message:  info type PROJECT_ASCENSION not found, Current XML file is: (None)

----------------------------------------------------------
Assert Failed

File:  CvUnit.cpp
Line:  3972
Expression:  canMoveOrAttackInto(pPlot) || isMadeAttack()
Message:

----------------------------------------------------------
Assert Failed

File:  CvGame.cpp
Line:  5821
Expression:  eIndex >= 0
Message:  eIndex is expected to be non-negative (invalid Index)

----------------------------------------------------------
Assert Failed

File:  d:\dev\civ\repos\mnai\cvgamecoredll\CvPlayerAI.h
Line:  25
Expression:  ePlayer != NO_PLAYER
Message:  Player is not assigned a valid value

----------------------------------------------------------
Assert Failed

File:  CvUnitAI.cpp
Line:  61
Expression:  AI_getUnitAIType() != NO_UNITAI
Message:  AI_getUnitAIType() is not expected to be equal with NO_UNITAI

----------------------------------------------------------
Assert Failed

File:  CvArea.cpp
Line:  861
Expression:  eIndex2 >= 0
Message:  eIndex2 is expected to be >= 0

----------------------------------------------------------
Assert Failed

File:  CvArea.cpp
Line:  851
Expression:  eIndex2 >= 0
Message:  eIndex2 is expected to be >= 0

----------------------------------------------------------
Assert Failed

File:  CvArea.cpp
Line:  864
Expression:  getNumAIUnits(eIndex1, eIndex2) >= 0
Message:

----------------------------------------------------------
Assert Failed

File:  CvPlayerAI.cpp
Line:  17653
Expression:  eIndex >= 0
Message:  eIndex is expected to be non-negative (invalid Index)

----------------------------------------------------------
Assert Failed

File:  CvPlayerAI.cpp
Line:  17645
Expression:  eIndex >= 0
Message:  eIndex is expected to be non-negative (invalid Index)

----------------------------------------------------------
Assert Failed

File:  CvPlayerAI.cpp
Line:  17656
Expression:  AI_getNumAIUnits(eIndex) >= 0
Message:

----------------------------------------------------------
Assert Failed

File:  CvGlobals.cpp
Line:  3764
Expression:  strcmp(szType, "NONE")==0 || strcmp(szType, "")==0
Message:  info type BUILDING_LION_CAGE not found, Current XML file is: xml\Units/CIV4UnitInfos.xml

----------------------------------------------------------
I got had couple asserts I did not include because I easily tracked them down to two typos in python referencing equipment promotions.

Those looked a lot like the BUILDING_LION_CAGE or PROJECT_ASCENSION asserts, which I have not tracked down to any source and which have happened only once each.

The Lion Cage one was particularly strange because it happened when loading a saved game but did not happened after I closed the same and loaded that same saved mane without changing any code.


I realized my earlier issue with everything miscasting was due to the code I added to help the Nullstone Citadel and Nullstone Golems interfere with casters nearby. It was supposed to add a promotion that gives a +100 miscast chance only when within range of the Nullstone Golem or Citadel and otherwise remove it, but I got True and False switched so it was always giving the promotion to every unit, This would have been obvious had I not set the promotion as its own next level in order to hide it from view.
 
Last edited:
I am pretty much ready to release a new update, but I keep getting some odd asserts when loading a saved game or sometimes when starting a new game.

The asserts claim that some info type that cannot be read. Usually it is a building, but never the same building twice, even when loading the same saved game. Sometimes it stops a file like CIV4UnitInfos.xml from loading correctly, but usually causes no problems. Reloading the saved game tends to return no such assert, or sometimes a similar assert for a different info type.

Sometimes the asserts show the info type as containing a missing Unicode character box.

What is going on here?


Spoiler :
Code:
Assert Failed

File:  CvArtFileMgr.cpp
Line:  175
Expression:  false
Message:  get##name##ArtInfo: ART_DEF_UILDING_WYRMHOLD_VAULT was not found
(It did not copy paste right but there was a box for the missing character where a B should be.)
----------------------------------------------------------
Assert Failed

File:  CvGlobals.cpp
Line:  3764
Expression:  strcmp(szType, "NONE")==0 || strcmp(szType, "")==0
Message:  info type BUILDING_HIPPODROME not found, Current XML file is: xml\Units/CIV4UnitInfos.xml

----------------------------------------------------------

Assert Failed

File:  CvGlobals.cpp
Line:  3764
Expression:  strcmp(szType, "NONE")==0 || strcmp(szType, "")==0
Message:  info type BUILDING_TEMPLE_OF_LEAVES not found, Current XML file is: xml\Units/CIV4UnitInfos.xml

----------------------------------------------------------


Assert Failed

File:  CvGlobals.cpp
Line:  3764
Expression:  strcmp(szType, "NONE")==0 || strcmp(szType, "")==0
Message:  info type BUILDING_PANTHER_CAGE not found, Current XML file is: xml\Units/CIV4UnitInfos.xml

----------------------------------------------------------


Assert Failed

File:  CvGlobals.cpp
Line:  3764
Expression:  strcmp(szType, "NONE")==0 || strcmp(szType, "")==0
Message:  info type CIVILIZAION_BALSERAPHS not found, Current XML file is: xml\Units/CIV4SpellInfos.xml
(It did not copy paste right but there was a box for the missing character where a T should be.)
----------------------------------------------------------
 
Which files would you like me to upload? Should I just go ahead and release the mod as it is now?

I just used Winmerge to delete the untouched FfH2 files put the rest in the .zip and .exe like I usually do. The setup is 55 mb and the archive almost 77 mb this time, larger than previous versions because I included a little more art (mostly copies of buttons that do not have spaces in the name so they so not show up as pink squares in mouseovers, and course 21 species of Dragon) and lot of longer pedia entries (such as including all of Kael's "Fideism" and "Ashes of Brigdarrow" stories, which are respectively 56 and 77 pages long.)

Edit: I just uploaded the new version of my modmod. When I follow the links from my signature they say there are not currently any available mirrors for downloading, but that should change within a few hours. (I expected this and so use tomorrow's date instead of today's as the version identifier.)
 
Last edited:
Oddly enough, I am not getting any such asserts anymore either. After running the installer that I posted the game seems much less sluggish too. I have no idea why, but I'm not complaining.
 
I am pretty sure the default dll in the installer is the assert DLL, both because it is larger than the one I remaned CvGameCoreDLL_normal.dll and because when I just added the Sawol Cavea equipment in worldbuilder I still got these asserts:
Spoiler :
Code:
Assert Failed

File:  CvUnitAI.cpp
Line:  61
Expression:  AI_getUnitAIType() != NO_UNITAI
Message:  AI_getUnitAIType() is not expected to be equal with NO_UNITAI

----------------------------------------------------------
Assert Failed

File:  CvArea.cpp
Line:  861
Expression:  eIndex2 >= 0
Message:  eIndex2 is expected to be >= 0

----------------------------------------------------------
Assert Failed

File:  CvArea.cpp
Line:  851
Expression:  eIndex2 >= 0
Message:  eIndex2 is expected to be >= 0

----------------------------------------------------------
Assert Failed

File:  CvArea.cpp
Line:  864
Expression:  getNumAIUnits(eIndex1, eIndex2) >= 0
Message:

----------------------------------------------------------
Assert Failed

File:  CvPlayerAI.cpp
Line:  17653
Expression:  eIndex >= 0
Message:  eIndex is expected to be non-negative (invalid Index)

----------------------------------------------------------
Assert Failed

File:  CvPlayerAI.cpp
Line:  17645
Expression:  eIndex >= 0
Message:  eIndex is expected to be non-negative (invalid Index)

----------------------------------------------------------
Assert Failed

File:  CvPlayerAI.cpp
Line:  17656
Expression:  AI_getNumAIUnits(eIndex) >= 0
Message:

----------------------------------------------------------
I thought I remembered getting those asserts whenever any piece of equipment was created, but the first several items that I just tried adding did not give me any asserts. I think it may only be those with <DefaultUnitAI/> instead of <DefaultUnitAI>UNITAI_UNKNOWN</DefaultUnitAI>

edit: I just replaced every instance of <DefaultUnitAI/> with <DefaultUnitAI>UNITAI_UNKNOWN</DefaultUnitAI> and then tried adding every unit listed under Items in worldbuilder. None give me any asserts anymore.

Now that I think of it I don't believe I ran any test games using the release DLL, as I renamed them as soon as I copied them into my modmod at the start of the merging process.
 
Last edited:
I just realized that the last time I posted about bugs I put it in Tholal's bug thread instead of here. I'm copying it here to make sure you see it and hope the next update can fix things.
In my modmod I recently added several new religions that are spread/founded by building certain buildings, using BtS tags like these:
Code:
           <ReligionChanges>
               <ReligionChange>
                   <ReligionType>RELIGION_FOXMEN</ReligionType>
                   <iReligionChange>1</iReligionChange>
               </ReligionChange>
           </ReligionChanges>

(The buildings are mostly wonders, but Sirona's cult the Brotherhood of Wardens may be spread by either the Shrine of Sirona or the Elohim's Chancel of the Guardians and Ceridwen's cult the Chain Breakers can be foundd by either The Nexus or a Sheim Planar Gate.)


This works fine in the regular game, but I found that when I purchase such wonders in Worldbuilder that the religion does not get spread.

I tried adding python code in CvEventManager.py under def onBuildingBuilt(self, argsList), but found that this does not do anything in worldbuilder either.


Speaking of worldbuilder, I am reminded of a couple other issues.

When you add and then remove a unit with a positive <iInstanceCostModifier>, the refund you get for removing it is greater than the units original cost. (I used to make settlers cost more the more you have, but removed that feature because of this bug. More recently I decided to use the same mechanic to make arcane units more expensive the more you have, to represent them competing for a limited supply of mana.)


In my modmod I also use pLoopPlot.setFoundDisabled(True) to prevent anyone from building cities too close to Letum Frigus, as my version of the White Hand ritual transforms that unique feature into a city and I don't want two cities on adjacent tiles. This functions does not however stop anyone from placing a city right next to Letum Frigus in an advanced Start game.


I really wish the AI understood pPlot.setFoundDisabled(True) better. I find that when a player starts too close to Letum Frigus to construct a city with its settler in its initial position that it does not move to a tile where cities can be built but just stays doing nothing for the whole game.


When I tried giving units representing the Matronae (archangels who have obtained Gems of Creation giving them infinite supplies of mana) 1,000,000 strength each, I found that instead of being able to defeat anything that they would lose every battle. When I dialed their strength back to 10,000 things seemed fine.


I've heard reports that some of the revolution mod stability tags for buildings seem to be doing the opposite of what they should.


A significant minority of the time when I load the game I still get an assert saying it cannot find some random xml entry (usually a building, although I remember it being Faeryl's leaderhead art once recently). The Assert usually shows the entry as containing one unreadable character in place of a normal letter. If I say to ignore the issue the game seems to work fine except that item not showing up properly. If I close the game and start it again the issue invariably disappears.

The latest one is for a unit
Code:
Assert Failed

File:  CvGlobals.cpp
Line:  3764
Expression:  strcmp(szType, "NONE")==0 || strcmp(szType, "")==0
Message:  info type UNIT_DISCIPLE_THE_ASHEN_VIL not found, Current XML file is: xml\Units/CIV4UnitInfos.xml

----------------------------------------------------------
[code]
It did not copy the odd character, but there was a narrow box between the V and the I in Veil where the E should be.
 
Top Bottom