Super Forts

Hi Red,

I am getting a crash to desktop that is connected to this bit of your code.

Spoiler :
Code:
void CvPlot::updateCulture(bool bBumpUnits, bool bUpdatePlotGroups)
{
	
	// Super Forts begin *culture*
	if (!isCity(true))
	// if (!isCity()) Original Code
	// Super Forts end
	{
		setOwner(calculateCulturalOwner(), bBumpUnits, bUpdatePlotGroups);
	}
	// Super Forts begin *culture* (This prevents forts from keeping the culture of dead players)
	else if (getOwnerINLINE() != NO_PLAYER)
	{
		if(!GET_PLAYER(getOwnerINLINE()).isAlive())
		{
			setOwner(calculateCulturalOwner(), bBumpUnits, bUpdatePlotGroups);
		}
	}
	// Super Forts end
}

it talks about there being no condition, break always...

It could also be connected to a bit of python that I have, that one only allows certain civs to found cities on certain features, and 2 builds a building in a city that founds on that feature.

So far I have only encountered it with one of these specific civs, it happens when I click found city button, but I am not sure if it always happens, as I was able to found an inital city...

So I am uncertain what the change in condition might be that allows it to found sometiumes and not every time, in the save I am working from it happens everytime i go to found this particular city site...

I don't really know what the problem or soultion is, but as it showed your code snippet I thought I would start here..

Thanks for any help you can offer!
 
Sorry I have no idea why that code would cause you problems. It hasn't caused any problems like you are describing in FFH2. However, the way it is written in that version caused some unintended behavior. Super Forts that were unowned would not be absorbed by your culture borders. I have fixed this in my development version. Try using the code below from my version. If that doesn't fix your problem then it might be something with your python.

Spoiler :
Code:
void CvPlot::updateCulture(bool bBumpUnits, bool bUpdatePlotGroups)
{
	// Super Forts begin *culture*
	if (!isCity(true))
	// if (!isCity()) Original Code
	{
		setOwner(calculateCulturalOwner(), bBumpUnits, bUpdatePlotGroups);
	}
	// This prevents forts from keeping the culture of dead players and allows culture to claim unowned forts
	else if ((getOwnerINLINE() == NO_PLAYER) || (!GET_PLAYER(getOwnerINLINE()).isAlive()))
	{
		setOwner(calculateCulturalOwner(), bBumpUnits, bUpdatePlotGroups);
	}
	// Super Forts end
}
 
I looked at how cities display it and I don't think it is possible for improvements if I understand correctly. Cities do it in CvGameTextMgr::buildCityBillboardIconString() but this function is not used anywhere in the DLL provided to modders. I think that means a part of the game engine that was not released for modding must handle it.

OK, Thanks for looking into for me.
 
Super Forts 1.20 download here
The major changes in this version of Super Forts are AI changes. The AI will now recognize choke points and canal points and build forts at these points. I have not found a mod that helps the AI recognize choke points before this one - I checked Better BTS AI and K-mod. Vanilla BTS does have some code to help the AI find canals but it was horrible, and mine will do it much better ;).

I have also exposed functions for getting or calculating choke and canal values to python. I have only used the choke and canal values to help the AI place forts but I imagine they could be useful for mapscripts and the AI's placement of cities.

Certain maps have a lot more choke and canal points than others. Archipelago and Highlands are good ones but they would be rare on Pangaea. A little explanation on what counts as a choke point or canal point in my code...
Spoiler :

A plot on the map is a choke point if blocking a player from traveling through that plot will force them to travel a much longer distance to reach the other side of it (or sometimes make it impossible). This requires at least two of the adjacent plots be impassable in the cardinal directions. Let us consider an example on land:

OXO
OOX
OOO

The letters above represent 9 plots on the map. O is a passable plot and X is an impassable plot (could be a mountain or water). The plot in the center is a choke point because if it is blocked off you will have to go beyond the boundaries of the 9 plots shown here to reach the top right plot.

What if the same example were in the water instead? Water is tricky because the answer will depend on if the X represents land or impassable water (like ice). If both X are land then it is not a choke point because the land would be connected and not allow boats to travel diagonally over it. If one or both X are ice then it could be a choke point.

