Quick Modding Questions Thread

No, you're right. Could only do something in the DLL, e.g.
Code:
if (eProject == GC.getInfoTypeForString("PROJECT_THEORY_OF_EVOLUTION"))
    iValue += 5;
in CvCityAI::AI_projectValue. Not sure how that value gets used; seems to be on a pretty small scale:
Spoiler :
Code:
if (GC.getProjectInfo(eProject).getNukeInterception() > 0)
{
    if (GC.getGameINLINE().canTrainNukes())
        iValue += (GC.getProjectInfo(eProject).getNukeInterception() / 10); // (Only 7 gets added here it seems)
}
Edit: Oh, AND has tweaked this:
Code:
iValue += (GC.getProjectInfo(eProject).getNukeInterception() *2);
Okay, so do I just insert your code like this:
Code:
int CvCityAI::AI_projectValue(ProjectTypes eProject)
{
    int iValue;
    int iI;

    iValue = 0;
    
    if (GC.getProjectInfo(eProject).getEveryoneSpecialBuilding() != NO_SPECIALBUILDING)
    {
        iValue += 10;
    }
    
    for (int iL = 0; iL < GC.getNumVictoryInfos(); iL++)
    {
        if (GC.getGameINLINE().isVictoryValid((VictoryTypes)iL))
        {
            if (GC.getVictoryInfo((VictoryTypes)iL).isTotalVictory())    //45deg: boost for SS production when playing with Mastery
            {
                if (GC.getProjectInfo(eProject).isSpaceprogram())
                {
                    iValue += 1000;
                }
                if (GC.getProjectInfo(eProject).isSpaceship())
                {
                    iValue += 1000;
                }                   
            }
        }   
    }   
    
    if (GC.getProjectInfo(eProject).getNukeInterception() > 0)
    {
        if (GC.getGameINLINE().canTrainNukes())
        {
            iValue += (GC.getProjectInfo(eProject).getNukeInterception() *2);
            if ((GC.getGameINLINE().countTotalNukeUnits()) > (GET_PLAYER(getOwnerINLINE()).getNumCities() *4))
            {
                iValue *= 3;
            }
        }
    }

    if (GC.getProjectInfo(eProject).getTechShare() > 0)
    {
        if (GC.getProjectInfo(eProject).getTechShare() < GET_TEAM(getTeam()).getHasMetCivCount(true))
        {
            iValue += (20 / GC.getProjectInfo(eProject).getTechShare());
        }
    }

    for (iI = 0; iI < GC.getNumVictoryInfos(); iI++)
    {
        if (GC.getGameINLINE().isVictoryValid((VictoryTypes)iI))
        {
            iValue += (std::max(0, (GC.getProjectInfo(eProject).getVictoryThreshold(iI) - GET_TEAM(getTeam()).getProjectCount(eProject))) * 200);
        }
    }

    for (iI = 0; iI < GC.getNumProjectInfos(); iI++)
    {
        iValue += (std::max(0, (GC.getProjectInfo((ProjectTypes)iI).getProjectsNeeded(eProject) - GET_TEAM(getTeam()).getProjectCount(eProject))) * 10);
    }

    if (eProject == GC.getInfoTypeForString("PROJECT_THEORY_OF_EVOLUTION"))
        iValue += 5;

Does this look good?
 
Yes. Seems that the specific value doesn't actually matter much so long as it's greater than 0 (because the AI logic for creating projects is very rudimentary).
 
Yes. Seems that the specific value doesn't actually matter much so long as it's greater than 0 (because the AI logic for creating projects is very rudimentary).
So it's only a build-it-or-don't situation for the AI? Strange.
 
I agree. The only significant call location of AI_projectValue is AI_bestProject (not touched by AND):
Spoiler :
(Refactored by me on the spot for readability)
Code:
ProjectTypes CvCityAI::AI_bestProject()
{
	int iBestValue = 0;
	ProjectTypes eBestProject = NO_PROJECT;
	for (int iI = 0; iI < GC.getNumProjectInfos(); iI++)
	{
		ProjectTypes eProject = (ProjectTypes)iI;
		if (!canCreate(eProject)) // Skip illegal projects
			continue;
		int iValue = AI_projectValue(eProject); // Evaluate project
		// ... (Special code for randomly setting iValue to at least 1 for Manhattan project)
		if (iValue <= 0) // Skip worthless projects
			continue;
		iValue += getProjectProduction(eProject); // Inertia: production already invested
		int iTurnsLeft = getProductionTurnsLeft(eProject, 0);
		// ... (code for ensuring that the city can create the project reasonably fast)
		if (iValue > iBestValue) // Pick the highest-value project
		{
			iBestValue = iValue;
			eBestProject = eProject;
		}
	}
}
So the AI project value does matter for picking the best available project, but it doesn't matter for deciding between that and a building, a unit or a conversion process. AI_bestProject, in turn, only gets called by AI_chooseProject – which will simply start producing the best project. AI_chooseProject gets called in one fairly low-priority place when the AI is inclined to produce a wonder and no good ones are available and in one other place that seems to get reached when the AI is aiming at a Space victory. I guess this means that, despite your DLL change, the AI will only treat Theory Of Evolution as a kind of second-class wonder.

Edit: I see that K-Mod has addressed this issue (eliminating the AI_chooseProject function) and that it does compare the project value with the best building value.
 
I guess this means that, despite your DLL change, the AI will only treat Theory Of Evolution as a kind of second-class wonder.
So it may build it but with little probability. I can live with that. My mod already has a lot of wonders and some projects.
Nevertheless, it was quite instructive for my future plans to prefer wonders over projects.
Thank you!
 
Hi,
Where is it defined that the yield from ocean tiles is always 0? Even if I put resources and fishing boats on ocean tiles, it still shows no yields. I'm assuming its because the code is written on the assumption that ocean tiles will be beyond a city's workable radius, so there's no reason to allow for yields on them. I'd like to allow for cities with 3 plot radius or ocean based cities to get the benefit of some resources from the ocean. What do I need to edit to enable yields from the ocean tiles?
thanks
 
bWaterWork is needed to work the coastal waters. It doesn’t help with ocean tiles.
Note: The ocean tiles within 2 of the coast can be worked. No yields appear on ocean tiles further out, even if there's a resource there.
 
Last edited:
Hey team,

A couple of people I've raised the point that it *seems* that when you do (very large) stack attack with my MOD, it's very slow (they play multiplayer should I need to specify).
I just re-read what I changed in combat, I only ever changed something very small in "CvUnit::updateCombat" and having re-read it's super small and looks efficient to me.

Which leads to a more encompassing question : do you have code that generally speeds up the game, especially during large stack combats ?
I believe you have mentioned how you improved the speed overall f1rpo, but that seem like a lot of fundamental changes to toroughly apply and test, I'm thinking more like a couple of easy wins routine changes that could plug'n'play

Thanks !
 
So you assume that combat is just as slow in BtS? Maybe it's related to simultaneous turns. I don't think I've ever tried Stack Attack (we're talking about PLAYEROPTION_STACK_ATTACK, right?) in MP, but I could imagine that there is a deliberate delay, maybe for some fairness reason. Or perhaps the network messages are a bottleneck (but, then, I guess it should be just 1 message per attack). Of course, if Stack Attack is just as slow in SP, something else must be the reason; maybe the processing of killed units. I don't think that this should normally cause a noticeable delay.

