Quick Modding Questions Thread

How do I restrict city building in desert, glacier and tundra, such that it is only possible near water? (Coast, river or oasis)
 
In CIV4TerrainInfos.xml you can set restrictions based on the terrain:

You can restrict city founding to coast:
<bFoundCoast>0</bFoundCoast>
or fresh water:
<bFoundFreshWater>0</bFoundFreshWater>
Setting both works ofc as well.

If you need more fine-grained control than that you can also set overrides per feature
<bNoCoast>0</bNoCoast> and\ or <bNoRiver>0</bNoRiver> in CIV4FeatureInfos.xml

Summary:
To restrict city founding to always require fresh water, set bFoundFreshWater to true for all terrain types. If you want cities in the desert or in the arctic to be coastal, set bFoundCoast for TERRAIN_ICE and TERRAIN_DESERT to true or set both both of them to require fresh water as well.
I pulled this from memory, will have to check the code to see if I'm right :p
 
Last edited:
CvDllTranslator.cpp

Code:
void CvDllTranslator::initializeTags(CvWString& szTagStartIcon, CvWString& szTagStartOur, CvWString& szTagStartCT, CvWString& szTagStartColor, CvWString& szTagStartLink, CvWString& szTagEndLink, CvWString& szEndLinkReplacement, std::map<std::wstring, CvWString>& aIconMap, std::map<std::wstring, CvWString>& aColorMap)
{
    szTagStartIcon = L"[ICON_";
    szTagStartOur = L"[OUR_";
    szTagStartCT = L"[CT_";
    szTagStartColor = L"[COLOR_";
    szTagStartLink = L"[LINK";
    szTagEndLink = L"[\\LINK";
    szEndLinkReplacement = L"</link>";

    //create icons map
    aIconMap[L"[ICON_BULLET]"] = std::wstring(1, (wchar)gDLL->getSymbolID(BULLET_CHAR));
    aIconMap[L"[ICON_HAPPY]"] = std::wstring(1, (wchar)gDLL->getSymbolID(HAPPY_CHAR));
    aIconMap[L"[ICON_UNHAPPY]"] = std::wstring(1, (wchar)gDLL->getSymbolID(UNHAPPY_CHAR));
    aIconMap[L"[ICON_HEALTHY]"] = std::wstring(1, (wchar)gDLL->getSymbolID(HEALTHY_CHAR));
    aIconMap[L"[ICON_UNHEALTHY]"] = std::wstring(1, (wchar)gDLL->getSymbolID(UNHEALTHY_CHAR));
    aIconMap[L"[ICON_STRENGTH]"] = std::wstring(1, (wchar)gDLL->getSymbolID(STRENGTH_CHAR));
    aIconMap[L"[ICON_MOVES]"] = std::wstring(1, (wchar)gDLL->getSymbolID(MOVES_CHAR));
    aIconMap[L"[ICON_RELIGION]"] = std::wstring(1, (wchar)gDLL->getSymbolID(RELIGION_CHAR));
    aIconMap[L"[ICON_STAR]"] = std::wstring(1, (wchar)gDLL->getSymbolID(STAR_CHAR));
    aIconMap[L"[ICON_SILVER_STAR]"] = std::wstring(1, (wchar)gDLL->getSymbolID(SILVER_STAR_CHAR));
    aIconMap[L"[ICON_TRADE]"] = std::wstring(1, (wchar)gDLL->getSymbolID(TRADE_CHAR));
    aIconMap[L"[ICON_DEFENSE]"] = std::wstring(1, (wchar)gDLL->getSymbolID(DEFENSE_CHAR));
    aIconMap[L"[ICON_GREATPEOPLE]"] = std::wstring(1, (wchar)gDLL->getSymbolID(GREAT_PEOPLE_CHAR));
    aIconMap[L"[ICON_BAD_GOLD]"] = std::wstring(1, (wchar)gDLL->getSymbolID(BAD_GOLD_CHAR));
    aIconMap[L"[ICON_BAD_FOOD]"] = std::wstring(1, (wchar)gDLL->getSymbolID(BAD_FOOD_CHAR));
    aIconMap[L"[ICON_EATENFOOD]"] = std::wstring(1, (wchar)gDLL->getSymbolID(EATEN_FOOD_CHAR));
    aIconMap[L"[ICON_GOLDENAGE]"] = std::wstring(1, (wchar)gDLL->getSymbolID(GOLDEN_AGE_CHAR));
    aIconMap[L"[ICON_ANGRYPOP]"] = std::wstring(1, (wchar)gDLL->getSymbolID(ANGRY_POP_CHAR));
    aIconMap[L"[ICON_OPENBORDERS]"] = std::wstring(1, (wchar)gDLL->getSymbolID(OPEN_BORDERS_CHAR));
    aIconMap[L"[ICON_DEFENSIVEPACT]"] = std::wstring(1, (wchar)gDLL->getSymbolID(DEFENSIVE_PACT_CHAR));
    aIconMap[L"[ICON_MAP]"] = std::wstring(1, (wchar)gDLL->getSymbolID(MAP_CHAR));
    aIconMap[L"[ICON_OCCUPATION]"] = std::wstring(1, (wchar)gDLL->getSymbolID(OCCUPATION_CHAR));
    aIconMap[L"[ICON_POWER]"] = std::wstring(1, (wchar)gDLL->getSymbolID(POWER_CHAR));

    aIconMap[L"[ICON_GOLD]"] = std::wstring(1, (wchar)GC.getCommerceInfo(COMMERCE_GOLD).getChar());
    aIconMap[L"[ICON_RESEARCH]"] = std::wstring(1, (wchar)GC.getCommerceInfo(COMMERCE_RESEARCH).getChar());
    aIconMap[L"[ICON_CULTURE]"] = std::wstring(1, (wchar)GC.getCommerceInfo(COMMERCE_CULTURE).getChar());
    aIconMap[L"[ICON_ESPIONAGE]"] = std::wstring(1, (wchar)GC.getCommerceInfo(COMMERCE_ESPIONAGE).getChar());

    aIconMap[L"[ICON_FOOD]"] = std::wstring(1, (wchar)GC.getYieldInfo(YIELD_FOOD).getChar());
    aIconMap[L"[ICON_PRODUCTION]"] = std::wstring(1, (wchar)GC.getYieldInfo(YIELD_PRODUCTION).getChar());
    aIconMap[L"[ICON_COMMERCE]"] = std::wstring(1, (wchar)GC.getYieldInfo(YIELD_COMMERCE).getChar());

    //create color map
    aColorMap[L"[COLOR_REVERT]"] = CvWString(L"</color>");
    for(int i=0; i < GC.getNumColorInfos(); i++)
    {
        const NiColorA& color = GC.getColorInfo((ColorTypes) i).getColor();
        CvWString colorType(GC.getColorInfo((ColorTypes) i).getType());
        CvWString wideColorType;
        wideColorType.Format(L"[%s]", colorType.GetCString());
        CvWString colorOut;
        colorOut.Format(L"<color=%i,%i,%i,%i>", (int) (color.r * 255), (int) (color.g * 255), (int) (color.b * 255), (int) (color.a * 255));
        aColorMap[wideColorType.GetCString()] = colorOut;
    }
}

