Flanking(the new ability for mounted units to kill siege weapons) how does it work?

oyzar

Have quit civ/forums
Joined
Oct 7, 2006
Messages
6,923
Location
Norway
How does flanking work? I know it deals damage if the unit survives(either withdraws or wins) but how much and to how many and how? Does it work like colleteral damage? How many units can you deal damage to at once?

Since colleteral damage is not affected by HP of the unit, you would need to kill them to have much effect. Hence i am wondering how many mounted units you would need to kill a stack of siege weapons(and other units). For example say there was a stack with 8 catapults and assorted units(among them quite a number of spears and war elephants), how many HA's would you need to take out the cats(assuming half survive due to 50% withdrawl chance with flanking 2).

http://www.nopaste.com/p/ac7rSHmlx here is the relevant code btw.
 
From the mighty siege of Seville I remember the knights dealing like 30% damage to 3-5 units maybe? The catapults were at either 3.5 or 2 or 0.5 strength til dead. Can't say if it's different rates for HA's or Cavs or whatever. But sorry, no hard facts here, just vague recollections. I mostly just wrote since you were gettin no love.
 
I don't have any specific facts either, but it is pretty sweet. Cavalry doesn't just cause collateral damage, it kills flanked catapults (after a while). e.g. I think 4 cavarly could kill a stack of 8 catapults.
 
I have found that flanking affects 6 units and it takes 6 to do the killing damage
 
in our battles I don't recall a single time my cavalry flanked one of your cannons, so I'm curious about this as well
there's definitely a random element, sometimes you get nothing at all
 
1. You get a flank attack if you WIN or WITHDRAW.

2. Flanking damage is equal to one round of combat damage. If your horse archer is doing 13hp to the war elephant, it will do 13hp to the catapults too. If it does 21hp to the axeman, it will do 21hp to the catapults too.

3. Early units can flank 6 enemies, cavalry 7, and gunships 8.

4. There is a chance for individual siege units to block your flank attack, but you get a shot at every siege unit in the stack so you usually reach the limit anyway.


Taken together, 1 and 2 imply that sometimes it's more effective to go for combat promotions to maximize flanking damage.
 
What about the case where the flanking unit completely destroys a full hit point defender? Obviously the catapult doesn't take that damage. Or is that full 100 hp distributed across all of the siege in the stack?

Of course, combat promos are only optimal if your unit has a decent chance of winning to begin with.
 
Flanking II Horse Archers vs Combat II War Elephants

0.5% victory odds
49.75% retreat odds
50.25% chance to flank

13HP damage per catapult

6 catapults damaged per successful flank

0.5025 * 13 * 6 = 39HP damage per horse archer

You will need 2.6 horse archers per catapult to wipe them out.

Of course, this assumes the defender has 2.6 war elephants per catapult to defend. In practice, your odds and damage will improve as the stack gets beat up - maybe only 2 horse archers per catapult are needed.
 
Flanking II Horse Archers vs Combat II War Elephants

0.5% victory odds
49.75% retreat odds
50.25% chance to flank

13HP damage per catapult

6 catapults damaged per successful flank

0.5025 * 13 * 6 = 39HP damage per horse archer

You will need 2.6 horse archers per catapult to wipe them out.

Of course, this assumes the defender has 2.6 war elephants per catapult to defend. In practice, your odds and damage will improve as the stack gets beat up - maybe only 2 horse archers per catapult are needed.

It's mostly worthwhile when your opponent has neither war elephants nor spearmen in the stack in the open field. Flank attacks are also more effective with cavalry, as their counter unit doesn't dominate them quite as much as elephants versus horse archers, and their withdrawal chance is higher. There is no medieval light cavalry unit though, so your surviving horse archer flanking units are not worth much during that period.

However, my usual use of horse archers is when a city has a really strong defender, there are no spearmen or war elephants, and my catapults/trebuchets have less than a 50 percent chance of winning. The disadvantage of this, of course, is that you can't flank siege weapons that are posted in a city.
 
