Quick Modding Questions Thread

Hi, Can someone tell me what is wrong with this code? The idea is that commanded units are going to follow their leader when he leads them in a charge, i.e. they immediately attack after him. The forced attack is working but the weakest unit is attacking first, rather than the strongest.

The messages I've included in the code show that the order of the pAttacker units in my trial is: Champion, Axeman and then Warrior but the combats are resolved in the order Warrior, Axeman, Champion. How do I fix this or what am I missing?
Spoiler :
Code:
def doLeadCharge(argsList):
   caster, pTargetPlot = argsList
   iAPlayer = caster.getOwner()
   pAPlayer = gc.getPlayer(iAPlayer)
   pPlot = caster.plot()

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

   iPlayer = caster.getOwner()
   pPlayer = gc.getPlayer(iPlayer)
   iX = pPlot.getX()
   iY = pPlot.getY()
   eTeam = pPlayer.getTeam()

   iAcceptableDamage = 25 # Don't want to force massively injured units to attack.

   iMod = caster.getModifier() + caster.getLeadershipModifier()

   lAttackers = []
   lStr = []

   for i in range(pPlot.getNumUnits()):
       pLoopUnit = pPlot.getUnit(i)
       if (caster.getOwner() == pLoopUnit.getOwner() and pLoopUnit.getCommandingUnit() == caster.getID()):
           if pLoopUnit.movesLeft() > 0 and pLoopUnit.canAttack():
               if not pLoopUnit.isMadeAttack() or pLoopUnit.isBlitz():
                   if pLoopUnit.getDamage() < iAcceptableDamage:
                       if pLoopUnit.canMoveOrAttackInto(pTargetPlot, False):
                      
                           iLoopStr = (pLoopUnit.baseCombatStr() * (100 + pLoopUnit.getExtraCombatPercent()))
                           iLoopStr = (pLoopUnit.getExperience() * 15 + iLoopStr)
                           lAttackers.append(pLoopUnit)
                           lStr.append(iLoopStr)

   lStr.sort()
   lStr.reverse()
   if len(lAttackers) > 0:
       for k in range (len(lStr)):
#       for k in range (len(lStr)-1, -1,-1):    Alternative, if did not use lStr.reverse()

           iStr = lStr[k]
           CyInterface().addMessage(CyGame().getActivePlayer(),True,10,"iStr is %d" %(iStr),'',0,'',ColorTypes(8),-1,-1,True,True)

           for m in range (len(lAttackers)):
               pAttacker = lAttackers[m]
               iAttackerStr = (pAttacker.baseCombatStr() * (100 + pAttacker.getExtraCombatPercent()) + pAttacker.getExperience() * 15)
               sDesc = pAttacker.getName()
               CyInterface().addMessage(CyGame().getActivePlayer(),True,10,"Attacking unit is %s" %(sDesc),'',0,'',ColorTypes(8),-1,-1,True,True)
               CyInterface().addMessage(CyGame().getActivePlayer(),True,10,"iAttackerStr is %d" %(iAttackerStr),'',0,'',ColorTypes(8),-1,-1,True,True)

               if iStr == iAttackerStr:
                   pAttacker.setFollowingCharge(True)
                   iCharging = pAttacker.getCharging()
                   pAttacker.changeExtraCombatPercent(iCharging)
                   pAttacker.attack(pTargetPlot, False)
                   pAttacker.changeExtraCombatPercent(-iCharging)
                   pAttacker.setFollowingCharge(False)
                   lAttackers.remove(pAttacker)
                   break
   caster.setHasPromotion(gc.getInfoTypeForString('PROMOTION_LEADING_CHARGE'), False)

# Idea is that all of the able commanded units will follow the leader onto the attacked tile

Edit: I managed to fix this issue. I moved the code that triggers the python from resolveCombat to updateCombat in CvUnit.cpp.
 
Last edited:
Hello team, a quick (maybe rethorical) one if I may...

Is there a way to make the color of the "X" in a checkbox something else than green ? I've seen a red "X" in a MOD somewhere but I don't know if it's a theme thing (therefore all checkbox are with a red cross) or we could have 3 values, empty/green/red
Similar question : can the text of the checkbox be changed into another color ?
I saw a tutorial today about changing the color of the End Turn button (the green one near the minimap) via the civ4 theme files (.thm files, which are located in Resource, not Assets). You just have to find the right color variable and change its RGB values. I assume anything unaffected by python or sdk interface changes, like checkboxes, is similarly handled by those .thm files.

Unfortunately that'd mean three different checkboxes are basically impossible AFAIK
 
I'm not sure if this counts as "modding" or is in the correct forum, but when I patch Civ 4 BTS up to patch 3.19, do I need to install all the previous patches first, or does installing patch 3.19 make all those changes by itself?
 