More generally, if there's a performance issue in a particular situation, then I would focus only on that. If it's evident that the time is being spent by the DLL, then a profiler will show very specifically how that time is being spent. Overall speed increases – beyond enabling whole-program optimization (/GL compiler option) for releases – I see no easy way. Iirc you distribute your mod as a standalone application (or almost), so this faster version of Python24.dll could be of interest to you. Mods can't generally provide that file because it goes into the BtS install dir. Well, I've only measured a 4% decrease in turn times in a test, so this is a bit off-topic.
 
I was wondering if it was possible to make music work like in Civ5, that is you have a play list for Peace, War and Either? Or just a War and Peace playlist and the Either being defined in both.
Did anyone ever try or even consider such a thing?
 
Another economic issue. How can the resource productivity of improvement processing resource be increased? As an example - "an advanced type mine generates 2 or more copper resources instead of one." In addition to abstract realism, for mods using "derived resources", there is a significant difference between requiring one copy of a resource or more.
 
Another economic issue. How can the resource productivity of improvement processing resource be increased? As an example - "an advanced type mine generates 2 or more copper resources instead of one." In addition to abstract realism, for mods using "derived resources", there is a significant difference between requiring one copy of a resource or more.
Have a look at the bottom of CvPlot::updatePlotGroupBonus as that is where the number of bonuses is updated if my reading of the function is correct.