You're probably looking for ICON_STAR / ICON_SILVER_STAR
 
Last edited:
All the gamefont icons get an id assigned at startup. You can access all of them by id in C++/python generated text, even those without keywords. However I'm not sure if you can do anything other than using the keywords if you mod xml only. You can also add more keywords by modding CvGameText::read to convert m_szText where the keywords are replaced with widechars of the id in question. It's requires modding the dll itself (obviously since it's a dll function), but it also requires some approach to figuring out the right id to use for this purpose.

I wonder if it's enough to mod CvDllTranslator.cpp if you want to add more keywords. I haven't tried that approach because I had to mod CvGameText anyway and picked that approach.

You do want to use the list of keywords in CvDllTranslator.cpp if what you need is listed there. Modding the dll is the complex approach even if you know how to do so.
 
I have a Horse Grenadier unit, which starts throwing grenades at a neighboring tile when set to sleep:

Spoiler :


My guess is the mission is assigned to the wrong animation in the kfm file, but I have no idea how to fix this. Can someone take a look for me? Unit assets and the art defines XML are attached.
 

Attachments

  • Horse_Grenadier.rar
    387.2 KB · Views: 122
  • CIV4ArtDefines_HorseGrenadier.xml
    883 bytes · Views: 60
I had to look into this. Python doesn't quite allow that.
  • changeExtraHappiness can add an arbitrary amount of happiness or unhappiness to a city, but it's permanent.
  • changeHappinessTimer can add a happiness bonus to a city for a certain length of time, but it only allows a flat +1 :). The Wedding Feud and Great Beast events in BTS use this.
  • changeConscriptAngerTimer and changeHurryAngerTimer can add temporary unhappiness to a city and it stacks, but it also scales with city size.
If you are willing to accept the scaling from city size, then this is all the code you need:
Code:
def onUnitBuilt(argsList):
           pCity = argsList[0]
           pUnit = argsList[1]
           if pUnit.getUnitClass() == gc.getInfoTypeForString("UNITCLASS_XXX"):
                      gamespeed = CyGame.getGameSpeedType()
                      gameSpeedModifier = gc.getGameSpeedInfo(gamespeed).getGrowthPercent()
                      iSlave = 8 * gameSpeedModifier / 100
                      pCity.changeConscriptAngerTimer(iSlave)

