As a French person I feel like it's my duty to explain strikes to you. - AdrienIer

Create an account  

 
Curious Civplayer - Mysteries of the DLL

(September 17th, 2020, 15:40)NobleHelium Wrote: So it's 0.1% increased chance for WLTKD every turn per citizen, starting with 0.8% at the minimum required size.


Think the easiest way to think of this is it's a 1% chance each turn for a qualifying size 11 city, and increase/decrease it by .1% for every population point from there. So, given ten size 11 cities that all qualify, you would on average expect one of them to celebrate roughly every 10 turns or so over an extended period of time.
Reply

Is it 1% for a size 11 city, or 1.1%? Is the roll for the set of integers [0, 1000)? (In other words, inclusive of zero but exclusive of 1000.) That's intuitively what I think it would be.
Reply

It will be a random number between 0 and 999.

https://forums.civfanatics.com/threads/d...-0.162502/
Mods: RtR    CtH

Pitboss: PB39, PB40PB52, PB59 Useful Collections: Pickmethods, Mapmaking, Curious Civplayer

Buy me a coffee
Reply

How exactly does feature growth (jungles and forests) work?

For this we turn to CvPlot.cpp and in there in the method doFeature:

First we start with some setup checks

Code:
if (!isUnit())
{
    if (getImprovementType() == NO_IMPROVEMENT)
    {
        for (iI = 0; iI < GC.getNumFeatureInfos(); ++iI)
        {
            if (canHaveFeature((FeatureTypes)iI))
            {
                if ((getBonusType() == NO_BONUS) || (GC.getBonusInfo(getBonusType()).isFeature(iI)))
                {
                    ...the actual calculation
                }
            }
        }
    }
}

These checks are meant to prevent feature growth. From top to bottom these are the factors that prevent growth:
  • a unit is present on the tile
  • there is any kind of improvement on the tile
  • the tile returns false for the canHaveFeature method
  • the tile does contain a bonus resource that allows this feature
For the last two bullet points we need to dive deeper into the code and xml. First canHaveFeature returns false for
  • the tile contains already a feature
  • the tile contains a peak
  • the tile contains a city
  • the feature in question is not allowed on the base terrain (forests are only allowed on grass, plain and tundra; jungles are only allowed on grass)
  • the feature has bNoCoast set in the FeatureInfo.xml and is on the coast (this is irrelevant for forest and jungle, oasis are not allowed on coast tiles)
  • the feature has bNoRiver set in the FeatureInfo.xml and is on a river (this is irrelevant for forest and jungle, oasis are not allowed on river tiles)
  • the feature has bRequiresFlatlands set in the FeatureInfo.xml and is not on flatland (this is irrelevant for forest and jungle, floodplains and oasis only require flatland)
  • the feature has bRequiresRiver set in the FeatureInfo.xml and is not on river (this is irrelevant for forest and jungle, floodplains only require rivers)
  • the feature has bNoAdjacent set in the FeatureInfo.xml and there is another feature of this type on any of the 8 neighboring tiles (this is irrelevant for forest and jungle, only oasis has this)
I've put all the checks that are not important for forest and jungle into italic. Otherwise this is basic stuff that we already know. Next does the bonus resource allow the feature in question

These bonuses allow a forest:
  • Uranium
  • Deer
  • Fur
  • Silk
  • Spices
And these bonuses allow a jungle:
  • Oil
  • Uranium
  • Banana
  • Pig
  • Rice
  • Dye
  • Gems
  • Ivory
  • Spices
  • Sugar
That was a lot to take in, but now we know if the feature can grow on the current tile. Next we look at the actual calculation of the growth probability:

Code:
iProbability = 0;

for (iJ = 0; iJ < NUM_CARDINALDIRECTION_TYPES; iJ++)
{
   pLoopPlot = plotCardinalDirection(getX_INLINE(), getY_INLINE(), ((CardinalDirectionTypes)iJ));

   if (pLoopPlot != NULL)
   {
       if (pLoopPlot->getFeatureType() == ((FeatureTypes)iI))
       {
           if (pLoopPlot->getImprovementType() == NO_IMPROVEMENT)
           {
               iProbability += GC.getFeatureInfo((FeatureTypes)iI).getGrowthProbability();
           }
           else
           {
               iProbability += GC.getImprovementInfo(pLoopPlot->getImprovementType()).getFeatureGrowthProbability();
           }
       }
   }
}