C++:
        eNonObsoleteBonus = getNonObsoleteBonusType(getTeam());

        if (eNonObsoleteBonus != NO_BONUS)
        {
            if (GET_TEAM(getTeam()).isHasTech((TechTypes)(GC.getBonusInfo(eNonObsoleteBonus).getTechCityTrade())))
            {
                if (isCity(true, getTeam()) ||
                    ((getImprovementType() != NO_IMPROVEMENT) && GC.getImprovementInfo(getImprovementType()).isImprovementBonusTrade(eNonObsoleteBonus)))
                {
                    if ((pPlotGroup != NULL) && isBonusNetwork(getTeam()))
                    {
                        pPlotGroup->changeNumBonuses(eNonObsoleteBonus, ((bAdd) ? 1 : -1));
                    }
                }
            }
        }

You could add a new property to the ImprovementInfo that defined the number of bonus resources an improvement harvests called getNumBonuses() and then use this like the following:

C++:
        eNonObsoleteBonus = getNonObsoleteBonusType(getTeam());

        if (eNonObsoleteBonus != NO_BONUS)
        {
            if (GET_TEAM(getTeam()).isHasTech((TechTypes)(GC.getBonusInfo(eNonObsoleteBonus).getTechCityTrade())))
            {
                if (isCity(true, getTeam()) ||
                    ((getImprovementType() != NO_IMPROVEMENT) && GC.getImprovementInfo(getImprovementType()).isImprovementBonusTrade(eNonObsoleteBonus)))
                {
                    if ((pPlotGroup != NULL) && isBonusNetwork(getTeam()))
                    {
                        int iBonusCount = GC.getImprovementInfo(getImprovementType()).getNumBonuses();
                        pPlotGroup->changeNumBonuses(eNonObsoleteBonus, ((bAdd) ? iBonusCount : -iBonusCount));
                    }
                }
            }
        }

Obviously, you need to perform the updates that defne the new ImprovementInfo XML property and write the C++ code that reads it, but that is where I would start if it were me.
 
Obviously, you need to perform the updates that defne the new ImprovementInfo XML property

Great! Thank you very much. Well, I'll try to add a property. I suspect, however, that soon I will appear here again with another stupid question. :D
 
To implement that cleanly, with full AI and UI support, I'd (eventually) do a text search for "isImprovementBonusTrade" on the DLL codebase, maybe also on the Python folders just in case. My thinking is that all code that currently only checks whether the improvement connects the resource may have to check, instead, how many instances get connected. For example, there's a comment "Always prefer improvements which connect bonuses." in CvCityAI.cpp. This might be a good place to get the AI to use the improvement that connects the highest number of instances. I also see examples where the existing code is probably sufficient, e.g. CvPlayer::countUnimprovedBonuses.