What if the plot in the top right corner is just a small peninsula? My code takes area into account and will not consider something a choke point if it only cuts off a tiny area (I believe an area greater than 5 is required by the code right now).

A canal point is a land plot adjacent to at least two water plots that when made passable to water units can greatly decrease the distance the water units have to travel between the water plots (or sometimes make it possible where it was impossible before). Similar to choke points, things like small bodies of water where a canal is not useful have been taken into account.


Super Forts has been part of More Naval AI, a FFH2 mod, for a while now. Tholal has helped with some of the AI. Being part of a popular mod has also helped me find bugs or places where things could be improved. This version of Super Forts is the same as what is currently in MNAI besides minor things specific to MNAI such as making so the Super Forts code is only used when a certain game option is on.

I have actually had the majority of this version for a long time. I should have released it months ago but there were some more things I wanted to add. Unfortunately I have been very busy with a masters thesis and life and haven't had time for modding. I decided to just go ahead and release what I have rather than waiting for time to add more. The things I want to add in the future include: a way to limit building improvements based on # of cities, a way to limit building improvements based on population, a prereq tech tag for improvement upgrades, and some UI/text (so it is clear why you can't build an improvement when using the things I have added or plan to add).

I haven't kept a good change log. If I ever make another mod I'll be better about that. Just know that there are all sorts of AI adjustments and bugfixes. Here are some of the major ones:
-When a player dies the forts they owned will immediately lose culture instead of at the end of the turn
-Workers will take path distance into account so they do not go to far to build forts (previously only used regular distance which did not account for obstacles like water)
-Forts will not revolt until a few turns have passed since they were captured (adjustable in GlobalDefines.xml). Without this the AI sometimes got stuck in a pattern of repeatedly capturing the same fort.
-Adjustments to how many units the AI builds for defense and how many are put in forts
-If using the <bUpgradeRequiresFortify> tag the unit's team will be checked rather than player (so units on the same team can upgrade each others forts)
-AI workers won't "cheat" and consider building forts on plots that haven't been explored
-Reverted my XML change so building a fort will no longer destroy the worker. Increased <iUnique> for forts to 2.
-The aforementioned choke and canal code and AI improvements

Sorry for the wall of text :)
 
Nice! I really like where this is heading, and your future plans (Especially the tech prereq for upgrades) are some things I will definately be excited to get!

is the iunique about how many plots have to be between each fort... my brain is having a bit of a fart and i cannot think what this does...

I am going to hold off trying to update my merge (which I am sure you will be glad to hear as it means less headaches for you in the near future :D) I will eagerly wait for the extra things you hope to add in the future :) If I am going to give myself a headache, it may as well be a big one :D
 
Thanks Lib. You're right about iUnique - it is the number of plots that have to be between each fort. This is easy to change in the XML.
 
Below is a more detailed description of how my choke point code works. It will mainly be useful to modders who want to merge it and adjust it for their own needs. I'll add in a link to this post near the start of this thread.

Spoiler :

I use a local terrain matching pattern to determine if a plot is a potential choke point. This involves checking the plots adjacent to the plot and the observation that a potential choke point requires that two of the cardinal directions be impassable. For example,
Spoiler :

even if all diagonal directions and one cardinal direction are blocked you can get around a plot. Units can go around the plot in the center as these impassable Xs and passable Os demonstrate:
XOX
OOX
XOX

Potential choke points are then further evaluated to determine their choke value (which could still end up as 0). I divide the adjacent plots into "sections" which are the groups of one or more plots that are separated from each other by the choke point. Here are some examples:
Spoiler :

2 sections:
(top and bottom)
OOO
XOX
OOO
(top right corner and the rest)
OXO
OOX
OOO

1 section: (which means it is not a choke point so value is 0)
OXX
OOX
OOO

4 sections: (the max possible)
OXO
XOX
OXO

Next, a plot is chosen in each of the sections and the path distance from all possible pairs of the chosen plots is calculated. If there were only 2 sections then there is only one possible pair of plots to check but there would be six possible pairs for 4 sections. I modified the FAStar pathfinder built into Civ4 to allow you to exclude a plot when finding a path because we need to exclude the current plot we are evaluating or the path would go through it and be a distance of 2. The pathfinder will return either a positive distance or -1. Let us consider each case separately...

