[BTS] The Easiest Way to Compile a New DLL

The program is not opening any .sln files. Not with double-clicking nor drag'n'drop nor CTRL+O works.
Never happened to me; I think the installer is supposed to create a file association for double click. Maybe that's somehow broken on your OS. Ctrl+O is perhaps only for source files. Have you tried
File -> Open -> Project/ Solution (Ctrl+Shift+O)?
If that doesn't work, what exactly happens?
 
Ctrl+O is perhaps only for source files. Have you tried
File -> Open -> Project/ Solution (Ctrl+Shift+O)?
Okay, it wasn't Ctrl+O than but the menu you described.
If that doesn't work, what exactly happens?
It was loading for some time (but only on the first attempt) than nothing. Just the window you can see on the attached screen shot.

Maybe I'll try to reinstall the whole thing next week and get back here after.
Thanks for the reply :)
 
It was loading for some time (but only on the first attempt) than nothing. Just the window you can see on the attached screen shot.
Maybe the project has been loaded and it's just that, for some reason, all windows are closed. It does say "CvGameCoreDLL" on the upper left. You could try toggling the Output window on (Debug -> Windows -> Output) and the Solution Explorer (View -> Other Windows -> Solution Explorer). Could also try opening the vcxproj file instead of the sln. (Whenever you get around to it – and if it doesn't sound pointless.)
 
You could try toggling the Output window on (Debug -> Windows -> Output) and the Solution Explorer (View -> Other Windows -> Solution Explorer).
Yeah! That was it. Thank you :goodjob:

Next question:

I tried things and everything seems to be working but I just realized, that I have built a debug dll.
Now to build a "normal" dll I have to choose "Release", right? Or something else?
upload_2022-6-27_12-30-16.png

I am testing it at the moment any way, but I'm still curious. What are the different options for? I mean, which ones of those are useful for a beginner like me? :)
 
I have only ever used "Debug" and "Release", but the really important difference is that "Debug" allows you to display assert messages when they are triggered in the SDK. Those come from lines like this:
Code:
default:
        FAssert(false);
(pasted from some random place)

There are a bunch of different types of FAssertBlah functions you can use, and they're defined in FAssert.h if you want to look at all of them. But basically, you tell the DLL "check this thing, and if it's not what I expect it to be, throw up a popup box telling me about it." Super duper useful when you're trying to get a mod to work.

I think there are other debugging things the Debug DLL allows you to do, but I honestly haven't used any of them. Release disables all of those but speeds up everything, so it's the shiny nice version you'd want to use when releasing a mod.

Then I think "Debug_fast" and other "_fast" ones are versions that scrunch the compiling time so you can use them over and over again if you're changing minor things. I don't actually know anything else about them because for my machine the Debug compilation takes about 1 minute maximum and that's fine for me :lol:



While you're setting everything up, you can set up Visual Studio to launch Civ4 attached to a debugger when it compiles a DLL. (When you hit the little green arrow at the top of VS.) That allows you to A) hit "Debug" on the 'there was an error' window that shows up whenever something goes wrong, which will take you straight to the place the error was generated in the code, and give you a call stack, and also B) if you click in the space just to the left of any code, you will set a "breakpoint", and when that place is reached in your game, Visual Studio will pause everything and show you this line. In either A) or B) situations you can hover over variables to see what their values are, and in the bottom left there will be a table that allows you to view game objects and all the variables associated with them. Crazy useful stuff; see page 27 of "manual.pdf" of f1rpo's AdvCiv , repeated below:

To attach the VS debugger, I recommend creating a copy of My Games\Beyond the Sword\
CivilizationIV.ini, e.g. named AdvCivDebug.ini. It's easiest to place it in the same
directory as Civ4BeyondSword.exe (CIV4_PATH directory). In the copy, set FullScreen = 0
and Mod = Mods\AdvCiv. <Merkava120's note: change that to your mod.> You may also want to reduce ScreenHeight a little and
make some other debug-friendly settings: CheatCode = chipotle, LoggingEnabled = 1,
AutoSaveInterval = 1. Then select the “Debug” configuration in VS 2010 and under
“Configuration Properties” - “Debugging,” enter Civ4BeyondSword.exe with its full path(!)
into the “Command” field and ini=AdvCivDebug.ini into “Command Arguments”. (VS will
write that information into CvGameCoreDLL\Project\AdvCiv.vcxproj.user; that file can also
be edited directly.) You can then “Start Debugging” via the “Debug” menu.
 