XXX is the unit class of the unit that you want to trigger the unhappiness addition when it is built. GrowthPercent is serving as the scaling factor. If you want to go strictly by turns you have to calculate the length of the game for both your current speed and normal speed.
Finally I had time to look into this. But where do I put it? :crazyeye:
 
Finally I had time to look into this. But where do I put it? :crazyeye:

It's a process. First, an FYI: Python is not modular the way XML is. You are going to have to either edit existing Python files in the main mod Python folder or create new files in both the Python and Config folders.

The easy way to do this is to "piggyback" the Python code onto existing Python code in another file. Find the def onXXX function that you want to use somewhere in the existing Python files and copy the non-duplicated lines in your own code to that file (that is, everything after the argslist lines). This works better once you've already created your own files and are having multiple different things happening with the same trigger. AND has several different effects triggered by Wonders being built that all use the same onBuildingBuilt trigger in VokaryaWonders.py.

The better way is to create your own files. You need two new files and an edit to an existing one. First, you need a .py file in the Python folder. This is the starting point.
Code:
from CvPythonExtensions import *
import CvUtil
gc = CyGlobalContext()
Make a file in a text editor with this, add your Python code at the end, and save it as a .py file in the Python folder. The gc line is a space-saver. You can type out CyGlobalContext() every time you use it, but gc saves keystrokes.

Now you need to tell Civ4 how to use this file. For this, you need to create an .xml file in the Config folder. At a minimum, this file looks like this:
Code:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<mod id="XXX">
        <event type="XXX" function="onXXX"/>
</mod>
You will need one copy of the event line for each different Python function (onBuildingBuilt, onUnitBuilt, etc.) in your Python file. The function matches the trigger you use in your Python file and the event type matches that except it doesn't start with on.

The last step is to tell Civ4 to load the config xml file you created. To do this, you edit the init.xml file in the Config folder. There is a long list of load lines in that file. Somewhere in there, you put the line for your own mod:
Code:
<load mod="XXX"/>
where the XXX is the ID that you put in your xml file in the Config folder. That should do it.
 
It's a process. First, an FYI: Python is not modular the way XML is. You are going to have to either edit existing Python files in the main mod Python folder or create new files in both the Python and Config folders.

The easy way to do this is to "piggyback" the Python code onto existing Python code in another file. Find the def onXXX function that you want to use somewhere in the existing Python files and copy the non-duplicated lines in your own code to that file (that is, everything after the argslist lines). This works better once you've already created your own files and are having multiple different things happening with the same trigger. AND has several different effects triggered by Wonders being built that all use the same onBuildingBuilt trigger in VokaryaWonders.py.

The better way is to create your own files. You need two new files and an edit to an existing one. First, you need a .py file in the Python folder. This is the starting point.
Code:
from CvPythonExtensions import *
import CvUtil
gc = CyGlobalContext()
Make a file in a text editor with this, add your Python code at the end, and save it as a .py file in the Python folder. The gc line is a space-saver. You can type out CyGlobalContext() every time you use it, but gc saves keystrokes.

Now you need to tell Civ4 how to use this file. For this, you need to create an .xml file in the Config folder. At a minimum, this file looks like this:
Code:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<mod id="XXX">
        <event type="XXX" function="onXXX"/>
</mod>
You will need one copy of the event line for each different Python function (onBuildingBuilt, onUnitBuilt, etc.) in your Python file. The function matches the trigger you use in your Python file and the event type matches that except it doesn't start with on.

The last step is to tell Civ4 to load the config xml file you created. To do this, you edit the init.xml file in the Config folder. There is a long list of load lines in that file. Somewhere in there, you put the line for your own mod:
Code:
<load mod="XXX"/>
where the XXX is the ID that you put in your xml file in the Config folder. That should do it.
Well, that doesn't work. :(
Here are my 3 files. Did I do something wrong?
 

Attachments

  • Nexus.rar
    2.7 KB · Views: 104
Well, that doesn't work. :(
Here are my 3 files. Did I do something wrong?

I think the issue is your whitespace. Python is very sensitive to whitespace. (XML is not. All it needs is properly closed tags.) Each time you indent, you indent by one tab. Don't use spaces to indent.

This is how your Python code should look. Replace each <tab> with an actual tab.
def onUnitBuilt(argsList):
<tab>pCity = argsList[0]
<tab>pUnit = argsList[1]
<tab>if pUnit.getUnitClass() == gc.getInfoTypeForString("UNITCLASS_SLAVE"):
<tab><tab>gamespeed = CyGame.getGameSpeedType()
<tab><tab>gameSpeedModifier = gc.getGameSpeedInfo(gamespeed).getGrowthPercent()
<tab><tab>iSlave = 8 * gameSpeedModifier / 100
<tab><tab>pCity.changeConscriptAngerTimer(iSlave)
 