If the distance is positive then things are straightforward. The distance should be 2 at a minimum if sections have been properly divided. Also note that the original path through the plot in question would be 2. Draw out some Os and Xs (or your symbols of choice) if you want to check. Due to this I subtract 1 from the result because I think there is a small value in forcing units to take a different path even if it is the same length as the original. A greater number could be subtracted if a modder doesn't want to consider short paths. The choke value will be based on the max path distance found among all pairs.

If the pathfinder returns -1 this means it found no path between a pair of plots. Thus, the choke point must completely separate one region of land from another. In this case, I determine the value based on the region with minimum size. To calculate the size of a region I wrote a function that creates an FAStar object to find the area. Like the pathfinder, the region finder can be passed a plot to exclude so that the plot we are evaluating can be excluded. After the minimum area out of the two regions is found, I subtract 4 from this to avoid counting a plot as a choke point if it only blocks a tiny region. This could be adjusted by SDK modders if they want to require larger regions. I believe choke points that divide an area into two regions are more valuable so I also multiply the result so far by 4. The result is then compared to the max distance to see if we have found a better value to replace it.

My process for finding canal points is very similar but I won't go into it here. Here is some of the relevant code if you like reading that better than my description...
Spoiler :

Code:
void CvPlot::calculateChokeValue()
{
	bool bInPassableSection;
	CvPlot *pAdjacentPlot, *apPlotsToCheck[4];
	int iPassableSections, iPlotsFound, iMaxDistance;
	int iChokeValue = 0;
	bool bWater = isWater();

	if(!isImpassable() && countImpassableCardinalDirections() > 1)
	{
		iPassableSections = countAdjacentPassableSections(bWater);
		if(iPassableSections > 1)
		{
			iMaxDistance = 0;
			iPlotsFound = 0;
			bInPassableSection = false;
			// Find appropriate plots to be used for path distance calculations
			for (int iI = 0; iI < NUM_DIRECTION_TYPES; ++iI)
			{
				pAdjacentPlot = plotDirection(getX_INLINE(), getY_INLINE(), ((DirectionTypes)iI));
				if(pAdjacentPlot != NULL)
				{
					if(pAdjacentPlot->isWater() == bWater)
					{	
						// Don't count diagonal hops across land isthmus
						if (bWater && !isCardinalDirection((DirectionTypes)iI))
						{
							if (!(GC.getMapINLINE().plotINLINE(getX_INLINE(), pAdjacentPlot->getY_INLINE())->isWater()) && !(GC.getMapINLINE().plotINLINE(pAdjacentPlot->getX_INLINE(), getY_INLINE())->isWater()))
							{
								continue;
							}
						}
						if(pAdjacentPlot->isImpassable())
						{
							if(isCardinalDirection((DirectionTypes)iI))
							{
								bInPassableSection = false;
							}
						}
						else if(!bInPassableSection)
						{
							bInPassableSection = true;
							apPlotsToCheck[iPlotsFound] = pAdjacentPlot;
							if((++iPlotsFound) == iPassableSections)
								break;
						}
					}
					else if(bWater || isCardinalDirection((DirectionTypes)iI))
					{
						bInPassableSection = false;
					}
				}
			}
			// Find the max path distance out of all possible pairs of plots
			for (int iI = 0; iI < (iPlotsFound - 1); ++iI)
			{
				for (int iJ = iI + 1; iJ < iPlotsFound; ++iJ)
				{
					int iDistance = GC.getMapINLINE().calculatePathDistance(apPlotsToCheck[iI], apPlotsToCheck[iJ], this);
					if(iDistance == -1)
					{
						// If no path was found then value is based off the number of plots in the region minus a minimum area
						iDistance = std::min(apPlotsToCheck[iI]->countRegionPlots(this), apPlotsToCheck[iJ]->countRegionPlots(this)) - 4;
						iDistance *= 4;
					}
					else
					{
						// Path already would have required 2 steps, but we forced the enemy to go another way so there is some value
						iDistance -= 1;
					}
					if(iDistance > iMaxDistance)
					{
						iMaxDistance = iDistance;
					}
				}
			}
			iChokeValue = iMaxDistance * (iPlotsFound - 1);
		}
	}

	setChokeValue(iChokeValue);
}