Yeah! That was it. Thank you :goodjob:
Cool; didn't expect everything to just work after this, but, well, great that it does.

The DLL for Chronicles of Mankind uses the build system from AdvCiv, which is essentially Nightinggale's, but with some modifications (Final-Release and Debug-Opt configs).
I have only ever used "Debug" and "Release", but the really important difference is that "Debug" allows you to display assert messages when they are triggered in the SDK.
If you only need assertions – there's also the Assert configuration, which is closer in speed to a release build than to a debug build. I would use Debug only when actually attaching a debugger. Even if one isn't comfortable with setting breakpoints and stepping through the code, the debugger is very helpful for diagnosing crashes to the desktop; will break automatically then, showing where the crash occurred and the callstack. And a lot of assertion popups aren't all that useful without the context provided by the debugger (edit: as Merkava already pointed out).

Last I checked, the Chronicles DLL had a fair number of assertions fail throughout a midgame turn. This will make it hard to tell whether a failed assertion is the result of some recent change or had been failing for years.

I'll put this in a spoiler because it's not relevant to the project files in the opening post:
Spoiler :
Release disables all of those but speeds up everything, so it's the shiny nice version you'd want to use when releasing a mod.
At least for a proper, non-beta release, Final-Release should really be used, which enables whole-program optimization. That'll add 10 minutes or so to the compilation time, but will eliminate the function call overhead for most of the small functions through inline expansion (among other optimizations, but I think this one is the most significant). Not so important in mods based on AdvCiv because I've already moved most of the frequently called small functions into header files where the compiler can always eliminate them, but, for Chronicles, Final-Release should yield a sizable speedup. Well, the most recent Chronicles DLL on GitHub is already a Final-Release build, so it's more a matter of losing speed by releasing a less optimized build. Edit: And the AdvCiv/ Chronicles makefile will only include version information from CvGameCoreDLL.rc in the DLL (shown under file properties in Windows) when compiling a Final-Release.
Then I think "Debug_fast" and other "_fast" ones are versions that scrunch the compiling time so you can use them over and over again if you're changing minor things. I don't actually know anything else about them because for my machine the Debug compilation takes about 1 minute maximum and that's fine for me :lol:
The "fast" configurations use parallelization. Nightinggale's thread has more info about that; it's his innovation (in the context of Civ :)). Scarcely makes a difference when only a few files (translation units) need to be recompiled (then linking takes up most of the time, which won't be parallelized), but a full rebuild should be much faster with a fast config (maybe 3 times faster on a quadcore). May slow down the whole system though (no idle cores) and may, in some few cases, make it difficult to correlate error messages with translation units. I only use the fast configurations
I mean, which ones of those are useful for a beginner like me? :)
Only Profile and (with the AdvCiv/ Chronicles project file) Debug-Opt and the non-fast Debug and Release can really be disregarded. Release-fast will cause the least fuss (for better or worse) – reasonably fast compilation and DLL, no assertions.
 
Last edited:
Okay. Next step for me.

Here is the thing I want to merge into my mod:
https://forums.civfanatics.com/threads/unique-units-great-generals.560427/#post-14101406

If I understand correctly I have to copy that code to CvPlayer.cpp. But where exactly?

I believe it goes somewhere here:
Code:
{
            int iExperienceThreshold = greatPeopleThreshold(true);
            if (m_iCombatExperience >= iExperienceThreshold && iExperienceThreshold > 0)
            {
                // create great person
                CvCity* pBestCity = NULL;
                int iBestValue = MAX_INT;
                int iLoop = 0;
                for (CvCity* pLoopCity = firstCity(&iLoop); pLoopCity != NULL; pLoopCity = nextCity(&iLoop))
                {
                    int iValue = 4 * GC.getGameINLINE().getSorenRandNum(getNumCities(), "Warlord City Selection");

                    for (int i = 0; i < NUM_YIELD_TYPES; i++)
                    {
                        iValue += pLoopCity->findYieldRateRank((YieldTypes)i);
                    }
                    iValue += pLoopCity->findPopulationRank();

                    if (iValue < iBestValue)
                    {
                        pBestCity = pLoopCity;
                        iBestValue = iValue;
                    }
                }

                if (pBestCity)
                {
                    int iRandOffset = GC.getGameINLINE().getSorenRandNum(GC.getNumUnitInfos(), "Warlord Unit Generation");
                    for (int iI = 0; iI < GC.getNumUnitInfos(); iI++)
                    {
                        UnitTypes eLoopUnit = (UnitTypes)((iI + iRandOffset) % GC.getNumUnitInfos());
                        /************************************************************************************************/
                        /* Afforess                      Start         12/19/09                                                */
                        /*                                                                                              */
                        /*                                                                                              */
                        /************************************************************************************************/
                        /* was:
                        if (GC.getUnitInfo(eLoopUnit).getLeaderExperience() > 0 || GC.getUnitInfo(eLoopUnit).getLeaderPromotion() != NO_PROMOTION)
                        */
                        if (GC.getUnitInfo(eLoopUnit).isGreatGeneral())
                        /************************************************************************************************/
                        /* Afforess                         END                                                            */
                        /************************************************************************************************/
                        {
                            pBestCity->createGreatPeople(eLoopUnit, false, true);
                            setCombatExperience(getCombatExperience() - iExperienceThreshold);
                            break;
                        }
                    }
                }
            }
        }