I'm not sure if this counts as "modding" or is in the correct forum, but when I patch Civ 4 BTS up to patch 3.19, do I need to install all the previous patches first, or does installing patch 3.19 make all those changes by itself?
Yes, it doesn't count as modding but also yes, 3.19 applies all previous changes.
Have fun playing Civ4 :)
 
i have a problem with my mod.
In turn 69 I established in python that the USA would join the Allies and at this point the USA in the Alinat team gets a research boost so that 3 to 5 technologies are free (1 turn). I really don't know where this is happening from.

Code:
<Define>
<DefineName>TECH_COST_EXTRA_TEAM_MEMBER_MODIFIER</DefineName>
<iDefineIntVal>350</iDefineIntVal>
</Define>
<Define>
<DefineName>TECH_COST_TOTAL_KNOWN_TEAM_MODIFIER</DefineName>
<iDefineIntVal>350</iDefineIntVal>
</Define>
 
Does anyone know how to trigger a rangestrike or airstrike from python (or in the DLL), as opposed to pressing the button on the interface?
Specifically, I want units to be able to cast one of a number of spells and then use the rangestrike functionality to select the tile that the spell is to affect.
 
Last edited:
Does anyone know how to trigger a rangestrike or airstrike from python (or in the DLL), as opposed to pressing the button on the interface?
Specifically, I want units to be able to cast one of a number of spells and then use the rangestrike functionality to select the tile that the spell is to affect.

I've tinkered with this quite a bit actually. So, in CvUnit you have three methods governing range strikes; one that tells if you if the unit can rangestrike at all (canRangeStrike), one that tells you if a unit on pPlot can do a range strike at coordinates iX, iY (canRangeStrikeAt), and one for doing the actual range strike itself (simply called "rangeStrike"). None of those are exposed to python but doing so would be pretty easy (see here for a refresher on how to do that).

However I think you are wanting to use the actual plot picking interface of the rangestrike, rather than the rangestrike itself, no? I.e. press spell button >> pick plot? In that case, you actually have everything you need without a DLL at all; TC01 made a great tutorial on plot picking as part of his python action buttons tutorial. If you're doing your ranged actions in SDK the methods have the same names - setInterfaceMode(INTERFACEMODE_PYTHON_PICK_PLOT) is the big one.

Note that whether you use SDK or not, the interface mode is set to call the method canPickPlot from somewhere in the python. (I think CvGameUtils?) I have had trouble with the interface thinking it needed line of sight despite my code specifically NOT requiring line of sight, and I think that's where it came from - or possibly canDoInterfaceModeAt(), which checks over canRangeStrikeAt(). Been a while since I played with the range strike code, though, so idk.
 
@Darkator: Perhaps you could add a tech in XML that has the Liberalism effect
<iFirstFreeTechs>4</iFirstFreeTechs>
and that can't be researched normally, and then give that tech to the Allied team through Python. Could name the tech something like "Tizard Mission" for flavor (based on a quick web search on the topic). Or, alternatively, a wonder with the Oracle effect. Randomizing the effect (3 to 5) would be harder to do; could use three such pseudo-techs, or launch a custom popup from Python.

If, instead, you increase their research rate somehow, then I think they'll still discover at most 1 tech per turn, so I don't see how you could let them choose 3 to 5 in one turn that way.
 
I presented the problem a bit wrong, The problem is not that the USA discovers 3-5 technologies in one turn, but that the USA has a very large increase in research for several turns and it is able to do any technology within a turn. (And it works for a few turns)


upload_2022-7-9_23-45-0.png

upload_2022-7-9_23-45-25.png

upload_2022-7-9_23-45-42.pngupload_2022-7-9_23-46-3.png

Zrzut ekranu z 2022-07-09 23-50-04.png Zrzut ekranu z 2022-07-09 23-50-37.png Zrzut ekranu z 2022-07-09 23-52-00.png


Perhaps you could add a tech in XML that has the Liberalism effect
<iFirstFreeTechs>4</iFirstFreeTechs>and that can't be researched normally, and then give that tech to the Allied team through Python. Could name the tech something like "Tizard Mission" for flavor (based on a quick web search on the topic). Or, alternatively, a wonder with the Oracle effect. Randomizing the effect (3 to 5) would be harder to do; could use three such pseudo-techs, or launch a custom popup from Python.

If, instead, you increase their research rate somehow, then I think they'll still discover at most 1 tech per turn, so I don't see how you could let them choose 3 to 5 in one turn that way.

And by the way, whether or not these XML statistics differ
Zrzut ekranu z 2022-07-09 23-53-06.png
 