iProbability *= std::max(0, (GC.getFEATURE_GROWTH_MODIFIER() + 100));
iProbability /= 100;

if (isRoute())
{
   iProbability *= std::max(0, (GC.getROUTE_FEATURE_GROWTH_MODIFIER() + 100));
   iProbability /= 100;
}

// RBMP game speed scaling:
iProbability = iProbability * 100 / GC.getGameSpeedInfo(GC.getGameINLINE().getGameSpeedType()).getImprovementPercent();

  1. We start by looking at the 4 tiles in cardinal direction to our current tile. For every tile that contains the feature we want to grow, we add either the growth probability of the feature (forest = 8, jungle = 16) or the growth probability of the improvement on that tile if there is one (Forest Preserve = 64). Do note that the probability increases with every feature, so four forests increase it to 32.
  2. Next step we multiply our probability with 0 or FEATURE_GROWTH_MODIFIER + 100, whichever is higher. FEATURE_GROWTH_MODIFIER is defined in GlobalDefines.XML and set to 25
  3. We divide our probability by 100
  4. If there is a road present on our tile we do the same as in 2. and 3. just this time with ROUTE_FEATURE_GROWTH_MODIFIER, which is also defined in GlobalDefines.XML and set to -50
  5. Next we have a RBMP adjustment here. We multiply our probability with 100 divided by the improvementPercent from the current game speed. On normal speed this value is 100, so the probability is unchanged. On Quick this value is 67.
Now we have our probability and we only need to role a random number and compare:


Code:
if (iProbability > 0)
{
   if (GC.getGameINLINE().getSorenRandNum(10000, "Feature Growth") < iProbability)
   {
       setFeatureType((FeatureTypes)iI);

       pCity = GC.getMapINLINE().findCity(getX_INLINE(), getY_INLINE(), getOwnerINLINE(), NO_TEAM, false);

       if (pCity != NULL)
       {
           // Tell the owner of this city.
           szBuffer = gDLL->getText("TXT_KEY_MISC_FEATURE_GROWN_NEAR_CITY", GC.getFeatureInfo((FeatureTypes) iI).getTextKeyWide(), pCity->getNameKey());
           gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_FEATUREGROWTH", MESSAGE_TYPE_INFO, GC.getFeatureInfo((FeatureTypes) iI).getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_WHITE"), getX_INLINE(), getY_INLINE(), true, true);
       }

       break;
   }
}

We role a number between 0 and 9999. If our probability is below that number the feature grows on this tile.


Examples

It's hard to summarize all the stuff above so I will just do some examples:

Worst non zero probability for forest growth (Normal speed): Only 1 forest on a neighboring cardinal tile and a road on our tile:

(((8 * 125) / 100) * 50) / 100 = 5 and a probability of 0.05% per turn.

Best probability for forest growth without improvements (Normal speed): 4 forests on a neighboring cardinal tile, no road:

(32 * 125) / 100 = 40 and a probability of 0.4% per turn.

Worst non zero probability for jungle growth (Normal speed): Only 1 jungle on a neighboring cardinal tile and a road on our tile:

(((16 * 125) / 100) * 50) / 100 = 10 and a probability of 0.1% per turn.

Best probability for jungle growth without improvements (Normal speed): 4 jungles on a neighboring cardinal tile, no road:

(64 * 125) / 100 = 80 and a probability of 0.8% per turn.


Best probability for forest and jungle growth with improvements (Normal speed): 4 forest preserves on a neighboring cardinal tile, no road:

(256 * 125) / 100 = 320 and a probability of 3.2% per turn.
Mods: RtR    CtH

Pitboss: PB39, PB40PB52, PB59 Useful Collections: Pickmethods, Mapmaking, Curious Civplayer

Buy me a coffee
Reply

(September 18th, 2020, 00:24)Charriu Wrote: It will be a random number between 0 and 999.

https://forums.civfanatics.com/threads/d...-0.162502/

Yeah so it would be 1.1% for a size 11 city.  0.1% per pop.
Reply

How does teleporation work or where are my units teleported to on closed borders, war declaration etc.?

For this we turn to CvUnit.cpp and in there in the method jumpToNearestValidPlot:

In there we have some boring setup before we get to the meat of the method. We are looking for the best plot, which is what tiles are named in the code.