EDIT: Attached CvPlayer.cpp as a txt file.
 

Attachments

  • CvPlayer.cpp.txt
    1.1 MB · Views: 17
Last edited:
Mmm this type can be tricky. Both Afforess and Leoreth (/edead) modified the same original Civ4 function. I like to approach these by figuring out what each one was adding and then making sure all their pieces get into my version.

Here is what the original does:
  • Loop through all unit infos (with a random offset?? idk why)
    • If the unit gives leader promos and experience
      • Create the great general in the city chosen earlier and set its experience, using the loop unit.
Leoreth/edead version (changed parts in bold):
  • Loop through all unit infos, with no random offset
    • If the unit gives leader promos and experience
      • Stop looping
  • Get the civilization unit of that unit (i.e. unique unit if there is one)
  • Create the great general in the city chosen earlier and set its experience, using the (possibly unique) unit.
And now, your / afforess version (changed parts from original in bold):
  • Loop through all unit infos with random offset
    • If the unit is a great general
      • Create the great general in the city chosen earlier and set its experience, using the loop unit.
So, you probably want to add Afforess's extra bit into Leoreth's and then paste over your version with Leoreth's, so you end up with

  • Loop through all unit infos, with no random offset (Leoreth)
    • If the unit is a great general (Afforess)
      • Stop looping (Leoreth)
  • Get the civilization unit of that unit (Leoreth)
  • Create the great general in the city chosen earlier and set its experience, using the (possibly unique) unit (Leoreth).
I assume that whatever mod you're using has an isGreatGeneral() function, which Afforess probably would have added, but if you were going to put both of these mods into a fresh one there would be more work to do.

Here's the resulting merge - in yours, this goes inside the if (pBestCity) brackets (replacing everything currently in there)

Code:
                    int iI;
                    for (iI = 0; iI < GC.getNumUnitInfos(); iI++)
                    {

                       if (GC.getUnitInfo((UnitTypes)iI).isGreatGeneral()) // this is afforess's bit
                        {
                            break;
                        }
                    }
                    UnitTypes eGreatGeneralType = ((UnitTypes)(GC.getCivilizationInfo(getCivilizationType()).getCivilizationUnits((UnitClassTypes)(GC.getUnitInfo((UnitTypes)iI).getUnitClassType()))));
                    pBestCity->createGreatPeople(eGreatGeneralType, false, true);
                    setCombatExperience(getCombatExperience() - iExperienceThreshold);


(ignore the weird indentation lol)
 
I assume that whatever mod you're using has an isGreatGeneral() function, which Afforess probably would have added
I believe that isGreatGeneral() function is added. I did a quick search for it and it is present in several files. So it should be safe to use.

Thank you for merging the code and ESPECIALLY for explaining what parts do what. I am no programmer and I have no time&intention of seriously learning C++ but I do try to understand things as best as I can :)

Now let's test it :bump:

EDIT:
It seems to be working. I received a unique GG in my present game, though it could be just random as in the past. But so far so good. Thank you guys! :)
 
Last edited:
Wooo!!

Btw for what it's worth, your religion / civics mod should be fairly straightforward if you follow Xienwolf's guide to add XML tags, then add something like <ForceReligion> to CivicInfos. Then in CvPlayer::canDoReligion() you could add something like this:

Code:
for (int i = 0; i < "number of civic options"; i++)
{
     ReligionTypes eForceReligion = GC.getInfo(getCivics(i)).getForceReligion();
     if ("the religion you are testing in this method" != eForceReligion)
         return false;
}