I think the issue is your whitespace. Python is very sensitive to whitespace. (XML is not. All it needs is properly closed tags.) Each time you indent, you indent by one tab. Don't use spaces to indent.

This is how your Python code should look. Replace each <tab> with an actual tab.
def onUnitBuilt(argsList):
<tab>pCity = argsList[0]
<tab>pUnit = argsList[1]
<tab>if pUnit.getUnitClass() == gc.getInfoTypeForString("UNITCLASS_SLAVE"):
<tab><tab>gamespeed = CyGame.getGameSpeedType()
<tab><tab>gameSpeedModifier = gc.getGameSpeedInfo(gamespeed).getGrowthPercent()
<tab><tab>iSlave = 8 * gameSpeedModifier / 100
<tab><tab>pCity.changeConscriptAngerTimer(iSlave)
No, it's still not working. No unhappiness from training Slaves. (In the test game the city trained 6 Slaves but nothing)

EDIT:
I also tried tinkering with values in
iSlave = 8 * gameSpeedModifier / 100
by chenging 100 to 10, than to 1000, but nothing.
 
Last edited:
No, it's still not working. No unhappiness from training Slaves. (In the test game the city trained 6 Slaves but nothing)

EDIT:
I also tried tinkering with values in
iSlave = 8 * gameSpeedModifier / 100
by chenging 100 to 10, than to 1000, but nothing.

Actually, I found two bugs. First of all, the config xml file (not init.xml) does need a module declaration. The second line should be something like this:
<mod id="UnhappyTest" module="UnhappyTest">

Second, in the Python file, CyGame needs to have empty parentheses () right after it and before the period.

Another thing that helps if you are trying to use Python is to enable Python popups. In the Civilization4.ini is a setting for HidePythonPopups. Set that to 0 and every time Python tries to do something and fails it will pop up a little screen telling you what the problem is. Understanding this information is another story, but it can help with debugging.
 
Actually, I found two bugs. First of all, the config xml file (not init.xml) does need a module declaration. The second line should be something like this:
<mod id="UnhappyTest" module="UnhappyTest">

Second, in the Python file, CyGame needs to have empty parentheses () right after it and before the period.

Another thing that helps if you are trying to use Python is to enable Python popups. In the Civilization4.ini is a setting for HidePythonPopups. Set that to 0 and every time Python tries to do something and fails it will pop up a little screen telling you what the problem is. Understanding this information is another story, but it can help with debugging.
I did all the above but still see no effect. Not even python popups.
Thanks anyway.

EDIT:

I think that
if pUnit.getUnitClass() == gc.getInfoTypeForString("UNITCLASS_SLAVE"):
should be
if pUnit.getUnitClassType() == gc.getInfoTypeForString("UNITCLASS_SLAVE"):

At least that is how it is in RoMEventManager for Colonist and Pioneer.
...but it still doesn't work :(

EDIT2:
I also tried this:
pCity.changeConscriptAngerTimer(5)
but still nothing :dunno:
 
Last edited:
It should be UnitClassType. I got it to work but I was doing it fast and didn't quite remember what I did properly. These are the files that I was able to get to work, although I have it triggering on a Musketman.

Also, the Python popups are good for finding errors in syntax. If you try to call a function that doesn't exist, it is likely that nothing will happen and that doesn't generate popups.
 

Attachments

  • UnhappyTest.zip
    2.8 KB · Views: 121
Last edited:
It should be UnitClassType. I got it to work but I was doing it fast and didn't quite remember what I did properly. These are the files that I was able to get to work, although I have it triggering on a Musketman.

Also, the Python popups are good for finding errors in syntax. If you try to call a function that doesn't exist, it is likely that nothing will happen and that doesn't generate popups.
Okay, it's finally working! :woohoo:
There was an empty line in my config file before the mod id line. Now I know that it DOES matter sometimes :crazyeye:
This whole thing was very instructive. Thanx for your thorough explanations :goodjob:
 
I want to change the city screen of AND2 from the first look to something like the second. Namely:
  • Move resources to the right a bit
  • Make the resource list longer
  • Move the specialists to the left
  • Make the specialists list taller (but IIRC that is auto-adjusting)
Spoiler :
upload_2020-1-6_12-32-18.jpeg

So can someone help me what are the coordinates I need to edit? I only know that CvMainInterface.py is the file I need to tinker with.


EDIT: SOLVED
On my own :D
 
Last edited:
Is it in any way possible to install (Python 2.4 compatible) libraries for the ingame Python context? I assume it's bundled in the exe somewhere but I thought I'd ask on the off chance it isn't.
 
Top Bottom