@<Nexus>: Currently, there's just one list for era music, and war/ peace only affects the diplo screen, I think? I've had music disabled for so long ... Well, assuming that we're talking about the era music, the DLL should have full control over the tracks that get played. At the start of a new era and when a track has finished playing (or has been skipped through - I forget which key press), the EXE asks CvGame::getNextSoundtrack for the next audio tag index. Defining a separate list in the XML schema and getting the DLL to parse that list and to assign indices to the "AS2D_SONG_..." strings would not be entirely easy. I guess one could avoid this labor by using a single list (the one that already exists - EraInfoSoundtracks) and putting the war-only tracks, I don't know, at the end of the list, i.e. a fixed number of them, say, 5 for each era. And maybe 5 peace-only tracks at the start, the rest in the middle. Then the programming task would only be to put the first 5 into one container (std::vector probably), the last 5 in another and the rest into both – all in getNextSoundtrack – and then to pick a random element from either the war or the peace container. Would seem prudent to implement something simpler first to verify that this approach is really feasible and not to put work into the (artistic) selection of tracks until the technical side works.
 
@<Nexus>: Currently, there's just one list for era music, and war/ peace only affects the diplo screen, I think? I've had music disabled for so long ... Well, assuming that we're talking about the era music, the DLL should have full control over the tracks that get played. At the start of a new era and when a track has finished playing (or has been skipped through - I forget which key press), the EXE asks CvGame::getNextSoundtrack for the next audio tag index. Defining a separate list in the XML schema and getting the DLL to parse that list and to assign indices to the "AS2D_SONG_..." strings would not be entirely easy. I guess one could avoid this labor by using a single list (the one that already exists - EraInfoSoundtracks) and putting the war-only tracks, I don't know, at the end of the list, i.e. a fixed number of them, say, 5 for each era. And maybe 5 peace-only tracks at the start, the rest in the middle. Then the programming task would only be to put the first 5 into one container (std::vector probably), the last 5 in another and the rest into both – all in getNextSoundtrack – and then to pick a random element from either the war or the peace container. Would seem prudent to implement something simpler first to verify that this approach is really feasible and not to put work into the (artistic) selection of tracks until the technical side works.
Thanks.
And what about dummy eras? E.g.: ERA_CLASSICAL is the normal thing for peace time and tricking the game to select tracks from ERA_CLASSICAL_WAR during wartime? IIRC I read somewhere that Fall From Heave 2 does something like that 🤔
 
And what about dummy eras? E.g.: ERA_CLASSICAL is the normal thing for peace time and tricking the game to select tracks from ERA_CLASSICAL_WAR during wartime? IIRC I read somewhere that Fall From Heave 2 does something like that 🤔
The soundtracks for each era are inside the XML where eras them self are defined. So dummy lists wouldn't work.

Indeed, since the function just returns an integer index you can't even do something clever like separate them out by name. So your best bet would be to just have the first N songs be for peace and the rest for war. And just hardcore that number.
If you want to get fancy you could add a new tag to the XML for each era that signifies how many songs are in which category. Something like:
Code:
            <SoundtracksPeaceCount>3</SoundtracksPeaceCount>
            <EraInfoSoundtracks>
                <EraInfoSoundtrack>AS2D_ERA_SOUNDTRACK_PEACE_1</EraInfoSoundtrack>
                <EraInfoSoundtrack>AS2D_ERA_SOUNDTRACK_PEACE_2</EraInfoSoundtrack>
                <EraInfoSoundtrack>AS2D_ERA_SOUNDTRACK_PEACE_3</EraInfoSoundtrack>
                <EraInfoSoundtrack>AS2D_ERA_SOUNDTRACK_WAR_1</EraInfoSoundtrack>
                <EraInfoSoundtrack>AS2D_ERA_SOUNDTRACK_WAR_2</EraInfoSoundtrack>
            </EraInfoSoundtracks>

This said, if you want to get really fancy about it there is a point in the DLL where everything is initialized. And there the game does actually load the full list of songs as strings into memory. It's in CivInfos.cpp in the Read function. So you could do some coding there to mark songs based on their name somehow and than use those markings later to select music. Than you could actually define songs in the XML as AS2D_ERA_SOUNDTRACK_PEACE / AS2D_ERA_SOUNDTRACK_WAR and have the game automatically know which are which.

That's what I'd do simply because I am crazy like that.
 
In fact, let me play around with something for a bit.
 
Back
Top Bottom