(I don't know all the exact names but that's the general idea lol)

You may just be able to use CvPlayer::isCivic() instead of looping through civic options, but I have not seen that used and haven't tested it myself.

Similar stuff could be used with a <PreventReligion> or something.
 
Wooo!!

Btw for what it's worth, your religion / civics mod should be fairly straightforward if you follow Xienwolf's guide to add XML tags, then add something like <ForceReligion> to CivicInfos. Then in CvPlayer::canDoReligion() you could add something like this:

Code:
for (int i = 0; i < "number of civic options"; i++)
{
     ReligionTypes eForceReligion = GC.getInfo(getCivics(i)).getForceReligion();
     if ("the religion you are testing in this method" != eForceReligion)
         return false;
}

(I don't know all the exact names but that's the general idea lol)

You may just be able to use CvPlayer::isCivic() instead of looping through civic options, but I have not seen that used and haven't tested it myself.

Similar stuff could be used with a <PreventReligion> or something.
Thanks, but this looks far too difficult for me at the moment. So I'll focus on rather small tweaks and simple merges, like this:
Is it true that you need to change only a single integer to modify the max number of civs in the game? Where do I find it?
 
No worries, it's good not to overwhelm yourself right away :)

Thanks, but this looks far too difficult for me at the moment. So I'll focus on rather small tweaks and simple merges, like this:
Is it true that you need to change only a single integer to modify the max number of civs in the game? Where do I find it?

That is correct and it is in CvGlobals.h I think. MAX_NUM_PLAYERS? (I'm on my phone so I can't check for sure haha)

There is something unusual that happens with changing that number, where large numbers of civs slow things down even in games that don't use them all, which @f1rpo has mentioned in the AdvCiv manual I linked earlier, but I don't know much beyond that.
 
That is correct and it is in CvGlobals.h I think. MAX_NUM_PLAYERS? (I'm on my phone so I can't check for sure haha)
Maybe one of these?
int getMAX_CIV_PLAYERS();
int getMAX_PLAYERS();
int getMAX_CIV_TEAMS();
int getMAX_TEAMS();

But it's not CvGlobals.h or CvGlobals.cpp, because I don't find the number (50) anywhere.
Save for jdog5000 comments :lol:
 
CvDefines.h
[CvEnums.h in AdvCiv, which might be relevant for Merkava]

The getters in CvGlobals are only for Python. Performance penalty comes from loops that go through all possible player and team IDs and from memory being allocated for data about all players that could possibly exist. Changing MAX_CIV_PLAYERS also breaks scenarios and old savegames.
 
Performance penalty comes from loops that go through all possible player and team IDs and from memory being allocated for data about all players that could possibly exist.
Well, AND2 had two dlls for a long time: one with a limit of 50 and an other with 100 and I didn't feel any significant difference in performance.
Changing MAX_CIV_PLAYERS also breaks scenarios and old savegames.
I am aware of that. Thank you :)
 
I believe that isGreatGeneral() function is added. I did a quick search for it and it is present in several files. So it should be safe to use.

Thank you for merging the code and ESPECIALLY for explaining what parts do what. I am no programmer and I have no time&intention of seriously learning C++ but I do try to understand things as best as I can :)

Now let's test it :bump:

EDIT:
It seems to be working. I received a unique GG in my present game, though it could be just random as in the past. But so far so good. Thank you guys! :)
I'm returning to this now. The works properly but I have a small issue that may or may not be related:
When I receive a unique Great General (Pharaoh for Egypt) it doesn't get a unique name set up for the pharaohs. XMLs are set up properly (I believe it doesn't matter if I add the names to GREAT_PERSONS.xml or to a new UNIQUE_GREAT_PERSONS.xml).
My guess is that normal GGs ran out of names already and for this reason it ignores the remaining pharaoh names also, though there are plenty of unused names for the later. Any ideas for a solution or a workaround?
Thanks in advance :)
 
So you have sth. like ...
Code:
<UnitInfo>
	<Class>UNITCLASS_GREAT_GENERAL</Class>
	<Type>UNIT_GREAT_PHARAO</Type>
	<UniqueNames>
		<UniqueName>TXT_KEY_GREAT_PERSON_HOREMHEB</UniqueName>
		<!-- ... -->
