Civ V : Bad Religion

I have a bit of a problem with Civ V

Civilization 5 is one of my favorite ways to spend time. With each game just different enough to keep things interesting and an endless stream of short and long term goals, this game provides endless satisfaction. I play exclusively single player with the Brave New World DLC. The Pangea map on small is the sweet spot for my playstyle since any type of win is attainable. Island maps are not too enjoyable for me since there is a good chance that one or more civs will be isolated for the first part of the game. This increases tech costs and reduces some of the interesting interactions the AI can provide.

However, one such interaction that I don’t enjoy is the missionary spam generated by certain leaders. At higher difficulties, the seemingly endless stream of missionaries and prophets becomes tiresome. Keep inquisitors on hand is a chore and not a great use of faith points. To my knowledge, the game does not provide a way of tuning religious behavior. Religion is either on or off, no middle ground. I did a half-hearted search for mods that tuned religion but are all for more complicated than what I want. I just want to reduce the religious unit harassment!

Getting the SDK

Civilization V has two forms of mods. The first version uses Lua, XML, custom databases, or any combination of these. These do not require modifications to the core DLL and are considered to be less of a hassle to produce. Since the behavior that I’m looking for is not exposed to as a simple functionCallToLua() that means I have to go the DLL route. Fortunately, the core game source is distributed with the Civ V SDK.

  1. If you haven’t already acquired the SDK, you can download it from steam. Search for Sid Meier’s Civilization V SDK.
  2. Once installed, find the folder named CvGameCoreSource. It should be located at <steam_installation_base>\steamapps\common\Sid Meier's Civilization V SDK\CvGameCoreSource.
  3. Copy this entire directory to your preferred development directory, e.g. D:\software\. This does two things. First, the default installation path (C:\Program Files) can only be written by administrators which makes compilation a hassle. Second, having a backup of the source is good for when things go pear-shaped.
  4. Take this opportunity to initialize a git repo at the root of this new folder. git init will suffice.
  5. Add a gitignore will something along these lines.
/BuildTemp/
/.vs/
*.ncb
/BuildOutput/
*.suo
*.user
*.aps
  1. The SDK was written with VC90. This compiler version is distributed with Visual Studio 2008 and can be found after a few minutes of Googling. Download and install the express version.

    • Optionally, grab the latest version of Visual Studio which is currently 2019. If you like VS 2008 then there is no need to perform this step. I prefer the IDE features in 2019 so that’s what I use.

Building the SDK

At this point, we’re ready to open the project in your IDE. While I’m sure you could get this to compile and debug from VS Code or CLion, I don’t think it is worth the effort. This is not a cross-platform project so just use the right tool for the job. If you chose to use VS 2019, you can download the SLN project file I’ve already created. Open the SLN file in the appropriate version of VS when you’re ready to continue.

  1. Make sure you’ve created your initial git commit with the provided ignore file. If you added a new SLN file, make sure that gets added too. For VS 2019 users, make sure to check that v90 is selected for the projects’ platform toolset. Right-click on each project and navigate to Properties->General->Platform Toolset to check this setting.

  2. There are three projects in the solution, each containing the name CvGameCoreDLL. The first project is the original game. _Expansion1 is Gods and Kids, and _Expansion2 is Brave New World. Since we’re talking about modifying religion, we will be using the _Expansion2 project. Right-click on the other two projects and select “Unload Project” This prevents us from modifying the wrong source files since each project has mostly the same code, more or less.

  3. If you were to try and compile, you’d immediately get an error, The express version of VS does not include afxres.h which is required for resource file compilation. Find all files in the project ending with .rc and perform the following changes:

 7
 8
 9
10
11
//
// Generated from the TEXTINCLUDE 2 resource.
//
// #include "afxres.h" //< Comment this out
#include "windows.h"   //< Add this
  1. Don’t forget to git commit!

  2. Click build in your IDE. The process should complete with some warnings that we’ll be ignoring. You may see red squiggles for some of the template classes but we’ll be ignoring those too.


Learning the Code

All the hard work is complete. Now for the fun part of reading and learning about the code. What I like to do with new codebases is to pick some aspect of the program that I understand how to trigger and then read through the code flow surrounding it. For example, barbarian spawning. Try to find the code the controls when barbarians 1. As a hint, all the interesting code will be in the Source Files directory and begin with Cv.

Since we’re looking for code that affects religion, CvReligionClasses.cpp seems like a great place to start.

Best. Function. Names. Ever.

Fortunately, the developers gave excellent function and variable names so finding what we need an intuiting what we don’t is a fairly straight forward process. These two functions have roughly the same logic where religion is the religion being spread and holy city is the birthplace of the said religion.

  1. Only spread religion, not pantheon
  2. Only spread religion if the holy city has already been created
  3. If this religion’s holy city was converted, convert it back and end
  4. If the caller only wants to spread post-enhancement, abort
  5. Otherwise, score each city using the following formula (h / d) * n where h is the count of non-believers, d is the distance from this city to our holy city, and n is 3 if current religion is not ours and current religion is a not a pantheon.
  6. Calculate the political impact of spreading to each city using a similar formula.
  7. Take the highest scoring city.