@Darkator: I see. Would be nice if tech costs could be (temporarily) reduced for the Allies because increasing the research rate a lot will probably lead to a high amount of overflow carried over once the fast-research period ends. I don't think overflow can be prevented or removed through Python. Can't really apply a tech cost modifier through Python either. (I'm assuming that you prefer not to modify the DLL.) Maybe someone with more experience with Python-only modding has some clever idea?

For increasing the research rate, free buildings with a CommerceChanges or CommerceModifiers effect should work, or you could use CyTeam::setHasTech in between turns, which would avoid overflow. But I don't know when to do that exactly, and it'll have no user interface support, i.e. the UI will not show that techs take only 1 turn to research. Maybe if you'd use a building with a high research boost, but not crazily high, to avoid very high overflow? Though that could mean that the Allies will need more than 1 turn per tech if their economy is in a bad state.
 
@f1rpo I understand, it is not so bad because after some technology the situation stabilizes, but what is the difference between these two tags in GlobalDefines
zrzut-ekranu-z-2022-07-09-23-53-06-png.633579
 
They're actually quite different. The first increases the tech costs for teams with multiple members:
Spoiler :
Code:
int CvTeam::getResearchCost(TechTypes eTech) const
{
   // ...
   iCost = GC.getTechInfo(eTech).getResearchCost();
   // ...
   iCost *= std::max(0,
         ((GC.getDefineINT("TECH_COST_EXTRA_TEAM_MEMBER_MODIFIER") *
         (getNumMembers() - 1)) + 100));
   iCost /= 100;
   return std::max(1, iCost);
}
350 seems like a very high value. Anything above 100 means that the members would probably tech faster on their own.

The second Global Define is responsible for tech diffusion between teams, i.e. for a boost to research rates based on the number of known teams that already know the tech (again, 350 is a lot):
Spoiler :
Code:
int CvPlayer::calculateResearchModifier(TechTypes eTech) const
{
   int iModifier = 100;
   // ...
   int iKnownCount = 0;
   int iPossibleKnownCount = 0;
   for (int iI = 0; iI < MAX_CIV_TEAMS; iI++)
   {
       if (GET_TEAM((TeamTypes)iI).isAlive())
       {
           if (GET_TEAM(getTeam()).isHasMet((TeamTypes)iI))
           {
               if (GET_TEAM((TeamTypes)iI).isHasTech(eTech))
               {
                   iKnownCount++;
               }
           }
           iPossibleKnownCount++;
       }
   }
   if (iPossibleKnownCount > 0)
   {
       iModifier += (GC.getDefineINT("TECH_COST_TOTAL_KNOWN_TEAM_MODIFIER")
             * iKnownCount) / iPossibleKnownCount;
   }
   // ...
   return iModifier;
}
 
Thanks @Merkava120,
I now am able to select the plots from within a spell, using CyInterface().setInterfaceMode(InterfaceModeTypes.INTERFACEMODE_PYTHON_PICK_PLOT). However the cursor for selecting the plots is just a wand and the highlighting of selectable plots in green does not happen.
The canPickPlot(self, argsList) python in CvGameUtils does not seem to be getting triggered at all.

Is there a boolean I need to set somewhere to allow the canPickPlot function to be called?
Is there another setting I need to set to have the green highlighted plots (or will this be resolved when the canPickPlot is triggering correctly) instead of the little wand?
 
Thanks @Merkava120,
I now am able to select the plots from within a spell, using CyInterface().setInterfaceMode(InterfaceModeTypes.INTERFACEMODE_PYTHON_PICK_PLOT). However the cursor for selecting the plots is just a wand and the highlighting of selectable plots in green does not happen....canPickPlot(...)...does not seem to be getting triggered at all.

Ok this made me curious about what is actually going on here and I found out a bunch of stuff.

First of all, seems INTERFACEMODE_PYTHON_PICK_PLOT is not the one used for ranged attacks, for that type of interface you want the aptly-named INTERFACEMODE_RANGE_ATTACK. The other options appear to be _AIRLIFT, _NUKE, _RECON, _PARADROP, _AIRBOMB, _AIRSTRIKE, _REBASE, so you could fiddle with those too if _RANGE_ATTACK isn't exactly what you want.

Unfortunately, I think this only affects the plot you are pointing at (and the cursor), and will not highlight all selectable plots within the unit's attack range. If you want that type of highlighting, it seems to be done directly in updateColoredPlots() in CvGameInterface.cpp. The relevant part begins with if (pHeadSelectedUnit->airRange() > 0), and the lines actually applying plot coloring are

Code:
NiColorA color(GC.getInfo(GC.getColorType("YELLOW")).getColor());
color.a = 0.5f;
kEngine.fillAreaBorderPlot(kTargetPlot.getX(), kTargetPlot.getY(), color, AREA_BORDER_LAYER_RANGED);
(This is from AdvCiv so there might be differences in function names but otherwise should be the same as BTS.)

It seems the only way to activate this without doing it yourself in the SDK is to give a unit some air range. You could just set their air combat to zero and give them a range matching the spell, but that means they'd have this highlighting show up every time you select them and not just when you use the spell. (And if your spells are not hard-attached to units, some units would have range but not spells, which would be silly.) You'd also have one less-relevant problem which I shall put in a spoiler

Spoiler Line of sight problem with range highlighting :

The BTS code assumes that if a unit is not an air unit, but has air range, that it will be using line of sight to do ranged attacks. So it will only highlight tiles within the unit's range that the unit can see, even though you can still select tiles outside this radius if you modify canRangeStrikeAt() to accommodate that. AdvCiv fixes this, and it's the issue I was thinking of earlier.


You might be able to expose the fillAreaBorderPlot() function to python, but since it's a CvEngine function there might be surprises I don't know about when you try to do that. (Never exposed CvEngine functions before, this one is in CvDLLEngineIFaceBase.h, a file that makes me nervous.) However, there's nothing stopping you from calling this in the SDK with your own colors. You could also fool with addColoredPlot(), which is in the same scary file. Looks like you'll also want to hit clearAreaBorderPlots or clearColoredPlots at some point when the spell is over but that's just me guessing things. (And all of this interface stuff for spells would need to go inside updateColoredPlots() so it gets applied every "tick" or whatever.)

Is there a boolean I need to set somewhere to allow the canPickPlot function to be called?

Well, I was completely wrong about this having anything to do with line of sight, especially since it's not even called by default. But, in case you do want to call it through python, you can turn it on in PythonCallbackDefines.xml. Set USE_WHATEVER_CALLBACK to 1.
 
Does anyone know where a tutorial for adding a new xml file to the SDK is? I was reading one just three days ago, but now I can't find where it was... :cry:
 
Does anyone know where a tutorial for adding a new xml file to the SDK is? I was reading one just three days ago, but now I can't find where it was... :cry:
Doesn't ring a bell; hopefully someone else knows.
Since you're working with AdvCiv, one of these Git commits might be helpful (well, not if you want to really just read that tutorial :)):
Spoiler :
[1] Add CIV4TruCivInfos.xml to the XML\Civilizations folder (among a bunch of other changes, unfortunately)
[2] Add another XML file to that same folder
[3] Move those additions to a separate folder XML\TrueStarts with its own schema file

This uses all the conveniences I've added for XML loading, so it's not really applicable to other mods. It's also unusual that I load the new files only when my new game option is used. And I haven't exposed anything to Python.
Adding another GlobalDefines file would be quite a different thing (and simpler). But I assume that you want a new "info" type with an associated XML file.
 
Spoiler Line of sight problem with range highlighting :

The BTS code assumes that if a unit is not an air unit, but has air range, that it will be using line of sight to do ranged attacks. So it will only highlight tiles within the unit's range that the unit can see, even though you can still select tiles outside this radius if you modify canRangeStrikeAt() to accommodate that. AdvCiv fixes this, and it's the issue I was thinking of earlier.

I've got it to work using CyInterface().setInterfaceMode(InterfaceModeTypes.INTERFACEMODE_PARADROP) and CyInterface().setInterfaceMode(InterfaceModeTypes.INTERFACEMODE_RANGE_ATTACK).
I'm messing with the permissions in the CvUnit.cpp, so now that I can trigger the option from within a spell, I'm sorted. Thanks for your help.

FYI, The visibility limitation is in the range attack
Code:
if (!pPlot->canSeePlot(pTargetPlot, getTeam(), airRange(), getFacingDirection(true)))
. Using the paradrop, that limitation is not there.
 
@f1rpo thank you, those commits will definitely help. I combed through the whole "tutorials and reference" forum, and a lot of the SDK forum, and I have no idea what I was reading lol. Maybe if I have time I'll throw up my own little tutorial once I figure out how it all works.

@LPlate2 glad I could help a bit :) interesting that the paradrop doesn't have the limit but that does make sense. paratroopers do not usually require line of sight, in the real world.
 
Hey team - probably more of a python question than Civ one :

I'm trying to show an image (of a file) in game according to a path, I want to "catch" an error if the .dds file doesn't exist. (* just to be clear : it works fine when the file is there, but show a "pink" box when it's not)
The python command "Try: .. except" is not helping with this.
What's the most efficient way of doing this ?

I tried this and it doesn't work sadly, I thought indeed it would be too simple :

Code:
        try:
            szFilePath = "PublicMaps\Pictures\BTG_"+ str(szCleanName) + ".dds"
            popup.addDDS(szFilePath, 500, 500, 500, 500)
        except:
            popup.setBodyString("[ No Map Picture]", CvUtil.FONT_LEFT_JUSTIFY)
 
Back
Top Bottom