in Civ4UnitInfos.xml? Then I don't see what's going wrong:
CvUnit.cpp#L298
The code for assigning the name – for obtaining the names from XML and keeping track of the names already used – already seems to be based on UnitTypes (not UnitClassTypes), so it looks like it should work. Stepping through that code fragment in the debugger would be my best bet.
(I believe it doesn't matter if I add the names to GREAT_PERSONS.xml or to a new UNIQUE_GREAT_PERSONS.xml)
For defining the TXT_KEYs – right, somewhere in the XML\Text folder, the specific file shouldn't matter.
 
So you have sth. like ...
Code:
<UnitInfo>
<Class>UNITCLASS_GREAT_GENERAL</Class>
<Type>UNIT_GREAT_PHARAO</Type>
<UniqueNames>
<UniqueName>TXT_KEY_GREAT_PERSON_HOREMHEB</UniqueName>
<!-- ... -->
in Civ4UnitInfos.xml?
Yes.
XML:
        <UnitInfo>
            <Class>UNITCLASS_GREAT_GENERAL</Class>
            <Type>UNIT_GREAT_PHARAOH</Type>
            <UniqueNames>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_01</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_02</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_03</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_04</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_05</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_06</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_07</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_08</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_09</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_10</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_11</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_12</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_13</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_14</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_15</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_16</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_17</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_18</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_19</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_20</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_21</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_22</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_23</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_24</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_25</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_26</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_27</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_28</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_29</UniqueName>
                <UniqueName>TXT_KEY_GREAT_PHARAOH_30</UniqueName>
            </UniqueNames>
            <Special>SPECIALUNIT_PEOPLE</Special>
            <Capture>NONE</Capture>
            <Combat>UNITCOMBAT_COMMANDER</Combat>
            <Domain>DOMAIN_LAND</Domain>
            <DefaultUnitAI>UNITAI_GENERAL</DefaultUnitAI>
            <Invisible>NONE</Invisible>
            <SeeInvisible>NONE</SeeInvisible>
            <Description>TXT_KEY_UNIT_GREAT_PHARAOH</Description>
            <Civilopedia>TXT_KEY_UNIT_GREAT_PHARAOH_PEDIA</Civilopedia>
            <Strategy>TXT_KEY_UNIT_GREAT_PHARAOH_STRATEGY</Strategy>
            <Advisor>ADVISOR_MILITARY</Advisor>
I don't see any typos in either files :confused:

Stepping through that code fragment in the debugger would be my best bet.
Now that's Greek to me :lol:
 
Now that's Greek to me :lol:
Maybe someone else sees a problem with the XML or C++ code. Short of using the debugger, you could add log calls to trace the execution of CvUnit::init:
Spoiler :
Code:
logBBAI("dbgphar: Initializing unit %d", iID);
iUnitName = GC.getGameINLINE().getUnitCreatedCount(getUnitType());
int iNumNames = m_pUnitInfo->getNumUnitNames();
logBBAI("dbgphar: iUnitName=%d, iNumNames=%d", iUnitName, iNumNames);
if (iUnitName < iNumNames)
{
    int iOffset = GC.getGameINLINE().getSorenRandNum(iNumNames, "Unit name selection");
    for (iI = 0; iI < iNumNames; iI++)
    {
        int iIndex = (iI + iOffset) % iNumNames;
        CvWString szName = gDLL->getText(m_pUnitInfo->getUnitNames(iIndex));
        if (!GC.getGameINLINE().isGreatPersonBorn(szName))
        {
            setName(szName);
            logBBAI("dbgphar: Setting name %S", szName.c_str());
            GC.getGameINLINE().addGreatPersonBornName(szName);
            break;
        }
        else logBBAI("dbgphar: Name %S already taken", szName.c_str());
    }
}
And then check the BBAI log after the birth of a pharaoh. (RAND should normally have that log enabled. Will only need LoggingEnabled in CivilizationIV.ini.) Formatted output (with the % signs) is a bit error-prone to write; I hope I got it right.

With the debugger – which is actually somewhat on-topic for this thread –, you'd just click next to the line if (iUnitName < iNumNames) in the VS Code Editor to set a breakpoint, select the debug build config, rebuild the DLL, configure the debug command, start debugging from the VS Debug menu, get a Pharaoh born so that the breakpoint is hit, mouse over the iUnitName, iUnitNames variables in the Code Editor to see their values, hit F10 repeatedly to advance one line at a time and see which branches are taken.
 
Last edited:
Top Bottom