[MOD] MagisterModmod

Hey Magister, could you upload the February 1st 2018 archive? I'm reinstalling CIV4, and I only seem to have the setup versions.
 
Hello Magister, how are you doing? Great to see you are still working on your Modmod.

I debugged FireBlaze's bug report and found the following bugs:
  • The Mage-related bug is an engine bug, the AI code doesn't work well with negative <iResistModify>. The bug is fixed in my unofficial build.
  • The long turn times are (at least partly) caused by SPELL_DESPAIR having empty <iRange>, which you already mentioned.
  • PROMOTION_ARCANE and PROMOTION_SUMMONER have HERO instead of PROMOTION_HERO as PromotionPrereqOr2
  • In CvEventManager, line 3466, some element is removed from some list that doesn't actually contain said element.
  • It seems you somewhere have more PrereqBonuses than the DLL allows. I'm not sure where exactly, but I might investigate that.
If you have any trouble with things you suspect going wrong in the DLL, I'm happy to help.

@FireBlaze and everybody else: Grabbing the fixes compiled by Woad here and (only!) the dll from here should fix some of the issues.
 
Thanks for posting fixes lfgr, am a bit confused about installing the dll. Im guessing its the CvGameCoreDLL from your bitbucket repo, but am unsure where to install it. There is no folder like that in Magisters Modmod folder.
My only idea was that magisters modmod uses the files of another mod, MNAI, and so I should place it in the MNAI folder? Sorry its been quite awhile since I've installed Civ mods.
 
Thanks, this version are a lot more stable, have had 2 games finished, 2 freezing at about turn 200 on epic, so a great improvement.
 
Had two quick games go through without any CtDs or freezes. Couple long turn loads and errors in the event log, but overall much more stable and smooth!

