[MODCOMP] Scaling Garrison Unhappiness

Imperator Knoedel

Simperator Knoedel
Joined
Nov 11, 2011
Messages
8,840
Location
The heart of the beast
Cities become unhappy from insufficient military protection even with garrisoned units, if the garrison's combined Garrison Value is too low when compared with the city's population size.

A unit's Garrison Value is now also displayed in its tooltip.

Some buildings can help alleviate the problem.


Spoiler Screenshots :

Cities of size 9 and higher are no longer completely satisfied with a single Warrior as garrison.


Hovering over the Warrior reveals its Garrison Value.


The Barracks and some other buildings can help an outnumbered garrison keep order.
 
Last edited:
This line looks suspicious:
iAnger /= iGarrison;
Shouldn't iAnger be multiplied by getPopulation() first?
Edit: Or rather times (getPopulation()-iGarrison) and divided by getPopulation().
For reference, let me cross-link to your post with the source code: link

I've implemented something similar based on defensive combat strength, i.e. combat strength after applying defensive modifiers (and generic ones from Combat promotions) except for city culture defense.
Here's my code: CvCity::getNoMilitaryPercentAnger CvCity::garrisonStrength
I'm using half the city population as the target value. Using the full population is nicer, but also makes this a more prominent change. And I have a clause that halves the strength counted for units that the city can't train anymore.

Despite naming my strength value function "garrisonStrength", I hadn't thought of using the culture garrison value. That has some advantages – it's, in a way, less of a novelty and non-garrison units like ships don't have to be explicitly excluded.
 
Last edited:
This line looks suspicious:
iAnger /= iGarrison;
Shouldn't iAnger be multiplied by getPopulation() first?
Edit: Or rather times (getPopulation()-iGarrison) and divided by getPopulation()..

You mean like this:

Code:
            iAnger += GC.getDefineINT("NO_MILITARY_PERCENT_ANGER");
            iAnger *= (getPopulation()-iGarrison);
            iAnger /= getPopulation();
Good call, I think I like this method better. I played around with other possibilities but in the end just settled for the most simple one, but this one is closer to what I had in mind originally, thanks.
 
New Update: Added an XML Tag for buildings to decrease unhappiness from lack of military protection, with or without garrison.

The relevant code now looks like this:

Code:
int CvCity::getNoMilitaryPercentAnger() const
{
    int iAnger;

    iAnger = 0;

    if (getMilitaryHappinessUnits() == 0)
    {
        iAnger += GC.getDefineINT("NO_MILITARY_PERCENT_ANGER");
    }

//KNOEDELstart
    else
    {
        CLLNode<IDInfo>* pUnitNode;
        CvUnit* pLoopUnit;
        int iGarrison;

        iGarrison = 0;

        pUnitNode = plot()->headUnitNode();

        while (pUnitNode != NULL)
        {
            pLoopUnit = ::getUnit(pUnitNode->m_data);
            pUnitNode = plot()->nextUnitNode(pUnitNode);

            iGarrison += pLoopUnit->getUnitInfo().getCultureGarrisonValue();
        }

        if (getPopulation() > iGarrison)
        {
            iAnger += GC.getDefineINT("NO_MILITARY_PERCENT_ANGER");
            iAnger *= (getPopulation()-iGarrison);
            iAnger /= getPopulation();
        }
    }

    iAnger *= std::max(0, (getGarrisonUnhappinessModifier() + 100));
    iAnger /= 100;
//KNOEDELend

    return iAnger;
}
 
Very nice!
A few suggestions if possible to do:
  • Only cities close to the border should scale. Ones far from the borders should feel safe even unprotected
  • Could civics also affect the scaling? Some sould increase the required number of units, while some other could decrease it.
 
I've also made some tweaks to my own variant. None that are relevant here except that I measured AI turn times and they were taking a few percentage points longer than with the BtS rule. Imp. Knoedel's code is faster than mine was because it doesn't check allUpgradesAvailable. Still, CvCity::unhappyLevel gets called a lot during low-level AI computations and the AI can have huge garrisons in some of its cities. A simple tweak should mostly take care of any such problems: Stop the while (pUnitNode != NULL) loop once iGarrison exceeds the target value (population). However, if the distance to the nearest non-friendly tile matters – I rather like the idea –, then the result of getNoMilitaryPercentAnger may have to be cached and updated once per turn and in CvUnit::setXY, CvCity::setPopulation and when the GarrisonUnhappinessModifier changes.
 
Top Bottom