int CvPlot::countRegionPlots(const CvPlot* pInvalidPlot) const
{
	int iCount = 0;
	int iInvalidPlot = (pInvalidPlot == NULL) ? 0 : GC.getMapINLINE().plotNum(pInvalidPlot->getX_INLINE(), pInvalidPlot->getY_INLINE()) + 1;
	FAStar* pRegionFinder = gDLL->getFAStarIFace()->create();
	gDLL->getFAStarIFace()->Initialize(pRegionFinder, GC.getMapINLINE().getGridWidthINLINE(), GC.getMapINLINE().getGridHeightINLINE(), GC.getMapINLINE().isWrapXINLINE(), GC.getMapINLINE().isWrapYINLINE(), 
		NULL, NULL, NULL, stepValid, NULL, countPlotGroup, NULL);
	gDLL->getFAStarIFace()->SetData(pRegionFinder, &iCount);
	// Note to self: for GeneratePath() should bReuse be true or false?
	gDLL->getFAStarIFace()->GeneratePath(pRegionFinder, getX_INLINE(), getY_INLINE(), -1, -1, false, iInvalidPlot, false);
	gDLL->getFAStarIFace()->destroy(pRegionFinder);
	return iCount;
}

There is a CvMap::calculateCanalAndChokePoints() function to calculate or recalculate the values of all plots on the map and it has been exposed to python. Recalculation is necessary in mods where plots can change from water to land (or land to water) and passable to impassable (or impassable to passable). In normal BTS (and even FFH2) the choke and canal values are calculated just once at the beginning of the game and stored in a variable in CvPlot.

The values calculated from my code above are what I consider the "raw" choke values. The AI does not directly use these values. I have added a CvPlayerAI::AI_getPlotChokeValue() that further adjusts the value for each player by taking into account things like how close it is to their cities.

 
Would someone be able to merge this mod so that it is compatible with the next war mod that comes with bts?
 
It requires SDK/DLL/C++ merging and (a little) python not just XML. I hope you find someone 50caliber. Unfortunately I can't. The C2C team has merged it.
 
So if I understand it correctly, if I have an "outpost" with iUnique 2 and it's upgraded by fortifying into a "fort", I would be able to make "outpost" next to it again since "iunique" checks only for improvements of the same type? Would it be possible for someone to suggest me if it's possible to check for an improvement list rather than the same improvement to avoid this issue?

A logical solution would be to add "UniqueTypes" menu with "UniqueType" submenu.

Even better if iUnique would be a subfeature of UniqueType so you could make distance requirements different (i.e. scavenging sites could be 1 tile avay from other scavenging sites, but at least 2 tiles away from a mercenary camp since mercenaries don't like scavengers milling around).

Of course it's mostly a question to those who already worked with this mod or the mod author. Having the option would be amazing, but I can understand if you say "it's not easy to do".
 
I believe iUnique uses the "final improvement upgrade" for its checks, but maybe I am confusing it with something else. I know I used the final upgrade for some checks somewhere.

If this is the case, it means you can not build an outpost next to a fort because the final improvement upgrade for both of them would be the same.

If it is not the case, then I should make it the case. :)
 
I double checked and it is already in. The code for this in CvPlot::canBuild() is checking the finalImprovementUpgrade.
Spoiler :

Code:
		// Super Forts begin *build*
		if (GC.getImprovementInfo(eImprovement).getUniqueRange() > 0)
		{
			int iUniqueRange = GC.getImprovementInfo(eImprovement).getUniqueRange();
			for (int iDX = -iUniqueRange; iDX <= iUniqueRange; iDX++) 
			{
				for (int iDY = -iUniqueRange; iDY <= iUniqueRange; iDY++)
				{
					CvPlot *pLoopPlot = plotXY(getX_INLINE(), getY_INLINE(), iDX, iDY);
					if (pLoopPlot != NULL && pLoopPlot->getImprovementType() != NO_IMPROVEMENT)
					{
						if (finalImprovementUpgrade(pLoopPlot->getImprovementType()) == finalImprovementUpgrade(eImprovement))
						{
							return false;
						}
					}
				}
			}
		}
		// Super Forts end
 
Top Bottom