How to calculate combat odds (moved from the Bugs subforum)

stachnie

Theorist
Joined
Jan 3, 2002
Messages
532
Location
Not far from Krakow, Poland
It deals with ratios of unit strength totals. Damage ranges each round from MISS to 6-40 or so (I think - I'd have to look to say for sure). Now there are manipulations from the original formula to account for a number of new effects as well... get's quite intricate.

O.K., I think I've caught it. Damage ranges make it more complicated than I thought, but this is still manageable.

We have two units: attacker (base strength A) and defender (base strength D). Maximum HP for A is AHPmax, for D is DHPMax; initial HP at the start of combat (because they don't need to be at full strength) will be AHPinit and DHPinit; actual HP at the beginning of the round will be AHP and DHP.

Total attack of the attacker will be Atot=A*(1+bonuses-penalties)*(AHP/AHPmax) and total defence of the defender will be Dtot=D(1+bonuses-penalties)*(DHP/DHPmax). A chance to win or lose each round perhaps will be governed by the ratio of Atot/Dtot:

Atot/(Atot+Dtot) to win
Dtot(Atot+Dtot) to lose

Bonuses and penalties may depend on the round, so I will define a set of functions

pn(i,AHP,DHP)

for n=1 to ntot which represent outcome n for the round i. The outcomes may be:

p1(i,AHP,DHP) - A wins but misses
p2(i,AHP,DHP) - A wins and takes minimum possible number of HP
p3(i,AHP,DHP) - A wins and takes minimum possible number of HP + 1
...
- A wins and takes maximum possible number of HP
- A wins but D retreats
- D wins but misses
- D wins and takes minimum possible number of HP
- D wins and takes minimum possible number of HP + 1
...
pntot-1(i, AHP,DHP) - D wins and takes maximum possible number of HP
pntot(i, AHP,DHP) - D wins but A retreats

p1(i,AHP,DHP)+p2(i,AHP,DHP)+p3(i,AHP,DHP)+... should add up to 1.0 for each i, AHP and DHP.

Now we define two arrays representing the state of the battle, S0 and S1. They will be arrays of real numbers indexed (-1...AHPinit) and (-1...BHPinit): these indexes represent actual HP of the attacker and defender. 0 means that the battle is lost, -1 means that the unit retreated.

Before the fight: all elements of S1=0, almost all elements of S0=0, S0(AHPinit,DHPinit)=1.

i:=1 {the number of the round}

@NextRound

FOR k:=1 to AHPinit do
FOR l:=1 to DHPinit do
FOR n:=1 to ntot do

BEGIN

{of course, for a given n only one case is chosen, so the last FOR loop may be replaced by explicit calls of all subsequent expressions with all possible pn's}

{if A loses some HP -> I will call it HPlost}
S1(k-HPlost,l) := S1(k-HPlost,l) + pn(i,k,l)*S0(k,l)

{if D loses some HP}
S1(k,l-HPlost) := S1(k,l-HPlost) + pn(i,k,l)*S0(k,l)

{if A or D wins but misses}
S1(k,l) := S1(k,l) + pn(i,k,l)*S0(k,l)

{if A retreats}
S1(-1,l) := S1(-1,l) + pn(i,k,l)*S0(k,l)

{if A loses and k<=HPlost -> A loses the fight}
S1(0,l) := S1(0,l) + pn(i,k,l)*S0(k,l)

{if D retreats}
S1(k,-1) := S1(k,-1) + pn(i,k,l)*S0(k,l)

{if D loses and l<=HPlost}
S1(k,0) := S1(k,0) + pn(i,k,l)*S0(k,l)

END

FOR k:=1 to AHPinit do S1(k,0):=S1(k,0)+S0(k,0) {add all cases when D lost earlier}
FOR k:=1 to AHPinit do S1(k,-1):=S1(k,-1)+S0(k,-1) {add all cases when D retreated earlier}

FOR l:=1 to DHPinit do S1(0,l):=S1(0,l)+S0(0,l) {add all cases when A lost earlier}
FOR l:=1 to DHPinit do S1(-1,l):=S1(-1,l)+S0(-1,l) {add all cases when A retreated earlier}

i:=i+1

S0:=S1 {copy whole array}
S1:=0 {clear the array}

GOTO @NextRound

The loop ends when for all l=1...DHPinit and k=1...AHPinit S0(k,l)=0 so the only nonzero elements are S0(k,-1), S0(k,0), S0(-1,l) and S0(0,l).

Probability that A wins: the sum of all S0(k,0)
Probability that A wins but D retreats: the sum of all S0(k,-1)
Probability that D wins: the sum of all S0(0,l)
Probability that D wins but A retreats: the sum of all S0(-1,l)


However, usually this will be an infinite loop, because when A or D misses and misses (no loss of HP), it may be repeated again and again. In order to avoid that, it is necessary to include some condition for the cases when A or D misses, e.g. if pn(i,k,l)*S0(k,l) gets below some limit (e.g. 1/(AHPmax*BHPMax*1000000), it gets assigned to the situation when A or D wins but with minimal HP loss for the opponent.

Uff! I think that if I had made no stupid mistakes and if I did not overlook something, it should work.

S.
 
@Alberts:

It's been a while since I referenced the odds calculations in the code... And I know we have a few places where the calculation is made in slightly different ways for differing reasons.

I trust your ability to quickly evaluate aspects of the coding so if you could, would you please give me a list of the functions that odds calculations are taking place and your impression of what they are being calculated for? You may be able to see some things I've been overlooking (or will overlook as I go to share how the combat system and odds currently compile here.)
 
We have three of them the actual calculations are these.

  1. CvUnitAI::AI_attackOddsAtPlotInternal
    Used by the AI to evaluate things like Danger, BestDefender, BestAttacker and so on.
  2. getCombatOdds
    Used to display CombatOdds and in the actual Combat calculation CvUnit::resolveCombat.
  3. getCombatOddsSpecific
    Used to display CombatOdds.

The actual Combat happens in CvUnit::resolveCombat.
 
getCombatOdds
Used to display CombatOdds and in the actual Combat calculation CvUnit::resolveCombat.

getCombatOddsSpecific
Used to display CombatOdds.
If I'm not mistaken, isn't the first generating the odds used to determine the actual ratios USED in combat to influence hit roles and damage in actual combat while the second is the overall battle odds calculation that is displayed (and should be used for AI determinations of combat odds?)
 
I'am not sure why we need those two, i know they are a bit different. But 3) should do it and 2) could be removed.

The AI code is a bit different because of the Best Attacker Cache but it's result has to be the same.
 
There needs to be the separation between odds used in hit and damage calculations and odds for the overall battle calculation.

Here's why...

The hit and damage odds are not taking into account likelihoods for withdraw, repel, and knockback - and they shouldn't as the chance of these things happening is based on particular checks during combat that are not related to the attack rolls.

The odds for attack rolls are actually odds to hit and the ratios between the unit strengths determine damage.

All of this is taken into consideration for the overall battle result likelihood - so there is a very distinct difference between result likelihood and the chance of landing a hit in battle.

The overall battle result likelihood is what the players should see and what the AI uses as part of its strategic considerations. But the odds of landing a hit in a given round is far more fundamental.

From what I can tell, #2 is the odds of landing a hit while #3 is the displayed overall result of combat odds while #1 is basically #3 but specialized for the AI. If it's absolutely necessary for the AI to have its own full calculation (why it can't draw on #3 rather than having its own full odds calculation process involved that needs to be updated any time 2 and 3 are updated is beyond me. Goes a long ways towards explaining why best attackers may be determined incorrectly.)
 
The 'odds' used in (at least some of) the AI routines (as opposed to the display-for-user ones) are not actually odds at all. They are weighted by the (assessment of) the value of the units involved, so they are the odds of a beneficial outcome, not of winning or losing a particular combat (so a 25% chance of killing a unit twice as valuable as the attacker will result in 50% 'odds' in those routines).

I forget exactly which routines do this, but certainly the ones the AI uses to calculate odds for GROUPS (not individual units) work that way.
 
The 'odds' used in (at least some of) the AI routines (as opposed to the display-for-user ones) are not actually odds at all. They are weighted by the (assessment of) the value of the units involved, so they are the odds of a beneficial outcome, not of winning or losing a particular combat (so a 25% chance of killing a unit twice as valuable as the attacker will result in 50% 'odds' in those routines).

I forget exactly which routines do this, but certainly the ones the AI uses to calculate odds for GROUPS (not individual units) work that way.

The AI uses CvUnitAI::AI_attackOddsAtPlotInternal for those things. But it also affects Human players because as example it is used to find the BestAttacker in a Group.
 
The AI uses CvUnitAI::AI_attackOddsAtPlotInternal for those things. But it also affects Human players because as example it is used to find the BestAttacker in a Group.

True, but if you want to choose what unit to attack with don't just use the stack - select the unit! Ideally all this would be less entangled however - there's a lot of legacy code structure here
 
OK, Stachnie, I've started with CvGameCoreUtils::getCombatOdds.

Took out all the comments.

@Sparth: Just reviewing this briefly I can see that my assumption of what you meant regarding these being the odds used in combat to determine if a strike lands was wrong. I think you're right here that there's much 'entanglement' as Koshling puts it. And yes, it's probably Legacy entanglement but we should probably be doing what we can to C2C DIS-entangle it.

@ALL: I'm working on some other things right now so reviewing all of this is a terribly passive affair for me at the moment. Seems like every month I work on this mod I understand on a deeper level still just how large the chunk I bit off to chew with the combat mod really is. I'm past seeing what you meant Koshling and I deeply apologize for my novice errors. I feel like my growing clarity of the distance to goal is making the goal appear to race away from the point of completion at a rate ten times faster than I can possibly work towards it here.

In part this is due to what I've added still meaning so much more to do than I'd originally imagined, but it's also due to the fact that there are horrendous problems that already existed that are much deeper than what I had begun to realize at the time. I can see why Koshling was so focused on the AI - I'd assumed it would be much more adaptive from the beginning. I can now see how one SMALL change can be a MAJOR problem for the underlying coding that was not only not prepared for it but how negatively it can react in the most complex areas of the processing web.

Anyhow, with my deepest apologies I still stay to try to do what I can.

This odds bit I feel is a major problem for the mod and is more than just a hindrance to my being able to have the game successfully work with the things I'd like it to be able to do. If the Player is getting whacked out results when attacking with a stack, then the AI presumably would be as well if it's all drawing on the same functions. It has been appearing that the AI is evaluating danger a bit off - hunters won't attack the animals, escorted settlers run for home as soon as they spot a duck, cities aren't being taken when it would be beneficial to do so - these may all relate to odds problems.

Anyhow, Stachnie, take a look through this function and see how far you can get before it loses you in any way. If you find any portion of it unexplained please just ask and I'll fill in the gaps. Once you understand this one we'll look at the others. If you need us to start with even the basic combat structure, we can do that too.
Spoiler :
Code:
int getCombatOdds(CvUnit* pAttacker, CvUnit* pDefender)
{
	float fOddsEvent;
	float fOddsAfterEvent;
	int iAttackerStrength;
	int iAttackerFirepower;
	int iDefenderStrength;
	int iDefenderFirepower;
	int iDefenderOdds;
	int iAttackerOdds;
	int iStrengthFactor;
	int iDamageToAttacker;
	int iDamageToDefender;
	int iNeededRoundsAttacker;
	int iNeededRoundsDefender;
	int iMaxRounds;
	int iAttackerLowFS;
	int iAttackerHighFS;
	int iDefenderLowFS;
	int iDefenderHighFS;
	int iFirstStrikes;
	int iDefenderHitLimit;
	int iI;
	int iJ;
	int iI3;
	int iI4;
	int iOdds = 0;

	int iDefenderSupportStrength = 0;
	int iAttackerSupportStrength = 0;
	if (GC.getGameINLINE().isOption(GAMEOPTION_STRENGTH_IN_NUMBERS))
	{
		iDefenderSupportStrength = pDefender->getDefenderSupportValue(pAttacker);
		iAttackerSupportStrength = pAttacker->getAttackerSupportValue();
	}
	
	iAttackerStrength = pAttacker->currCombatStr(NULL, NULL) + iAttackerSupportStrength;
	iAttackerFirepower = pAttacker->currFirepower(NULL, NULL) + iAttackerSupportStrength;

	iDefenderStrength = pDefender->currCombatStr(pDefender->plot(), pAttacker) + iDefenderSupportStrength;
	iDefenderFirepower = pDefender->currFirepower(pDefender->plot(), pAttacker) + iDefenderSupportStrength;

	FAssert((iAttackerStrength + iDefenderStrength) > 0);
	FAssert((iAttackerFirepower + iDefenderFirepower) > 0);

	int iDefenderDodge = pDefender->dodgeVSOpponentProbTotal(pAttacker);
	int iDefenderPrecision = pDefender->precisionVSOpponentProbTotal(pAttacker);
	int iAttackerDodge = pAttacker->dodgeVSOpponentProbTotal(pDefender);
	int iAttackerPrecision = pAttacker->precisionVSOpponentProbTotal(pDefender);
	int iAttackerHitModifier = iAttackerPrecision - iDefenderDodge;
	int iDefenderHitModifier = iDefenderPrecision - iAttackerDodge;

	int iDefenderInitialOdds = ((GC.getCOMBAT_DIE_SIDES() * iDefenderStrength) / (iAttackerStrength + iDefenderStrength));
	int iDefenderHitOdds = std::max(5, iDefenderInitialOdds + ((iDefenderHitModifier * iDefenderInitialOdds)/100));

	int iAttackerInitialOdds = GC.getCOMBAT_DIE_SIDES() - iDefenderInitialOdds;
	int iAttackerHitOdds = std::max(5, iAttackerInitialOdds + ((iAttackerHitModifier * iAttackerInitialOdds)/100));

	iDefenderOdds = ((iDefenderHitOdds - iAttackerHitOdds)+ GC.getCOMBAT_DIE_SIDES())/2;
	iAttackerOdds = ((iAttackerHitOdds - iDefenderHitOdds)+ GC.getCOMBAT_DIE_SIDES())/2;

	if (iDefenderOdds == 0)
	{
		return 1000;
	}

	if (iAttackerOdds == 0)
	{
		return 0;
	}

	iStrengthFactor = ((iAttackerFirepower + iDefenderFirepower + 1) / 2);

	int iAttackArmorTotal = pAttacker->armorVSOpponentProbTotal(pDefender);
	int iDefendPunctureTotal = pDefender->punctureVSOpponentProbTotal(pAttacker);
	int iAttackPunctureTotal = pAttacker->punctureVSOpponentProbTotal(pDefender);
	int iDefendArmorTotal = pDefender->armorVSOpponentProbTotal(pAttacker);

	int iUnmodifiedDefenderArmor = (iDefendArmorTotal - iAttackPunctureTotal);
	int iUnmodifiedAttackerArmor = (iAttackArmorTotal - iDefendPunctureTotal);
	int iModifiedDefenderArmorZero = (iUnmodifiedDefenderArmor < 0 ? 0 : iUnmodifiedDefenderArmor);
	int iModifiedAttackerArmorZero = (iUnmodifiedAttackerArmor < 0 ? 0 : iUnmodifiedAttackerArmor);
	int iModifiedDefenderArmor = (iModifiedDefenderArmorZero > 95 ? 95 : iModifiedDefenderArmorZero);
	int iModifiedAttackerArmor = (iModifiedAttackerArmorZero > 95 ? 95 : iModifiedAttackerArmorZero);

	int iDefenderArmor = (100 - iModifiedDefenderArmor);
	int iAttackerArmor = (100 - iModifiedAttackerArmor);

	int iDefendDamageModifierTotal = pDefender->damageModifierTotal();
	int iAttackDamageModifierTotal = pAttacker->damageModifierTotal();

	int iDamageToAttackerBase = ((GC.getDefineINT("COMBAT_DAMAGE") * (iDefenderFirepower + iStrengthFactor)) / std::max(1,(iAttackerFirepower + iStrengthFactor)));
	int iDamageToDefenderBase = ((GC.getDefineINT("COMBAT_DAMAGE") * (iAttackerFirepower + iStrengthFactor)) / std::max(1,(iDefenderFirepower + iStrengthFactor)));
	int iDamageToAttackerModified = iDamageToAttackerBase + ((iDamageToAttackerBase * iDefendDamageModifierTotal)/100);
	int iDamageToDefenderModified = iDamageToDefenderBase + ((iDamageToDefenderBase * iAttackDamageModifierTotal)/100);
	int iDamageToAttackerArmor = (iDamageToAttackerModified * iAttackerArmor)/100;
	int iDamageToDefenderArmor = (iDamageToDefenderModified * iDefenderArmor)/100;
	iDamageToAttacker  = std::max(1, iDamageToAttackerArmor);
	iDamageToDefender  = std::max(1, iDamageToDefenderArmor);

	iDefenderHitLimit = pDefender->maxHitPoints() - pAttacker->combatLimit(pDefender);

	iNeededRoundsAttacker = (std::max(0, pDefender->currHitPoints() - iDefenderHitLimit) + iDamageToDefender - 1 ) / iDamageToDefender;
	iNeededRoundsDefender = (pAttacker->currHitPoints() + iDamageToAttacker - 1 ) / iDamageToAttacker;
	iMaxRounds = iNeededRoundsAttacker + iNeededRoundsDefender - 1;

	iAttackerLowFS = (pDefender->immuneToFirstStrikes()) ? 0 : pAttacker->firstStrikes();
	iAttackerHighFS = (pDefender->immuneToFirstStrikes()) ? 0 : (pAttacker->firstStrikes() + pAttacker->chanceFirstStrikes());

	iDefenderLowFS = (pAttacker->immuneToFirstStrikes()) ? 0 : pDefender->firstStrikes();
	iDefenderHighFS = (pAttacker->immuneToFirstStrikes()) ? 0 : (pDefender->firstStrikes() + pDefender->chanceFirstStrikes());

	if (GC.getLFBEnable())
	{
		return LFBgetCombatOdds(iAttackerLowFS, iAttackerHighFS, iDefenderLowFS, iDefenderHighFS, iNeededRoundsAttacker, iNeededRoundsDefender, iAttackerOdds);
	}

	for (iI = iAttackerLowFS; iI < iAttackerHighFS + 1; iI++)
	{
		for (iJ = iDefenderLowFS; iJ < iDefenderHighFS + 1; iJ++)
		{
			if (iI >= iJ)
			{
				iFirstStrikes = iI - iJ;

				for (iI3 = 0; iI3 < (iFirstStrikes + 1); iI3++)
				{
					fOddsEvent = ((float)getBinomialCoefficient(iFirstStrikes, iI3)) * pow((((float)iAttackerOdds) / GC.getCOMBAT_DIE_SIDES()), iI3) * pow((1.0f - (((float)iAttackerOdds) / GC.getCOMBAT_DIE_SIDES())), (iFirstStrikes - iI3));

					if (iI3 >= iNeededRoundsAttacker)
					{
						fOddsAfterEvent = 1;
					}
					else
					{
						fOddsAfterEvent = 0;

						for (iI4 = (iNeededRoundsAttacker - iI3); iI4 < (iMaxRounds - iI3 + 1); iI4++)
						{
							fOddsAfterEvent += ((float)getBinomialCoefficient((iMaxRounds - iI3), iI4)) * pow((((float)iAttackerOdds) / GC.getCOMBAT_DIE_SIDES()), iI4) * pow((1.0f - (((float)iAttackerOdds) / GC.getCOMBAT_DIE_SIDES())), ((iMaxRounds - iI3) - iI4));
						}
					}
					iOdds += ((int)(1000.0 * (fOddsEvent*fOddsAfterEvent + 0.0005)));
				}
			}
			else 
			{
				iFirstStrikes = iJ - iI;
				for (iI3 = 0; iI3 < (iFirstStrikes + 1); iI3++)
				{
					if (iI3 < iNeededRoundsDefender)
					{
						fOddsEvent = ((float)getBinomialCoefficient(iFirstStrikes, iI3)) * pow((((float)iDefenderOdds) / GC.getCOMBAT_DIE_SIDES()), iI3) * pow((1.0f - (((float)iDefenderOdds) / GC.getCOMBAT_DIE_SIDES())), (iFirstStrikes - iI3));
						fOddsAfterEvent = 0;
						for (iI4 = iNeededRoundsAttacker; iI4 < (iMaxRounds - iI3 + 1); iI4++)
						{
							fOddsAfterEvent += ((float)getBinomialCoefficient((iMaxRounds - iI3), iI4)) * pow((((float)iAttackerOdds) / GC.getCOMBAT_DIE_SIDES()), iI4) * pow((1.0f - (((float)iAttackerOdds) / GC.getCOMBAT_DIE_SIDES())), ((iMaxRounds - iI3) - iI4));
						}
						iOdds += ((int)(1000.0 * (fOddsEvent*fOddsAfterEvent + 0.0005)));
					}
				}				
			}
		}
	}
	iOdds /= (((pDefender->immuneToFirstStrikes()) ? 0 : pAttacker->chanceFirstStrikes()) + 1) * (((pAttacker->immuneToFirstStrikes()) ? 0 : pDefender->chanceFirstStrikes()) + 1); 
	return iOdds;
}
 
True, but if you want to choose what unit to attack with don't just use the stack - select the unit! Ideally all this would be less entangled however - there's a lot of legacy code structure here

This means the AI has a major problem because it can only suck in combat. That AI code should be much closer to the actual result.
 
@Thunderbrd: Thanks a lot. Unfortunately, I am busy this week. Maybe I will manage to take a closer look on Thursday or Saturday.

After a short look I can see that I have forgotten about First Strikes. Of course, they go at the beginning (if applicable).

I am not sure if I understand some parameters. Firepower - is this a damage multiplyer like in Civ 2? Armor sounds like damage reducer, maybe Puncture is the ability to damage Armor? Anyway, it may turn out that in fact I do not need to know these things if we may apply existing functions.

I think the main problem with the scheme I suggested is to know all pn's - the probabilities of all possible outcomes of a round. This may be the most difficult thing because it would be necessary to reverse functions calculating damage.

S.
 
WB Stachnie... I'd not remembered I had told you I'd be doing something on my end here so thanks for the bump... I'll try to look into it tonight.
 
Firepower - is this a damage multiplyer like in Civ 2?
I've never understood the note in the code but:
Code:
int CvUnit::currFirepower(const CvPlot* pPlot, const CvUnit* pAttacker) const
{
	return ((maxCombatStr(pPlot, pAttacker) + currCombatStr(pPlot, pAttacker) + 1) / 2);
}

// this nomalizes str by firepower, useful for quick odds calcs
// the effect is that a damaged unit will have an effective str lowered by firepower/maxFirepower
// doing the algebra, this means we mulitply by 1/2(1 + currHP)/maxHP = (maxHP + currHP) / (2 * maxHP)
Apparently it's the average of the current combat strength (which takes the amount of damage the unit has taken into consideration - if a unit has 80 max HP and has taken 40 HP damage (50%) then current combat strength is at 50% of max so Firepower is 75% of the max Str since it's the average of the two. I think this is to keep the unit from being completely ineffective at say 10% HP remaining but still having a diminished combat strength during the fight.

The calls to the attacker are there for determining the defender's combat modifiers since all combat modifiers compile onto the defender (a positive modifier for an attacker = a negative modifier for a defender in all reality.) (Modifier in this case means % combat modifier.) There's also some kind of special math that happens if the total combat modifier gets down to or approaches -100% so that the defender is not considered to have negative strength ever. I'd have to research further to determine how the numbers are adjusted there. In short, the thing to take from this is that in reality in the code the attacker is never modified by combat modifiers... only the defender.


Armor sounds like damage reducer, maybe Puncture is the ability to damage Armor?
Puncture is the ability to ignore armor. This hasn't been used much yet and I believe it will be changed to 'armor vs damage type' and puncture by total damage type and a separately compiled amount of damage per type coalescing to a total damage with each type striking each heal-as combat class HP amount independently on the defending unit. I've still yet to sort that fully out so generalized armor/puncture remains out of use for now.

Anyway, it may turn out that in fact I do not need to know these things if we may apply existing functions.

I think the main problem with the scheme I suggested is to know all pn's - the probabilities of all possible outcomes of a round. This may be the most difficult thing because it would be necessary to reverse functions calculating damage.

S.
Well... that's the thing. We need to figure out in each application of odds what approach we need to use... an approximation tool that is at all accurate for the player and AI to see and use for determining willingness to attack would be useful in most capacities and in the actual combat, what's affecting real odds to determine base to-hits and damage amounts will be the important part.
 
Apparently it's the average of the current combat strength (which takes the amount of damage the unit has taken into consideration - if a unit has 80 max HP and has taken 40 HP damage (50%) then current combat strength is at 50% of max so Firepower is 75% of the max Str since it's the average of the two. I think this is to keep the unit from being completely ineffective at say 10% HP remaining but still having a diminished combat strength during the fight.

O.K., I understand. It means that combat effectiveness during the fight may go from 1 (full health) to a bit more than 0.5 (nearly dead).

Puncture is the ability to ignore armor. This hasn't been used much yet and I believe it will be changed to 'armor vs damage type' and puncture by total damage type and a separately compiled amount of damage per type coalescing to a total damage with each type striking each heal-as combat class HP amount independently on the defending unit. I've still yet to sort that fully out so generalized armor/puncture remains out of use for now.

Very well! I was afraid it gradually reduces the armor of the defender, like a fight in Morrowind or Oblivion (a hero or NPC with poor armor/weapon may end with a completely damaged, useless one that needs to be repaired after the combat). It would be more realistic but it would make all calculations much more difficult, because D would behave in a way difficult to predict (depending on the number of wins or losses in previous rounds).

So I think the general scheme I suggested may be applied. I guess an unit with Enraged ability increases its combat effectiveness each round (maybe for some limited number of rounds) etc. but as long as effective A and D do not depend on the number of wins and losses (only on the number of the round, A&D and HP&HPmax of the attacker and defender), this is not a big problem.

S.
 
Additionally, another factor I haven't tried to work in yet is power strikes, which increase the Damage % modifier for the first # of strikes - this may need to be a bit reworked though to match to the particular weapon in use... I can mention more on that once it gets sorted out.
 
So I will summarize what I have understood up to now:

- effective A and D used to determine the winner of each round depends on some factors (e.g. Enraged) but it may be calculated for each round and it does not depend on any other factor like number of wins and losses from the start of the fight

- the damage depends on much more factors (e.g. on abilities like Enraged, Puncture, Armor, Power Strikes etc.), but in a different way depending on the winner (e.g. both Attacker and Defender may have some Puncture).

The scheme I suggested may be called a Bayesian one, because it uses the tree of conditional probabilities. To make it work we need to:

- find the probabilities of win, loss and dodge each round (including First Strikes at the beginning)

- in case of win or loss, find the range of possible damage and the probabilities of each possible values (I guess it may be the most difficult part of the task)

- advance to the next round and determine if the fight has ended (because someone lost or flied), we will need to put some reasonable limits to probabilities, because otherwise the loop would be almost infinite (it could last until reaching limits depending on the float type).

S.
 
- effective A and D used to determine the winner of each round depends on some factors (e.g. Enraged) but it may be calculated for each round and it does not depend on any other factor like number of wins and losses from the start of the fight
True except that there CAN be two 'winners' in the round - both units can land a hit (iirc). And obviously, the strength depletion from a hit will have an impact on odds the next round.
 
Top Bottom