edit: an option similar to this (https://forums.civfanatics.com/threads/ffh2-total-victory-mode.213973/) would be sick now that we've got stability for the whole game, I wanna explore more of the mod and play with all the toys :v

edit 2: is it intended that some low level permanent summons (skeletons, severed souls) cost unit upkeep? I kind of get the reasoning behind it, but -- and I'm a noob so I may be missing a lot of strategies -- it makes it super difficult to keep up on Immortal (which I just swapped up to from Monarch) when my eco crashes from trying to stave off the hordes with my own hordes of Skeletons.
 
Last edited:
Mercurians weren't receiving angels in when good units died. I used a freshly-downloaded copy so I don't think this is just me, but I don't see any other complaints either. I should mention that I'm using the fixes that have been discussed recently [1][2]. Alternatively, I might have goofed something.

I was frequently getting the following error message: "Error in unitKilled event handler <bound method CvEventManager.onUnitKilled of <BugEventManager.BugEventManager instance at 0x1356E788>>", and PythonErr contained the following lines:
Code:
Traceback (most recent call last):
  File "BugEventManager", line 361, in _handleDefaultEvent
  File "CvEventManager", line 4215, in onUnitKilled
  File "CustomFunctions", line 3114, in doAfterlife
  File "CustomFunctions", line 3106, in findAfterlife
NameError: global name 'iReligion' is not defined
I added iReligion= unit.getReligion() to findAfterlife and spawning angels works again. I've attached the PythonDbg and PythonErr logs, as well as CustomFunctions.py with the aforementioned change.

My programming experience is next to none, so this might be a sloppy fix. However, I thought that it would be good to document my results for anyone with the same problem who comes searching.
 

Attachments

  • CustomFunctions.zip
    34.1 KB · Views: 233
  • Logs.zip
    25.3 KB · Views: 232
Last edited:
Had two quick games go through without any CtDs or freezes. Couple long turn loads and errors in the event log, but overall much more stable and smooth!
This encouraged me to redownload the mod.
I started a new game and had no stability problems yet. One issue still remained. The AI's leaving their Capitol empty or poorly defended. The barbarians took out 1 Civ on turn 3. The Sheiam took out the Dwarves on turn 31. Both before I met either. In fact I haven't met anyone yet.
Part of the problem is how close their starts were. By entering Worldbuilder I see that Sheiam and the Dwarves started only 7 tiles apart. I am on a Standard size map with 6 foes. I am 15 tiles from the Dwarves old Capitol. 12 IIRC from my start location.
 
edit: an option similar to this (https://forums.civfanatics.com/threads/ffh2-total-victory-mode.213973/) would be sick now that we've got stability for the whole game, I wanna explore more of the mod and play with all the toys :v

That is out of scope of what I want to do for now, and I believe Magister doesn't do DLL changes. I mean, while it's cool, you can always just disable all victory conditions except time, right? :)

My programming experience is next to none, so this might be a sloppy fix. However, I thought that it would be good to document my results for anyone with the same problem who comes searching.
While I don't really play MagisterModmod, I'm sure it is appreciated. At the very least it may help Magister to fix the bug.

The AI's leaving their Capitol empty or poorly defended.
Is this something you experience in every game? If this is not present in MNAI, it might be caused by a combination of Magister's changes and MNAI's AI. I usually play Extramodmod and never noticed something like this.
 
I just uploaded a new update which includes the DLL from lfgr's mnai-2.71-ubf1.

I believe all of the bugs which have been mentioned recently, and were not fixed by that DLL, were things I fixed on my end back in February.

There are a few minor changes as well as bug fixes, but not many. Most of those changes were made before Valentine's Day and I don't clearly recall whether they made it into my last release or not. I have not had a lot of time for modding since then, and most of my modding time was spent trying in vain to track down the cause of crashes lfgr's DLL fixed for me. I can say there are some tweaks to The White Hand code, the Undercouncil gained the power to ban Sun mana, and that I included custom icons for Death (Arawn) 1-3 (adding Roman numerals in the lower corner) so that the different levels of that spell sphere can be more easily told apart.
 
Is this something you experience in every game? If this is not present in MNAI, it might be caused by a combination of Magister's changes and MNAI's AI. I usually play Extramodmod and never noticed something like this.
That was a new game. I hadn't been on in a while. I'll try creating a new game.

Just to make sure I downloaded the most current version with the newest MNAI, which link should I follow,
MagisterModmod for FfH2 in MagisterCultuum's Signature?
or the first "here" from his Sig comment "You may download the installer here or the archive here." ?
 
Last edited:
So far so good.
The Kuriotates got killed off on turn 83. I haven't met them, so I don't know how big they got. Probably just their one large city.
Turn 160 and no one else has died. So it appears to be good.
 
Last edited:
Are warp bubbles supposed to have a damaging effect like ICBMs or is 'nuke mode' just for map-wide transport?
 
Warp Bubbles have Dimensional Affinity and can kill units, both in regular combat and through Nuke Mode. They don't have effect of spreading fallout like ICBMs in vanilla civ though.

---

I just experienced my first crash to desktop bug with the new release. It was not related to the crash lfgr fixed. The logs did help me track down this problem.
Code:
Traceback (most recent call last):
  File "BugEventManager", line 361, in _handleDefaultEvent
  File "CvEventManager", line 4069, in onUnitCreated
  File "CvEventManager", line 4567, in onUnitPromoted
MemoryError: Stack overflow

I think it was caused by the Balseraph Puppets summoning more puppets without limit. The BBAILog file ended in "Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of Puppet of ....." repeated way to many times for me to type.

That led me to look in CvSpellInterface.py under def effectPuppet(pCaster). I forget whether this was before or after my last release, but I had changed things so that a puppet's name gets updated each turn to identify the puppet's summoner and so that the Puppet's promotions are updated based on the summoner's current spell spheres.

The problem is that I also made the Puppet promotion pass the Puppet promotion on as a Summon Perk, instead of making the summons of a puppet Illusions like they used to be.

effectPuppet(pCaster) was getting called for those summons of summons many levels deep. My rival in this game had a mage with both ice affinity and death affinity. It summoned a puppet which then summoned Skeletons. Those Skeletons gained the Death 1&2 and Ice 1&2 promotions. They started summoning Ice Elementals, which summoned Spectres, which summoned Ice Elementals, which summoned Spectres, which summoned Ice Elementals, which summoned Spectres, ad infinitum. The DLL stops a unit from summoning more of itself, but two different summon types can summon each other back and forth without limit.

I believe I have fixed the problem by putting all the code that checks promotions within a conditional to make sure it only applies to Puppet units rather than all units with the Puppet racial promotion. I have not tested this fix extensively yet, but would recommend others do the same.

Spoiler :

Code:
def effectPuppet(pCaster):
   iSummoner = pCaster.getSummoner()
   if iSummoner != 1:
       pPlayer = gc.getPlayer(pCaster.getOwner())
       pSummoner = pPlayer.getUnit(iSummoner)

       pCaster.setName("Puppet of " + pSummoner.getName())

       if pCaster.getUnitType() == gc.getInfoTypeForString('UNIT_PUPPET'):

           iChanneling1 = gc.getInfoTypeForString('PROMOTION_CHANNELING1')
           iChanneling2 = gc.getInfoTypeForString('PROMOTION_CHANNELING2')
           iChanneling3 = gc.getInfoTypeForString('PROMOTION_CHANNELING3')

           bChanneling1 = pSummoner.isHasPromotion(iChanneling1)
           bChanneling2 = pSummoner.isHasPromotion(iChanneling2)
           bChanneling3 = pSummoner.isHasPromotion(iChanneling3)

           for iProm in [   iChanneling1, iChanneling2, iChanneling3,
                           gc.getInfoTypeForString('PROMOTION_DRUIDIC'),
                           gc.getInfoTypeForString('PROMOTION_UNHOLY_TAINT'),
                           gc.getInfoTypeForString('PROMOTION_EXTENSION1'),
                           gc.getInfoTypeForString('PROMOTION_EXTENSION2'),
                           gc.getInfoTypeForString('PROMOTION_EXTENSION3'),
                           gc.getInfoTypeForString('PROMOTION_ARCANE'),
                           gc.getInfoTypeForString('PROMOTION_SUMMONER'),
                           gc.getInfoTypeForString('PROMOTION_SUNDERED'),
                           gc.getInfoTypeForString('PROMOTION_TWINCAST')
                   ]:
               pCaster.setHasPromotion(iProm, pSummoner.isHasPromotion(iProm))

           listMana = [
                       (gc.getInfoTypeForString('PROMOTION_AIR1'), gc.getInfoTypeForString('PROMOTION_AIR2'), gc.getInfoTypeForString('PROMOTION_AIR3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_AIR')),
                       (gc.getInfoTypeForString('PROMOTION_BODY1'), gc.getInfoTypeForString('PROMOTION_BODY2'), gc.getInfoTypeForString('PROMOTION_BODY3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_BODY')),
                       (gc.getInfoTypeForString('PROMOTION_CHAOS1'), gc.getInfoTypeForString('PROMOTION_CHAOS2'), gc.getInfoTypeForString('PROMOTION_CHAOS3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_CHAOS')),
                       (gc.getInfoTypeForString('PROMOTION_DEATH_ARAWN1'), gc.getInfoTypeForString('PROMOTION_DEATH_ARAWN2'), gc.getInfoTypeForString('PROMOTION_DEATH_ARAWN3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_DEATH_ARAWN')),
                       (gc.getInfoTypeForString('PROMOTION_DEATH1'), gc.getInfoTypeForString('PROMOTION_DEATH2'), gc.getInfoTypeForString('PROMOTION_DEATH3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_DEATH')),
                       (gc.getInfoTypeForString('PROMOTION_DIMENSIONAL1'), gc.getInfoTypeForString('PROMOTION_DIMENSIONAL2'), gc.getInfoTypeForString('PROMOTION_DIMENSIONAL3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_DIMENSIONAL')),
                       (gc.getInfoTypeForString('PROMOTION_EARTH1'), gc.getInfoTypeForString('PROMOTION_EARTH2'), gc.getInfoTypeForString('PROMOTION_EARTH3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_EARTH')),
                       (gc.getInfoTypeForString('PROMOTION_ENCHANTMENT1'), gc.getInfoTypeForString('PROMOTION_ENCHANTMENT2'), gc.getInfoTypeForString('PROMOTION_ENCHANTMENT3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_ENCHANTMENT')),
                       (gc.getInfoTypeForString('PROMOTION_ENTROPY1'), gc.getInfoTypeForString('PROMOTION_ENTROPY2'), gc.getInfoTypeForString('PROMOTION_ENTROPY3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_ENTROPY')),
                       (gc.getInfoTypeForString('PROMOTION_FIRE1'), gc.getInfoTypeForString('PROMOTION_FIRE2'), gc.getInfoTypeForString('PROMOTION_FIRE3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_FIRE')),
                       (gc.getInfoTypeForString('PROMOTION_FORCE1'), gc.getInfoTypeForString('PROMOTION_FORCE2'), gc.getInfoTypeForString('PROMOTION_FORCE3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_FORCE')),
                       (gc.getInfoTypeForString('PROMOTION_ICE1'), gc.getInfoTypeForString('PROMOTION_ICE2'), gc.getInfoTypeForString('PROMOTION_ICE3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_ICE')),
                       (gc.getInfoTypeForString('PROMOTION_LAW1'), gc.getInfoTypeForString('PROMOTION_LAW2'), gc.getInfoTypeForString('PROMOTION_LAW3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_LAW')),
                       (gc.getInfoTypeForString('PROMOTION_LIFE1'), gc.getInfoTypeForString('PROMOTION_LIFE2'), gc.getInfoTypeForString('PROMOTION_LIFE3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_LIFE')),
                       (gc.getInfoTypeForString('PROMOTION_METAMAGIC1'), gc.getInfoTypeForString('PROMOTION_METAMAGIC2'), gc.getInfoTypeForString('PROMOTION_METAMAGIC3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_METAMAGIC')),
                       (gc.getInfoTypeForString('PROMOTION_MIND1'), gc.getInfoTypeForString('PROMOTION_MIND2'), gc.getInfoTypeForString('PROMOTION_MIND3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_MIND')),
                       (gc.getInfoTypeForString('PROMOTION_NATURE1'), gc.getInfoTypeForString('PROMOTION_NATURE2'), gc.getInfoTypeForString('PROMOTION_NATURE3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_NATURE')),
                       (gc.getInfoTypeForString('PROMOTION_SHADOW1'), gc.getInfoTypeForString('PROMOTION_SHADOW2'), gc.getInfoTypeForString('PROMOTION_SHADOW3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_SHADOW')),
                       (gc.getInfoTypeForString('PROMOTION_SPIRIT1'), gc.getInfoTypeForString('PROMOTION_SPIRIT2'), gc.getInfoTypeForString('PROMOTION_SPIRIT3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_SPIRIT')),
                       (gc.getInfoTypeForString('PROMOTION_SUN1'), gc.getInfoTypeForString('PROMOTION_SUN2'), gc.getInfoTypeForString('PROMOTION_SUN3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_SUN')),
                       (gc.getInfoTypeForString('PROMOTION_WATER1'), gc.getInfoTypeForString('PROMOTION_WATER2'), gc.getInfoTypeForString('PROMOTION_WATER3'), gc.getInfoTypeForString('PROMOTION_AFFINITY_WATER'))
                   ]

           for iSpell1, iSpell2, iSpell3, iAffinity in listMana:
               if pCaster.isHasPromotion(iAffinity):
                   pCaster.setHasPromotion(iAffinity, False)
               if pSummoner.isHasPromotion(iAffinity):
                   if bChanneling1:
                       pCaster.setHasPromotion(iSpell1, True)
                   if bChanneling2:
                       pCaster.setHasPromotion(iSpell2, True)
                   if bChanneling3:
                       pCaster.setHasPromotion(iSpell3, True)
               else:
                   pCaster.setHasPromotion(iSpell1, pSummoner.isHasPromotion(iSpell1))
                   pCaster.setHasPromotion(iSpell2, pSummoner.isHasPromotion(iSpell2))
                   pCaster.setHasPromotion(iSpell3, pSummoner.isHasPromotion(iSpell3))

edit: just found another bug in CvSpellInterface.py under effectDefrock(pCaster).
Code:
Traceback (most recent call last):

  File "CvSpellInterface", line 32, in canCast

  File "<string>", line 0, in ?

  File "CvSpellInterface", line 1055, in effectDefrock

NameError: global name 'pPlot' is not defined
ERR: Python function canCast failed, module CvSpellInterface
I recomend you add the line pPlot = pCaster.plot()
Spoiler :
Code:
def effectDefrock(pCaster):
   if not pCaster.isAvatarOfCivLeader():
       iPlayer = pCaster.getOwner()
       pPlayer = gc.getPlayer(iPlayer)
       sName = "<color=%d,%d,%d,%d>%s</color>" %(pPlayer.getPlayerTextColorR(), pPlayer.getPlayerTextColorG(), pPlayer.getPlayerTextColorB(), pPlayer.getPlayerTextColorA(), pCaster.getName() )
       iDivine = gc.getInfoTypeForString('PROMOTION_DIVINE')
       iLuo = gc.getInfoTypeForString('UNIT_LUONNOTAR')
       if pCaster.isHasPromotion(iDivine):
           if pCaster.getUnitType() == iLuo:
               pCaster.setHasPromotion(iDivine, False)
           else:
               CyInterface().addMessage(iPlayer, True, 25, CyTranslator().getText("TXT_KEY_MESSAGE_DEFROCK", (sName, )), '', InterfaceMessageTypes.MESSAGE_TYPE_INFO, pCaster.getButton(), gc.getInfoTypeForString('COLOR_RED'), pCaster.getX(), pCaster.getY(), True, True)
               pCaster.kill(True, PlayerTypes.NO_PLAYER)

       elif pPlayer.getStateReligion() != -1:
           pCaster.setHasPromotion(gc.getInfoTypeForString('PROMOTION_LOYALTY'), False)
           pCaster.setHasPromotion(gc.getInfoTypeForString('PROMOTION_REBELLIOUS'), True)

       if pCaster.isBarbarian():

           pPlot = pCaster.plot()

           if pPlot.isOwned():
               iPlayerP = pPlot.getOwner()
               pPlayerP = gc.getPlayer(iPlayerP)
               if pPlayerP.getCivilizationType() == gc.getInfoTypeForString('CIVILIZATION_GRIGORI'):
                   CyInterface().addMessage(iPlayerP, True, 25, CyTranslator().getText("TXT_KEY_MESSAGE_DEFROCK_BARB_TO_GRIGORI", (sName, )), '', InterfaceMessageTypes.MESSAGE_TYPE_INFO, pCaster.getButton(), gc.getInfoTypeForString('COLOR_GREEN'), pCaster.getX(), pCaster.getY(), True, True)
                   newUnit = pPlayerP.initUnit(pCaster.getUnitType(), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_SOUTH)
                   newUnit.convert(pCaster)
                   newUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_REBELLIOUS'), False)
                   newUnit.setReligion(gc.getInfoTypeForString('RELIGION_CHILDREN_OF_THE_ONE'))
 
Last edited:
I remember one game where I was attacked by a stack of casters protected by a stack of those hills2 Kilmorph dwarves and their puppets. The puppets kept summoning tar demons. This multiplied their number of units quickly. Even if I beat the tar demon, I was wounded and the puppet would just summon another at full strength. They could have a relatively small stack, remain fully fortified on a forest hill, yet pillage my tiles with puppets and summons. They can definitely wear us down with that strategy.
I can't imagine puppets summoning more puppets and those being allowed to summon more puppets, etc. One puppet per caster with puppets not being allowed to create puppets does seem the reasonable correction here. Good catch. I'll be looking for that update. :)
 
I just uploaded another update.

This includes lfgr's mnai-2.71-ubf1.1, plus a few more minor bug fixes.

I added custom icons for the greater versions of spell buildings (just adding a gold star to the upper corner), so that in a city's mouse over you can tell which version is present. I also changed the icon for the Aphotic Throne and Tower of Mortality, desaturating the images, so they can be more easily distinguished from the Eyes and Ears Network and Tower of Necromancy, respectively.

The Gift Vampirism code was tweaked to pass on more of the Sire's promotions to its spawn. Vampires who get their gift from a Governor's Manor in city X instead of from another unit are called "Spawn of Lord X" instead of "Spawn of X's Governor," as that sounds less clunky and more Feudal.

Agents of Esus can gain xp a bit faster from Embezzlement and Disruption, so leveling them up to become Whisperers is a little easier.

The Embezzlement ability is guaranteed to grant at least 1 gold even in cities with no commerce.

The Changeling promotion now grants +80 Withdrawal and +2 Poison Damage instead of Shadow Affinity. Assume True Form removes affinity promotions, so Gibbon Goetia cannot become unstoppable just by shifting into a mage with each affinity.

The Lichdom spell is removed, as I found it worked just as well to let archmages and archmage heroes upgrade to Liches in cities with Towers of Necromancy. Liches are a bit stronger but require Malevolent Designs, Strength of Will, Pass through the Ether, Death Mana, and a Tower of Necromancy.

The greater version of Guardian Vines starts with Divine, so it can cast Treetop Defense to fortify itself and other defenders (and can also summon animals if you have FoL as your state religion.)
----
 
Food for thought: Maybe captured Workers and Slaves should get the Rebellious promotion? Imo it doesn't make sense that I can abduct a few elves from their homes and have them build roads at the edge of my borders without any of my military units nearby to guard them, and they don't use this obvious window of opportunity to try to escape. I think there should be a price to stealing workers, and them having a chance to become barbarian, forcing you to always have a military escort around them to recapture them while "free" workers you build yourself don't have this same restriction, works both mechanically and realistically.
 
Top Bottom