void CvUnit::flankingStrikeCombat(const CvPlot* pPlot, int iAttackerStrength, int iAttackerFirepower, int iDefenderOdds, int iDefenderDamage, CvUnit* pSkipUnit)
{
if (pPlot->isCity(true, pSkipUnit->getTeam()))
{
return;
}
I'm guessing if defending tile is city, skip everything
CLLNode<IDInfo>* pUnitNode = pPlot->headUnitNode();

std::vector< std::pair<CvUnit*, int> > listFlankedUnits;
while (NULL != pUnitNode)
{
Create an array of units to be targeted by flanking.
CvUnit* pLoopUnit = ::getUnit(pUnitNode->m_data);
pUnitNode = pPlot->nextUnitNode(pUnitNode);

if (pLoopUnit != pSkipUnit)
{
if (!pLoopUnit->isDead() && isEnemy(pLoopUnit->getTeam(), pPlot))
{
if (!(pLoopUnit->isInvisible(getTeam(), false)))
{
if (pLoopUnit->canDefend())
{
Checking individual unit and get status ... so it can check whether it's dead, invisible, or non-enemy. I guess candefend invalidates flanking workers, settlers, missionaries, etc.
int iFlankingStrength = m_pUnitInfo->getFlankingStrikeUnitClass(pLoopUnit->getUnitClassType());

if (iFlankingStrength > 0)
{
And skip units that don't have flanking damage, which is defined by iFlankingStrength
int iFlankedDefenderStrength;
int iFlankedDefenderOdds;
int iAttackerDamage;
int iFlankedDefenderDamage;

getDefenderCombatValues(*pLoopUnit, pPlot, iAttackerStrength, iAttackerFirepower, iFlankedDefenderOdds, iFlankedDefenderStrength, iAttackerDamage, iFlankedDefenderDamage);
I'm guessing that iFlankedDefenderOdds is based off of the odds of your attacker winning against the defender, although I'm not sure. I'm not sure if iFlankedDefenderDamage and such is based off of the base or modified strength (including hp adjustments)
if (GC.getGameINLINE().getSorenRandNum(GC.getDefineINT("COMBAT_DIE_SIDES"), "Flanking Combat") >= iDefenderOdds)
{
int iCollateralDamage = (iFlankingStrength * iDefenderDamage) / 100;
int iUnitDamage = std::max(pLoopUnit->getDamage(), std::min(pLoopUnit->getDamage() + iCollateralDamage, collateralDamageLimit()));

if (pLoopUnit->getDamage() != iUnitDamage)
{
listFlankedUnits.push_back(std::make_pair(pLoopUnit, iUnitDamage));
}
}
}
}
}
}
}
}
Roll random number to see if your flanking damage hits the unit based on iDefenderOdds. Now update the defending units damage by increasing it by iCollateralDamage, which is calculated as the attacking damage * iFlankingDamage. If flanking damage fails, then push to the next unit in the stack, presumably something having to do with the flanking damage limit.
int iNumUnithorsehocky = std::min((int)listFlankedUnits.size(), collateralDamageMaxUnits());

for (int i = 0; i < iNumUnithorsehocky; ++i)
{
int iIndexHit = GC.getGameINLINE().getSorenRandNum(listFlankedUnits.size(), "Pick Flanked Unit");
CvUnit* pUnit = listFlankedUnits[iIndexHit].first;
int iDamage = listFlankedUnits[iIndexHit].second;
pUnit->setDamage(iDamage, getOwnerINLINE());
if (pUnit->isDead())
{
CvWString szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_KILLED_UNIT_BY_FLANKING", getNameKey(), pUnit->getNameKey(), pUnit->getVisualCivAdjective(getTeam()));
gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer, GC.getEraInfo(GC.getGameINLINE().getCurrentEra()).getAudioUnitVictoryScript(), MESSAGE_TYPE_INFO, NULL, (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pPlot->getX_INLINE(), pPlot->getY_INLINE());
szBuffer = gDLL->getText("TXT_KEY_MISC_YOUR_UNIT_DIED_BY_FLANKING", pUnit->getNameKey(), getNameKey(), getVisualCivAdjective(pUnit->getTeam()));
gDLL->getInterfaceIFace()->addMessage(pUnit->getOwnerINLINE(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer, GC.getEraInfo(GC.getGameINLINE().getCurrentEra()).getAudioUnitDefeatScript(), MESSAGE_TYPE_INFO, NULL, (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE());

pUnit->kill(false);
}

listFlankedUnits.erase(std::remove(listFlankedUnits.begin(), listFlankedUnits.end(), listFlankedUnits[iIndexHit]));
}

if (iNumUnithorsehocky > 0)
{
CvWString szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_DAMAGED_UNITS_BY_FLANKING", getNameKey(), iNumUnithorsehocky);
gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer, GC.getEraInfo(GC.getGameINLINE().getCurrentEra()).getAudioUnitVictoryScript(), MESSAGE_TYPE_INFO, NULL, (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pPlot->getX_INLINE(), pPlot->getY_INLINE());

if (NULL != pSkipUnit)
{
szBuffer = gDLL->getText("TXT_KEY_MISC_YOUR_UNITS_DAMAGED_BY_FLANKING", getNameKey(), iNumUnithorsehocky);
gDLL->getInterfaceIFace()->addMessage(pSkipUnit->getOwnerINLINE(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer, GC.getEraInfo(GC.getGameINLINE().getCurrentEra()).getAudioUnitDefeatScript(), MESSAGE_TYPE_INFO, NULL, (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE());
}
}
}
Got too bored to continue.
 
I'm guessing that the combat odds affects the odds of an individual targeted siege unit being hit. Also, if the iDefenderStrength is affected by hp, then you should do more flanking damage if your siege hits first.
 
mounted units (HA, Crui & cav) with 2 flank promos can be devestating...

A stack of 20 knights with flanking 2 can wipe out 15 catapults or trebs protected by 20 other units, even if those units are pikes or war elephants....I do not even cosnsider a stack of flanking 2 capharacts!!

Withdraw chances are good so you will deal damage even if not winning the fight.

Worst of all is cavalry of course as they come at an era when you can have level 4 or 5 cavalry off the gate...and having flanking 2, pinch cavalry will wipe out anything of this era...russian cavalry with 4 promos of the gate are just unstopable..

In online multplayer games the flanking ability is absolutely broken...so much that in a multiplayer mod that is being used for competitive tournaments there is an option to remove flanking from the game...
 
Oooh, I arrive late at the party -- well here is my attempt:

A mounted unit/Gunship can hit siege units via flank attack if it has a iFlankingStrength > 0 for the siege's class (=FlankingStrikeUnitClass) set in CIV4UnitInfos.xml. In BTS 3.17 iFlankingStrength = 100 for all allowed combinations. The maximum number of units to flank is given by the flanker's iCollateralDamageMaxUnits; iCollateralDamageLimit = 100 means complete kills via flanking are possible.



Both the probability of a siege unit to escape flanking and the amount of hitpoints it will lose if it doesn't escape depend on the details of the direct combat between the mounted unit and the defender (3.17). The code is quite misleading as it contains an unnecessary call of getDefenderCombatValues() for a direct combat between the mounted unit and a siege unit which does NOT happen, the returned values are NOT used, I guess this is the old logic.

The chance of a siege unit to escape flanking is a function of the chance of the defending unit to win a round during its direct combat with the flanker, and the withdrawal probability of the mounted unit (native withdrawal + extra withdrawal from flanking and tactics promotions):

DefenderOdds = CurrentCombatStrength_Defender / (CurrentCombatStrength_Attacker + CurrentCombatStrength_Defender) * 100%
"EscapeOdds" = DefenderOdds * (100 - withdrawalProbability) / 100

CurrentCombatStrength = MaxCombatStrength * CurrentHitPoints/100
MaxCombatStrength = BaseCombatStrength * Modifier


Example: healthy Keshik vs. injured Maceman (50HP) + stack of 8 healthy Catapults



Code:
[SIZE="3"]Direct Combat Keshik vs. Maceman:
Attacker - Keshik				Defender - Maceman
BaseCombatStrength    = 6; 			BaseCombatStrength    = 8; 
Modifier              = 100; 			Modifier              = 100; 
MaxCombatStrength     = 600; 			MaxCombatStrength     = 800; 
CurrentHitPoints      = 100; 			CurrentHitPoints      = 50; 
CurrentCombatStrength = 600;			CurrentCombatStrength = 400;

DefenderOdds = 400 / (600 + 400) * 100% = 40%
[COLOR="RoyalBlue"][B]EscapeOdds   = 40% * (100-20)/100       = 32%[/B][/COLOR][/SIZE]

The EscapeOdds are equal and fixed for all siege units! The game compiles a list of all possible flank targets in the defender's tile which don't manage to escape the flank attack by rolling a random number for each. Out of that list up to the flanker's iCollateralDamageMaxUnits siege units will get drawn in a random order to receive the damage (which might lead to getting killed).

The amount of flanking damage is indeed equal to the number of hitpoints the flanker takes away from the defender by winning 1 round of their direct combat. Hence it is a function of the attacker's and defender's respective current fire powers, with a unit's fire power being the average of its MaxCombatStrength and its CurrentCombatStrength. All hit siege units will suffer the same damage independent of their strength and health (A Gunship will take as many hitpoints away from a healthy Mobile Artillery as from a wounded Cannon!).

CurrentFirePower = (MaxCombatStrength + CurrentCombatStrength + 1)/2 { = MaxCombatStrength for healthy units }

The average of the combatants' current fire powers (=StrengthFactor) is used to calculate the number of hitpoints a unit takes away from the enemy by winning one combat round according to:

StrengthFactor = (CurrentFirePower_Attacker + CurrentFirePower_Defender +1)/2
Damage_Defender = 20 * (CurrentFirePower_Attacker + StrengthFactor)/(CurrentFirePower_Defender + StrengthFactor) = HPs Attacker takes from Defender
Damage_Attacker = 20 * (CurrentFirePower_Defender + StrengthFactor)/(CurrentFirePower_Attacker + StrengthFactor) = HPs Defender takes from Attacker


Code:
[SIZE="3"]Example (StrengthFactor = 600)	
Attacker - Keshik			Defender - Maceman
CurrentFirePower      = 600; 		CurrentFirePower      = (800+400+1)/2 = 600;
Damage_Attacker       = 20;		Damage_Defender       = 20;[/SIZE]

The following picture shows a slightly more verbose combat help with an extra line for the XPs the attacker will get for winning this combat, the green / red number of HPs the attacker / defender takes away in 1 combat round, and the Odds for the attacker to win a combat round (= 100 - DefenderOdds). Note that the EscapeOdds (32%) of the siege units have nothing to do directly with the Combat Odds (93.6%) or the Retreat Odds (1.3%).



The Keshik kills the Maceman winning 3 consecutive combat rounds (Maceman: 50HP->30HP->10HP->Death). 3 of the 8 Catapults managed to escape, so the other 5 are in the list of the valid flank attack targets, therefore all of them lose 20 HPs since the Keshik can flank up to 6 units (Horse Archer class). The Keshik's First Strike only increased its chance to survive the direct combat, while having no effect at all during the whole flanking procedure.



Here is a picture of a FlankingII Keshik vs. a CombatII War Elephant:



The 70% DefenderOdds are reduced to 35% EscapeOdds for the Catapults which is relevant -- turning Dave's numbers around it takes 24 FlankingII HAs on average to guarantee the flanking death of 1 healthy Catapult defended by enough C2 Jumbos (12 HAs don't survive to flank, Cat escapes 4 times, 8 * 13 HPs damage needed to kill).
 
Thanks for the info dan. Especially the fact that you check if a unit escapes before you put them in the list(meaning that with enough defending cats you'll always damage 6 cats), and the fact that escape odds is dependant on the withdrawl ability and not just the chance to win a combat(meaning it is always way under 50% as long as you have over 50% withdrawl chance).

I am just curious though, isn't this the code that handles wether a unit gets in the damage list? I don't see anything about withdrawl odds or escapeodds here:
Code:
if (GC.getGameINLINE().getSorenRandNum(GC.getDefineINT("COMBAT_DIE_SIDES"), "Flanking Combat") >= iDefenderOdds)
{
      int iCollateralDamage = (iFlankingStrength * iDefenderDamage) / 100;
      int iUnitDamage = std::max(pLoopUnit->getDamage(), std::min(pLoopUnit->getDamage() + iCollateralDamage, collateralDamageLimit()));

      if (pLoopUnit->getDamage() != iUnitDamage)
      {
            listFlankedUnits.push_back(std::make_pair(pLoopUnit, iUnitDamage));
      }
}

There is a slight error in your post, but it doesn't affect any results or reprecursions.

CurrentFirePower = (MaxCombatStrength + CurrentCombatStrength + 1)/2 { = MaxCombatStrength for healthy units + 0.5}
and not as you listed
CurrentFirePower = (MaxCombatStrength + CurrentCombatStrength + 1)/2 { = MaxCombatStrength for healthy units }
 
^All those numbers are integers so the "+1" is only added before the "/2" to avoid loss due to the truncating, so it will not add a "+0.5" to the result. --> CurrentFirePower = MaxCombatStrength for healthy units. ;)
 
Right for some reason i thought firepower was not integers, for example 13.6 with the elephants. I am still unsure where in the code the escape odds come from though...
 
The quoted code:
Code:
if (GC.getGameINLINE().getSorenRandNum(GC.getDefineINT("COMBAT_DIE_SIDES"), "Flanking Combat") >= iDefenderOdds)
checks whether a siege unit "escapes"
Code:
int iCollateralDamage = (iFlankingStrength * iDefenderDamage) / 100;
All mounted units and Gunships have iFlankingStrength = 100 for all siege units they can damage. Therefore the flanking damage is equal to the defender's damage.
Code:
      int iUnitDamage = std::max(pLoopUnit->getDamage(), std::min(pLoopUnit->getDamage() + iCollateralDamage, collateralDamageLimit()));
checks whether the flanker can kill the siege unit, or whether there is a limit (there is none).
Code:
      if (pLoopUnit->getDamage() != iUnitDamage)
      {
            listFlankedUnits.push_back(std::make_pair(pLoopUnit, iUnitDamage));
      }
puts the siege unit into the list of valid flanking targets
 
The Escape Odds are the iDefenderOdds in CvUnit::flankingStrikeCombat() which get the iAttackerKillOdds passed from CvUnit::resolveCombat().
 
The Escape Odds are the iDefenderOdds in CvUnit::flankingStrikeCombat() which get the iAttackerKillOdds passed from CvUnit::resolveCombat().

This simple sentence actually solves most of the mystery... Thanks a lot, guess i should have looked at what actually got passed in.
 
Top Bottom