There are lots of parameters we can tune tweak to alter how cities are chosen. One idea would be to modify the scoring equation to maybe not pick on us as much. Notice that the formula does not have any bias towards human players. The AI will harass all parties equally. An even easier solution can be found by looking at the outermost control of the player loop. The code reads

4706
4707
4708
4709
4710
...
for(int iPlayerLoop = 0; iPlayerLoop < MAX_CIV_PLAYERS; iPlayerLoop++) {   
    CvPlayer &kLoopPlayer = GET_PLAYER((PlayerTypes)iPlayerLoop);
    if(kLoopPlayer.isAlive() && iPlayerLoop != m_pPlayer->GetID()) {
        ...

Wouldn’t it be cool if they just skipped us entirely? We just need a way to tell if the player is us or another AI. We can inspect the properties of kLoopPlayer which is of type CvPlayer. Conveniently, there is a function that sounds like it will do what we need. bool isHuman() const; is listed in the CvPlayer.h file and if we inspect all references to this function, the current usage implies that it will return true if the instance is not an AI. Let’s give it a shot. !kPlayerLoop.isHuman() should do the trick.

4706
4707
4708
4709
4710
4711
...
for(int iPlayerLoop = 0; iPlayerLoop < MAX_CIV_PLAYERS; iPlayerLoop++) {
    // Only spread to living AI players
    CvPlayer &kLoopPlayer = GET_PLAYER((PlayerTypes)iPlayerLoop);
    if(kLoopPlayer.isAlive() && iPlayerLoop != m_pPlayer->GetID() && !kPlayerLoop.isHuman()) {
        ...

Make this change in CvReligionClasses::ChooseProphetConversionCity and a similar change in CvReligionClasses::ChooseMissionaryTargetCity. Compile the code to make sure everything is still working.


Testing and Debugging

Now we can test our new mod. If you want to get a handle on how this game works, it is supremely helpful to know how to attach the debugger to your running game. To make the debugging experience easier, we need to make sure we enable debug symbols. Check that Properties->C/C++->General->Debug Information Format is set to Program Database. While you’re there, let’s update the output file name to match the name of the DLL that Civ will expect. Set Properties->Linker->General->Output File to $(OutDir)CvGameCore_Expansion2.dll.

Here is the deployment script that I use to push my DLL and PDBs to the Civ runtime directory. This must be run as admin if you have Steam installed to the default location. Once you have the DLLs and PDBs in place, launch Civ V.

  1. Wait for Civ V to fully load
  2. Start a local game with your favorite configuration
  3. Once the game starts and your settler and warrior are on screen, switch back to Visual Studio.
    • Pro-tip: Unbind the mouse from the game by Settings->Interface Options->Bind Mouse to Never.
  4. From VS, Debug->Attach to Process
  5. Connection type should be default, target should be the name of your computer. Attach to should say Automatic: Native code. Scroll through the process list to find CivilizationV_DX11.exe and click Attach.
  6. Ctrl+alt+break
  7. The game should now be paused. Clicking on the UI should not work. Depending on what the game was doing at the time, you may or may not see meaningful code at the breakpoint.

We’re nearly there. If you’ve made it this far, you have successfully built a modded DLL, loaded it into Civ, and attached your debugger. Click F5 to let the game continue running.

Religion usually takes a few turns to establish and then a few more turns before the AI starts harassing you. You can either play and just wait to see if the AI leaves you alone or you play with your new debugger. Let’s do the latter. We’ll start by finding some interesting places to place breakpoints.

One quick place to add a breakpoint is in CvUnit::getName. This called whenever the player selects a unit and is used to look up the custom or default name. If the breakpoint is a red circle, that means the breakpoint installation worked. If it is white with a red ring, that means you might not have loaded the DLL or PDBs properly. Try the previous section again.

With the breakpoint in place, hit F5 to continue the game. Select either your warrior or settler which should trigger the breakpoint. From here, you should see

CvUnit Locals

Dig into the properties of this to see all the code that a unit is composed of. You can modify strength, defense, move counts, and just about anything else imaginable. There are many, many classes in Civ V so I suggest reading experimenting with more breakpoints to learn more about the inner workings of the game.


Future Work

This kind of change would be great to have an option for in the game setup menu. Future work could add all the mechanics required to expose this feature as an option. Another improvement would be to have a mechanic that discourages AI from harassing you if you’re on good terms. This is a big hammer for a small problem that some people may consider more of a cheat than anything. I think these quality-of-life improvements enhance my experience. These are the types of improvements that let me focus on the aspects of the game that I love.


  1. CvBarbarians::DoSpawnBarbarianUnit ↩︎