Code:
    for (iI = 0; iI < GC.getMapINLINE().numPlotsINLINE(); iI++)
    {
        pLoopPlot = GC.getMapINLINE().plotByIndexINLINE(iI);

        if (pLoopPlot->isValidDomainForLocation(*this))
        {
            if (canMoveInto(pLoopPlot))
            {
                if (canEnterArea(pLoopPlot->getTeam(), pLoopPlot->area()) && !isEnemy(pLoopPlot->getTeam(), pLoopPlot))
                {
                    FAssertMsg(!atPlot(pLoopPlot), "atPlot(pLoopPlot) did not return false as expected");

                    if ((getDomainType() != DOMAIN_AIR) || pLoopPlot->isFriendlyCity(*this, true))
                    {
                        if (pLoopPlot->isRevealed(getTeam(), false))
                        {
                            ...The actual best value calculation
                        }
                    }
                }
            }
        }
    }


We start by iterating through all the plots of the whole map and we do some basic checks. All plots, which fail those checks are not considered for teleportation. The checks are:

  1. Is the domain type of the current plot valid for this unit (Land units are not allowed on water and vice versa for example.)
  2. Can the unit move into this plot (Basically is the plot impacable like a mountain)
  3. canEnterArea checks if you can enter the territory of another player. Basically can one of your units legally move into the tile.
  4. If the unit is an air unit we only consider friendly cities
  5. Lastly we check if that plot is already revealed by the player (Note this is not a check if the unit has a valid path to this plot, just basic revealed state.
All of these checks should very basic and logical for everybody. Now we know that our current plot is valid and we start calculating a value for this plot.

Code:
iValue = (plotDistance(getX_INLINE(), getY_INLINE(), pLoopPlot->getX_INLINE(), pLoopPlot->getY_INLINE()) * 2);

if (pNearestCity != NULL)
{
   iValue += plotDistance(pLoopPlot->getX_INLINE(), pLoopPlot->getY_INLINE(), pNearestCity->getX_INLINE(), pNearestCity->getY_INLINE());
}

if (getDomainType() == DOMAIN_SEA && !plot()->isWater())
{
   if (!pLoopPlot->isWater() || !pLoopPlot->isAdjacentToArea(area()))
   {
       iValue *= 3;
   }
}
else
{
   if (pLoopPlot->area() != area())
   {
       iValue *= 3;
   }
}

if (iValue < iBestValue)
{
   iBestValue = iValue;
   pBestPlot = pLoopPlot;
}

Let's go step by step:

1. iValue = (plotDistance(getX_INLINE(), getY_INLINE(), pLoopPlot->getX_INLINE(), pLoopPlot->getY_INLINE()) * 2);

Here we calculate the distance of the current plot to the current position of our unit and multiplicate by 2


2. iValue += plotDistance(pLoopPlot->getX_INLINE(), pLoopPlot->getY_INLINE(), pNearestCity->getX_INLINE(), pNearestCity->getY_INLINE());

Here we calculate the distance of the current plot to our nearest city. The pNearestCity was determined in the boring setup phase and is just the nearest city to the unit owned by the units owner. We add this distance to our value.


3. Now this part:


Code:
if (getDomainType() == DOMAIN_SEA && !plot()->isWater())
{
   if (!pLoopPlot->isWater() || !pLoopPlot->isAdjacentToArea(area()))
   {
       iValue *= 3;
   }
}
else
{
   if (pLoopPlot->area() != area())
   {
       iValue *= 3;
   }
}

The first if clause only applies to water units (DOMAIN_SEA). If:
  • the unit is a water unit
  • the current position of the unit is not a water tile (it's in a city)
  • the current plot in the rotation is also not a water tile
  • or the current plot in the rotation is not adjacent to the units current area 

So some further explanation is necessary here. What is an area? Well every connected body of water or land tiles is its own area. If all of the above applies the value is multiplied by 3. Basically what this means is that if our water unit is in a city or fort, all non-water plots or water tiles that are not adjacent to the area of the city or fort, get a higher value and are therefore less likely to be considered.

Now for all units that are not water units or are in a water tile, we just check if the current plot in the rotation is not in the same area as the unit and multiply our value by 3.


After the last step we have our final value for the plot and compare it to the best value. Plot with the smallest value wins and the unit will be teleported there. If no best plot can be found the unit is destroyed.

Now there are two more important things to know.

First in which order are we iterating through the plots?
We start with the 0/0 tile which should be in the bottom left of the map. Then we go through all the tiles in the x-coordinate before we start with the next y-coordinate.

What is the nearest city, if both cities have the same distance?
It's the city that is longer owned by the player. (Note a recapture of a city makes this city the newest city)

Summary

With the exception of some edge cases we can say that the unit is teleported towards the closest plot of the unit, with a preference for plots closer to the nearest city. I think it's best if I end this with a simple example:




The distances of the plots A, B and C to our nearest city 'Vienne' is 5, while D, E and F have the distance 1

We look at unit #1 in the west first. All plots A,B and C have the distance 1 to the unit, while D, E and F have the distance 3.

If we put this into the method above we get the following values for the plots:

A = (1*2)+5 = 7
B = (1*2)+5 = 7
C= (1*2)+5 = 7
D = (3*2)+1 = 7
E = (3*2)+1 = 7
F= (3*2)+1 = 7

All plots have the same distance, in that case it will be the plot that was checked first. This is the plot closest to the 0/0 point of the map, which in this example is C.

Now we look at unit #2 in the east. All plots A,B, and C have the distance 3 to the unit, while D, E and F have the distance 1. From our method we get:

A = (3*2)+5 = 11
B = (3*2)+5 = 11
C= (3*2)+5 = 11
D = (1*2)+1 = 3
E = (1*2)+1 = 3
F= (1*2)+1 = 3

It can only be D, E or F, because of their lower value. Between those 3 we once again look towards the closest plot to the 0/0 point of the map, which is F.
Mods: RtR    CtH

Pitboss: PB39, PB40PB52, PB59 Useful Collections: Pickmethods, Mapmaking, Curious Civplayer

Buy me a coffee
Reply

This is interesting, thanks again for looking into the code. If you have time later, would you be able to look at cases where there are units belonging to different players share tiles in various territories (including perhaps a third party that one player is at war with and the other has open borders with), and teleportation that occurs if war is declared? I have memories of some strange outcomes where lots of units got jumbled together and some ended up in unexpected places. Specifically, if there are cases under which you can during your turn cause another player's units to be teleported.

I vaguely recall the setup was something like:

Alice is at war with Bob and has a stack in Bob's territory.
Charlie has open borders with Bob and a unit sharing the tile Alice's stack is on.
Charlie declares war on Alice, and Alice's units end up teleported out of Bob's territory.

Reply

Interesting question. The question in that case is: Under which circumstances is jumpToNearestValidPlot called?

I will look into that
Mods: RtR    CtH

Pitboss: PB39, PB40PB52, PB59 Useful Collections: Pickmethods, Mapmaking, Curious Civplayer

Buy me a coffee
Reply

If Charlie declared war I'd expect his units to get teleported, not Alice's. Are you saying that isn't what happened?

If Alice declared then I'd expect her units to teleport, but would be surprised if they teleported out of Bob's territory. popcorn
Reply

Following up from my last question about teleportation, El Grillo had the following question:

When and how is teleportation triggered?

For this we have to look, where in the code the method jumpToNearestValidPlot of CvUnit.cpp is called. I won't give code examples for every single one of them, rather I will just name the class and method:

1. CvCity: popOrder

This is part of the production cycle of the city in this case the training of a unit:

Code:
if (GC.getUnitInfo(eTrainUnit).getDomainType() == DOMAIN_AIR)
{
   if (plot()->countNumAirUnits(getTeam()) > getAirUnitCapacity(getTeam()))
   {
       pUnit->jumpToNearestValidPlot();  // can destroy unit
   }
}

You can already see the check for DOMAIN_AIR. Here an air unit is teleported (or killed) if the city does not have enough capacity for it after teleportation. This could be exploited in theory to teleport your own units after production, but you would need to fill up a lot of cities and forts to get the teleported, where you want them to.

2. CvPlot:verifyUnitValidPlot

This is a big one. First let's start with what the method itself does. verifySpyUnitsValidPlot is called twice in here. The first time we iterate across all units on this plot. If the following applies:
  • the unit is not a cargo unit (galleys, galleons etc.)
  • the unit is not in an active battle
  • the unit is not in a valid domain
  • or the unit is can not enter an area
the unit is teleported.

The second time units are teleported is when the following applies:
  • the plot is owned by anyone (is in somebodys border for example)
  • the unit is not in an active battle
  • the unit is not part of the player, their team or their vassalls
  • a visible enemy unit is on the tile
  • the unit is not invisible
Now the last important thing to know is, when verifyUnitValidPlot is called and there are a lot of calls to it:
  • when you build the Great Wall, it is called for all plots of the whole map. This is because the Great Wall has bBorderObstacle set to 1 to push out Barbarians
  • when a player conqueres a city, which causes occupation turns in that city, it is called on all plots of the whole map
  • everytime the turn rolls, for every plot
  • if the owner of the plot changes (city capture, culture increasing), it is called for that plot
  • if an improvement that acts as a city (fort) is constructed on the plot, then this is called for this plot
  • on a war declaration it is called for all plots of the whole map
  • if you make peace with somebody, it is called for all plots of the whole map. Exception only if you vassallize somebody with the peace.
  • if open borders change, it is called for all plots of the whole map
3. CvTeam: verifySpyUnitsValidPlot

As the name already suggests, this teleport is only called on spy units. More importantly all spy units of all players are teleported, who cannot enter borders. A spy cannot enter borders if the player has any espionage mission to which the following applies:
  • The mission has the tag bIsPassive in the XML set to 1. These are the passive missions like 'see demographics', 'see research' etc.
  • The mission has the tag bNoActiveMissions in the XML set to 1. From the mod wiki it says 'If the mission requires no other active missions to be taking place.' There is only one mission in the XML that has this set to 1: ESPIONAGEMISSION_NO_ACTIVE_MISSIONS. This mission has bIsPassive also set to 1
  • The method canDoEspionageMission returns true for the player. This method checks tech and espionage costs. I mentioned already that ESPIONAGEMISSION_NO_ACTIVE_MISSIONS fulfills all requirements so far. Here this mission fails because it has a cost of -1, which returns false in the canDoEspionageMission method.
So what this boilds down to is, that spies can be teleported in theory, but because there are no missions fullfilling all requirements, this never happens. Lastly it's important to note when verifySpyUnitsValidPlot is called. This is called everytime espionage points are invested into somebody.

4. CvUnit: convert

This is not original code, rather it was added by BUG as part of the unoffical patch. 

Code:
if (cargoSpaceAvailable(aCargoUnits[i]->getSpecialUnitType(), aCargoUnits[i]->getDomainType()) > 0)
{
   aCargoUnits[i]->setTransportUnit(this);
}
else
{
   aCargoUnits[i]->setTransportUnit(NULL);
   aCargoUnits[i]->jumpToNearestValidPlot();
}
This code is called, when you upgrade a unit. It ensures that transported units are teleported, if you upgrade a cargo unit to a unit that can't cargo. This can't happen in the normal game, because this upgrade path is not possible. But it was required for some mods.

5. CvUnit: verifyStackValid

This teleports the unit if it is not invisible and if there is an enemy unit on the same plot. This method itself is called in CvTeam declareWar. There it is called only for the player, who declares war. For this player it is called for every unit that the player owns.



With all this knowledge I want to come back to the scenario that El Grillo described here:

(October 11th, 2020, 18:09)El Grillo Wrote: Alice is at war with Bob and has a stack in Bob's territory.
Charlie has open borders with Bob and a unit sharing the tile Alice's stack is on.
Charlie declares war on Alice, and Alice's units end up teleported out of Bob's territory.

I've setup a simple test scenario and I can confirm this exact behaviour. It is caused by points 5. and 2. there specifically the call on a war declaration to all plots. There we have the following code:

Code:
       GC.getMapINLINE().verifyUnitValidPlot();

       for (iI = 0; iI < MAX_PLAYERS; iI++)
       {
           if (GET_PLAYER((PlayerTypes)iI).getTeam() == getID())
           {
               GET_PLAYER((PlayerTypes)iI).verifyUnitStacksValid();
           }
       }


We have the call to all plots of the map first (point #2) and after that we teleport all of the units of the declaring player (point #5). The problem is that the first call GC.getMapINLINE().verifyUnitValidPlot(); has no knowledge about who declared the war. It goes through the units in the order in which they entered the plot. So going back to El Grillos example. Because Alice entered the plot first her unit gets teleported in Bobs territory by the war declaration from Charlie.

I defintely deem this a bug and would like to direct the discussion to my mod section: Discussion
Mods: RtR    CtH

Pitboss: PB39, PB40PB52, PB59 Useful Collections: Pickmethods, Mapmaking, Curious Civplayer

Buy me a coffee
Reply



Forum Jump: