For a moment I tried making the unharmed odds conditioned on the unit surviving, but I found the results a bit more confusing and would prefer to avoid it unless I hear a good reason otherwise.
Do you mean that you multiplied the probability of escaping unharmed by the probability of winning? If so, I agree that it doesn't make sense since it is displayed on a line by itself, separate from the outcome. If not, can you explain what you mean by this?
So if and when the BUG Mod adds this, then I will probably be most interested in enabling:
Victory + XP
...
Maybe the Attacker Combat Limit could use a new name.
He's talking about the conditional chance that a unit wins without losing hitpoints assuming the condition that it wins is true.
Do you mean stripping away all the other stuff? I'm already thinking about how many options this should have. Should I make each piece an option (overkill) or have perhaps a Detail setting: Low, Medium, High. The latter will give you less control over individual elements, but BUG is approaching the point of option overload for the average user (probably past it, actually).
Won't this simply be the defender's E[XP] shown on the withdraw outcome? Or does ACL mean something other than the minimum HP to which a bombarding unit can damage a defender?
Oh boy, think I'll go take a nap.
I'm likely too tired to completely understand these conditional chances right now, but the E[HP] | Victory seems surprisingly high to me. Let's look at PoM's last screenshot, the example on the right which is probably a Cav attacking a War Elephant (Victory Odds 75.11%). E[HP] = 53.0 HP . So on average a winning Cav will suffer < 3 hits from the defending Elephant. The Cav's odds to win 1 round during combat are merely 55.6% -- my gut feeling would expect something like 3-4 hits on average. Is this felt discrepancy the reason why you don't find it that interesting anymore Roland?
Ok. Time for me to go take a nap ...
That looks awesome. Didn't you want to keep the overall E[HP] for both units on a line by itself? Even as an option?
I would guess they do.Do any of the lines wrap using the larger BM font? If you don't use it, I can test it here as I do.
That's a good suggestion - I'll see how it looks.For the HP/hit line, how about cutting some of the text down so the numbers stick out?
20,20 HP/hit x 5/5 hits @ 50.0%
You can get the real multiplication symbol using its Unicode XML entity: "×" without the quotes.
Consider a battle where the only outcomes are victory or defeat and the odds are 50% each way. In that battle, also assume that 25% of the time the attacker will walk away unharmed. I could either display the flat odds the attacker will walk away unharmed as 25%, or I could display the odds that the attacker was unharmed assuming he won the battle, which would be 50% and that is worked out by P(unharmed | survived) = P(unharmed) / P(survived). This involved division - not multiplication.Do you mean that you multiplied the probability of escaping unharmed by the probability of winning? If so, I agree that it doesn't make sense since it is displayed on a line by itself, separate from the outcome. If not, can you explain what you mean by this?
Sure. Here's one.Do you have a screenshot showing a unit that might withdraw? Just for completeness.
It looks like you and EmperorFool discussed this to see that it's not as interesting a value anymore, since it can be gained from E[HP | withdraw] displayed beside the withdraw odds. I didn't leave a very intuitive name on it before because honestly it just was a flat-out debug-like output line I'd left in and I felt with version 0.1, it was useful information alongside the rest of it. But yeah, not so useful now...This is really a great mod component. A fabulous job PieceOfMind!
As a mathematician, I can really appreciate all of the values and I know about their significance in the combat algorithm. But I guess that when I'm playing the game, then I won't be using all of them. So if and when the BUG Mod adds this, then I will probably be most interested in enabling:
Victory + XP
Retreat + XP
Witdraw + XP
Attacker Unharmed
Attacker Combat Limit
Maybe the Attacker Combat Limit could use a new name. I know that it's named this way in the XML-files, but most people won't know that. In my opinion it's better to talk about a more intuitive name like Attacker damage limit or Attacker maximum damage.
It's great that PieceOfMind enabled all of this information, but in a typical game I won't be needing it all. It would be great if I could enable some of these in an atypical battle where I do want to see all of the details but in a typical battle I would just be interested in the ones that I mentioned in my previous post. So in a typical battle, the other values will just distract me from the ones that I do want to see.
My preference for the BUG MOD so that all users might be satisfied:
One, two or three predefined versions where the mod designers decide what the user will see, one custom setting that can be defined in an xml file where the advanced user can customize exactly what he will see.
Do you and PieceOfMind think that is possible?
I'm likely too tired to completely understand these conditional chances right now, but the E[HP] | Victory seems surprisingly high to me. Let's look at PoM's last screenshot, the example on the right which is probably a Cav attacking a War Elephant (Victory Odds 75.11%). E[HP] = 53.0 HP . So on average a winning Cav will suffer < 3 hits from the defending Elephant. The Cav's odds to win 1 round during combat are merely 55.6% -- my gut feeling would expect something like 3-4 hits on average. Is this felt discrepancy the reason why you don't find it that interesting anymore Roland?
Ok. Time for me to go take a nap ...
Anymore? I didn't put forth a different position earlier, did I?
Anyhow, I just don't think that I'll be using the average hitpoints left very often in gameplay. I do think that I will use the chance to lose no hitpoints as that means that my unit doesn't need to heal. That last information is very useful when I am moving my SOD from enemy city to enemy city. It means faster conquest with less units needing to stop to heal if I pick the ones that have a high chance to lose no hitpoints (likely the drill promoted units).
The average hitpoints left isn't that interesting as it doesn't tell you a lot about the time you need to heal (as many might expect it does). That has to do with the distribution of damage that leads to this average hitpoints left. This distribution is typically so variable (high variation for the mathematicians) that the average isn't very interesting for gameplay. It's hard to estimate by looking at this average if you'll likely be healing for 1, 2, 3 or more turns because of this variability.
But maybe other players will use the average hitpoints left in their combat tactics, so it's still a useful piece of information. Just not for me.
Edit: By the way, I would be interested in something like this:
Victory: 75% (3XP)
0 turns to heal 20%
1 turn to heal 27%
2 turns to heal 37%
3 turns to heal 16%
Defeat 25%
etc.
But it would of course depend on the healers present on the tile and other factors, so that information should be extracted from the game when such a calculation is made. (I don't know how hard that would be). I guess that with the information of the healing speed, PieceOfMind could easily calculate the turns to heal with the values from his present calculations. His own 'Unit Healing' strategy article would be useful for this part.
Ok I have a number of posts to reply to so bear with me for a minute.
That's a good suggestion - I'll see how it looks.
On another note, it would be nice if selecting a medic told you explicitly how much HP you can expect to heal each turn in the various types of territory or with a hospital etc.
By the way, an interesting thing I discovered by accident when making the changes in v0.2, which I'd sort of ignored beforehand, was that when the attacker is killed or retreats the expected defender hitpoints are the same.
int iDetail = GC.getDefineINT("COMBAT_ODDS_DETAIL");
// AdvancedCombatOdds.h
#pragma once
#ifndef ADVANCED_COMBAT_ODDS_H
#define ADVANCED_COMBAT_ODDS_H
#define ACO_DETAIL_LOW 0
#define ACO_DETAIL_MEDIUM 1
#define ACO_DETAIL_HIGH 2
#endif
// only show E[HP] if at medium or higher
if (iDetail >= ACO_DETAIL_MEDIUM)
{
...
}
Hmm, looks better again. The only thing that concerns me a bit is the use of slashes to separate the values. I've prefered using commas to this point because a "/" could cause one to think it's a fraction of some sort. I often think of the phrase "out of" when I see a "/".Maybe even
20/20 HP x 5/5 hits @ 50.0%
I removed "/hit" as it should be clear by the multiplication.
I like that idea. Perhaps if there are no retreat odds or withdraw odds it should be omitted though? Or would omitting it only when it is not needed make the cases where it is needed look inconsistent because you are getting other info?Looking at the various screenshots, I wonder if the first line should summarize the probabilities of each unit surviving. For the attacker: the sum of victory, retreat, and withdraw. For the defender: the sum of retreat, withdraw and defeat.
Survival: 63.2% / 51.8%
For the details of each outcome, you'd look below. I suggest this only because it seems already you need to look below sometimes, depending on the information you seek. To me, the thing I need to know most often is the chance that each unit survives the combat. But I haven't played with it yet (did you post the new code?) to get a good feel.
/*************************************************************************************************/
/** ADVANCED COMBAT ODDS 13/02/09 PieceOfMind */
/** BEGIN v0.2 */
/*************************************************************************************************/
//BOOL ACO_debug = false;
BOOL ACO_debug = false;
//Change this to true when you need to spot errors, particular in the expected hit points calculations
ACO_debug = (GC.getDefineINT("ADVANCED_COMBAT_ODDS_DEBUG")==1); // 1 for on, 0 for off
/** Many thanks to DanF3771 for some of these calculations! **/
int iAttackerStrength = pAttacker->currCombatStr(NULL, NULL);
int iAttackerFirepower = pAttacker->currFirepower(NULL, NULL);
int iDefenderStrength = pDefender->currCombatStr(pPlot, pAttacker);
int iDefenderFirepower = pDefender->currFirepower(pPlot, pAttacker);
FAssert((iAttackerStrength + iDefenderStrength)*(iAttackerFirepower + iDefenderFirepower) > 0);
// int iAttackerOdds = ((GC.getDefineINT("COMBAT_DIE_SIDES") * iAttackerStrength) / (iAttackerStrength + iDefenderStrength));
int iDefenderOdds = ((GC.getDefineINT("COMBAT_DIE_SIDES") * iDefenderStrength) / (iAttackerStrength + iDefenderStrength));
int iAttackerOdds = GC.getDefineINT("COMBAT_DIE_SIDES") - iDefenderOdds;
int iStrengthFactor = ((iAttackerFirepower + iDefenderFirepower + 1) / 2);
int iDamageToAttacker = std::max(1, ((GC.getDefineINT("COMBAT_DAMAGE") * (iDefenderFirepower + iStrengthFactor)) / (iAttackerFirepower + iStrengthFactor)));
int iDamageToDefender = std::max(1, ((GC.getDefineINT("COMBAT_DAMAGE") * (iAttackerFirepower + iStrengthFactor)) / (iDefenderFirepower + iStrengthFactor)));
int iExperience;
if (pAttacker->combatLimit() < 100)
{
iExperience = GC.getDefineINT("EXPERIENCE_FROM_WITHDRAWL");
}
else
{
iExperience = (pDefender->attackXPValue() * iDefenderStrength) / iAttackerStrength;
iExperience = range(iExperience, GC.getDefineINT("MIN_EXPERIENCE_PER_COMBAT"), GC.getDefineINT("MAX_EXPERIENCE_PER_COMBAT"));
}
int iDefExperienceKill;
iDefExperienceKill = (pAttacker->defenseXPValue() * iAttackerStrength) / iDefenderStrength;
iDefExperienceKill = range(iDefExperienceKill, GC.getDefineINT("MIN_EXPERIENCE_PER_COMBAT"), GC.getDefineINT("MAX_EXPERIENCE_PER_COMBAT"));
int iNeededRoundsAttacker = (pDefender->currHitPoints() - pDefender->maxHitPoints() + pAttacker->combatLimit() - (((pAttacker->combatLimit())==GC.getMAX_HIT_POINTS())?1:0))/iDamageToDefender + 1;
//The extra term introduced here was to account for the incorrect way it treated units that had combatLimits.
//A catapult that deals 25HP per round, and has a combatLimit of 75HP must deal four successful hits before it kills the warrior -not 3. This is proved in the way CvUnit::resolvecombat works
// The old formula (with just a plain -1 instead of a conditional -1 or 0) was incorrectly saying three.
int iNeededRoundsDefender = (pAttacker->currHitPoints() - 1)/iDamageToAttacker + 1;
//szTempBuffer.Format(L"iNeededRoundsAttacker = %d\niNeededRoundsDefender = %d",
// iNeededRoundsAttacker, iNeededRoundsDefender);
//szString.append(NEWLINE);
//szString.append(szTempBuffer.GetCString());
//szTempBuffer.Format(L"for %d XP -- " SETCOLR L"%d*%dHP" ENDCOLR L" / " SETCOLR L"%d*%dHP" ENDCOLR L" Odds: %.1f%% %d",
// iExperience, TEXT_COLOR("COLOR_POSITIVE_TEXT"), iNeededRoundsAttacker, iDamageToDefender, TEXT_COLOR("COLOR_NEGATIVE_TEXT"), iNeededRoundsDefender, iDamageToAttacker, float(iAttackerOdds)/10.0f, iDefenderStrength);
//szTempBuffer.Format(L"for %d XP -- " SETCOLR L"%d*%dHP" ENDCOLR L" / " SETCOLR L"%d*%dHP" ENDCOLR L" Odds: %.1f%%",
// iExperience, TEXT_COLOR("COLOR_POSITIVE_TEXT"), iNeededRoundsAttacker, iDamageToDefender, TEXT_COLOR("COLOR_NEGATIVE_TEXT"), iNeededRoundsDefender, iDamageToAttacker, float(iAttackerOdds)*100.0f / float(GC.getDefineINT("COMBAT_DIE_SIDES")));
//szString.append(NEWLINE);
// szString.append(szTempBuffer.GetCString());
//szTempBuffer.Format(L"iAttackerStrength = %d\niDefenderStrength = %d\n\niAttackerFirePower = %d\niDefenderFirePower = %d\n",
// iAttackerStrength, iDefenderStrength, iAttackerFirepower, iDefenderFirepower);
//szString.append(NEWLINE);
//szString.append(szTempBuffer.GetCString());
//szTempBuffer.Format(L"iStrengthFactor = %d\n = (iAFP+iDFP+1)/2\n\niDamageToAttacker = %d\niDamageToDefender = %d\n",
// iStrengthFactor, iDamageToAttacker, iDamageToDefender);
//szString.append(NEWLINE);
//szString.append(szTempBuffer.GetCString());
//szTempBuffer.Format(L"iDefenderOdds = %d\n = (DIE_SIDES (%d) * %d) / (%d + %d)\niAttackerOdds = %d",
// iDefenderOdds, GC.getDefineINT("COMBAT_DIE_SIDES"),iDefenderStrength, iAttackerStrength, iDefenderStrength, iAttackerOdds);
//szString.append(NEWLINE);
//szString.append(szTempBuffer.GetCString());
//szTempBuffer.Format(L"Attacker combatLimit = %d", // \nAttacker getDamage() = %d",
// pAttacker->combatLimit());//,pAttacker->getDamage());
//szString.append(NEWLINE);
//szString.append(szTempBuffer.GetCString());
int iDefenderHitLimit = pDefender->maxHitPoints() - pAttacker->combatLimit();
//szTempBuffer.Format(L"iDefenderHitLimit = %d",iDefenderHitLimit);
//szString.append(NEWLINE);
//szString.append(szTempBuffer.GetCString());
//NOW WE CALCULATE SOME INTERESTING STUFF :)
szTempBuffer.Format(L"-----Advanced Combat Odds v0.2-----");
szString.append(NEWLINE);
szString.append(szTempBuffer.GetCString());
//Alternatively, I could have calculated the defender odds as 1 minus the sum of the earlier three. I'm more likely to spot errors this way though.
//szTempBuffer.Format(SETCOLR L"%dHP per hit (%d hits required)" ENDCOLR L"\n" SETCOLR L"%dHP per hit (%d hits required)" ENDCOLR,
// TEXT_COLOR("COLOR_POSITIVE_TEXT"), iDamageToDefender, iNeededRoundsAttacker, TEXT_COLOR("COLOR_NEGATIVE_TEXT"), iDamageToAttacker, iNeededRoundsDefender);
// szString.append(NEWLINE);
// szString.append(szTempBuffer.GetCString());
//const int MAX_SIZE_ARRAY = 16;//This value 16 is hardcoded in for now
//It is unlikely that
//Calculate Expected Hitpoints for both attacker and defender and display them side by side.
//eg. (100->83:80->5)
float E_HP_Att = 0.0f;//expected damage dealt to attacker
float E_HP_Def = 0.0f;
float E_HP_Att_Withdraw;
float E_HP_Att_Victory;
int E_HP_Att_Retreat = (pAttacker->currHitPoints()) - (iNeededRoundsDefender-1)*iDamageToAttacker;//this one is predetermined easily
float E_HP_Def_Withdraw;
float E_HP_Def_Defeat; // if attacker dies
//Note E_HP_Def is the same for if the attacker withdraws or dies
//float prob_attack[MAX_SIZE_ARRAY];
//float prob_defend[MAX_SIZE_ARRAY];
//I was planning to use these two arrays for other calculations, but don't need them for the moment.
float AttackerUnharmed;
float DefenderUnharmed;
AttackerUnharmed = getCombatOddsSpecific(pAttacker,pDefender,0,iNeededRoundsAttacker);
DefenderUnharmed = getCombatOddsSpecific(pAttacker,pDefender,iNeededRoundsDefender,0);
DefenderUnharmed += getCombatOddsSpecific(pAttacker,pDefender,iNeededRoundsDefender-1,0);
//the following code was incorrect
/**
if(iNeededRoundsAttacker-1 == 0) // this is due to the abnormal combatLimit situations
{
for (int n_A = 0; n_A < iNeededRoundsDefender; n_A++)
{
DefenderUnharmed += getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker);//this is the defender at the combatLimit
szTempBuffer.Format(L"DEBUG DefenderUnharmed = %.2f at this point",DefenderUnharmed);
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
}
}
**/
if(ACO_debug) {
szTempBuffer.Format(L"E[HP ATTACKER]");
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
}
// already covers both possibility of defender not being killed AND being killed
for (int n_A = 0; n_A < iNeededRoundsDefender; n_A++)
{
//prob_attack[n_A] = getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker);
E_HP_Att += ( (pAttacker->currHitPoints()) - n_A*iDamageToAttacker) * getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker);
if(ACO_debug)
{
szTempBuffer.Format(L"+%d * %0.2f%% (Def %d) (%d:%d)",
((pAttacker->currHitPoints()) - n_A*iDamageToAttacker),100.0f*getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker),iDefenderHitLimit,n_A,iNeededRoundsAttacker);
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
}
}
E_HP_Att_Victory = E_HP_Att;//NOT YET NORMALISED
E_HP_Att_Withdraw = E_HP_Att;//NOT YET NORMALIZED
if((pAttacker->withdrawalProbability()) > 0)
{ // if withdraw odds involved
if(ACO_debug)
{
szTempBuffer.Format(L"Attacker retreat odds");
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
}
for (int n_D = 0; n_D < iNeededRoundsAttacker; n_D++)
{
E_HP_Att += ( (pAttacker->currHitPoints()) - (iNeededRoundsDefender-1)*iDamageToAttacker) * getCombatOddsSpecific(pAttacker,pDefender,iNeededRoundsDefender-1,n_D);
if(ACO_debug)
{
szTempBuffer.Format(L"+%d * %0.2f%% (Def %d) (%d:%d)",
( (pAttacker->currHitPoints()) - (iNeededRoundsDefender-1)*iDamageToAttacker),100.0f*getCombatOddsSpecific(pAttacker,pDefender,iNeededRoundsDefender-1,n_D),100-n_D*iDamageToDefender,iNeededRoundsDefender-1,n_D);
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
}
//prob_attack[iNeededRoundsDefender-1] += getCombatOddsSpecific(pAttacker,pDefender,iNeededRoundsDefender-1,n_D);
}
}
// finished with the attacker HP I think.
if(ACO_debug)
{
szTempBuffer.Format(L"E[HP DEFENDER]\nOdds that attacker dies or retreats");
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
}
for (int n_D = 0; n_D < iNeededRoundsAttacker; n_D++)
{
//prob_defend[n_D] = getCombatOddsSpecific(pAttacker,pDefender,iNeededRoundsDefender,n_D);//attacker dies
//prob_defend[n_D] += getCombatOddsSpecific(pAttacker,pDefender,iNeededRoundsDefender-1,n_D);//attacker retreats
E_HP_Def += ( (pDefender->currHitPoints()) - n_D*iDamageToDefender) * (getCombatOddsSpecific(pAttacker,pDefender,iNeededRoundsDefender,n_D)+getCombatOddsSpecific(pAttacker,pDefender,iNeededRoundsDefender-1,n_D));
if(ACO_debug)
{
szTempBuffer.Format(L"+%d * %0.2f%% (Att 0 or %d) (%d:%d)",
( (pDefender->currHitPoints()) - n_D*iDamageToDefender),100.0f*(getCombatOddsSpecific(pAttacker,pDefender,iNeededRoundsDefender,n_D)+getCombatOddsSpecific(pAttacker,pDefender,iNeededRoundsDefender-1,n_D)),100-(iNeededRoundsDefender-1)*iDamageToAttacker,iNeededRoundsDefender,n_D);
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
}
}
E_HP_Def_Defeat = E_HP_Def;
E_HP_Def_Withdraw = 0.0f;
if (pAttacker->combatLimit() < (pDefender->maxHitPoints() ))//if attacker has a combatLimit (eg. catapult)
{
if (pAttacker->combatLimit() == iDamageToDefender*(iNeededRoundsAttacker-1) )
{//Then we have an odd situation because the last successful hit by an attacker will do 0 damage, and doing either iNeededRoundsAttacker or iNeededRoundsAttacker-1 will cause the same damage
if(ACO_debug)
{
szTempBuffer.Format(L"Odds that attacker withdraws at combatLimit (abnormal)");
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
}
for (int n_A = 0; n_A < iNeededRoundsDefender; n_A++)
{
//prob_defend[iNeededRoundsAttacker-1] += getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker);//this is the defender at the combatLimit
E_HP_Def += (float)iDefenderHitLimit * getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker);
//should be the same as
//E_HP_Def += ( (pDefender->currHitPoints()) - (iNeededRoundsAttacker-1)*iDamageToDefender) * getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker);
E_HP_Def_Withdraw += (float)iDefenderHitLimit * getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker);
if(ACO_debug)
{
szTempBuffer.Format(L"+%d * %0.2f%% (Att %d) (%d:%d)",
iDefenderHitLimit,100.0f*getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker),100-n_A*iDamageToAttacker,n_A,iNeededRoundsAttacker);
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
}
}
}
else // normal situation
{
if(ACO_debug)
{
szTempBuffer.Format(L"Odds that attacker withdraws at combatLimit (normal)",pAttacker->combatLimit());
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
}
//prob_defend[iNeededRoundsAttacker] = 0.0f;
for (int n_A = 0; n_A < iNeededRoundsDefender; n_A++)
{
//prob_defend[iNeededRoundsAttacker] += getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker);//this is the defender at the combatLimit
E_HP_Def += (float)iDefenderHitLimit * getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker);
E_HP_Def_Withdraw += (float)iDefenderHitLimit * getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker);
if(ACO_debug)
{
szTempBuffer.Format(L"+%d * %0.2f%% (Att %d) (%d:%d)",
iDefenderHitLimit,100.0f*getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker),100-n_A*iDamageToAttacker,n_A,iNeededRoundsAttacker);
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
}
}//for
}//else
}
float AttackerKillOdds = 0.0f;
float PullOutOdds = 0.0f;
float RetreatOdds = 0.0f;
float DefenderKillOdds = 0.0f;
// General odds with XP values included, and coloured
if(pAttacker->combatLimit() == (pDefender->maxHitPoints() )) //ie. we can kill the defender... I hope this is the most general form
{
//float AttackerKillOdds = 0.0f;
for (int n_A = 0; n_A < iNeededRoundsDefender; n_A++) {
AttackerKillOdds += getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker);
}//for
szTempBuffer.Format(SETCOLR L"Victory " ENDCOLR L"%0.2f%% (" SETCOLR L"%dXP" ENDCOLR L") (" SETCOLR L"%0.1fHP" ENDCOLR L")",
TEXT_COLOR("COLOR_POSITIVE_TEXT"),100.0f*AttackerKillOdds,TEXT_COLOR("COLOR_POSITIVE_TEXT"),iExperience,TEXT_COLOR("COLOR_POSITIVE_TEXT"), E_HP_Att_Victory/AttackerKillOdds);
}
else
{ // else we cannot kill the defender (eg. catapults attacking)
//float PullOutOdds = 0.0f;
for (int n_A = 0; n_A < iNeededRoundsDefender; n_A++)
{
PullOutOdds += getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker);
//if (pAttacker->combatLimit() == iDamageToDefender*(iNeededRoundsAttacker-1) ) {
// PullOutOdds += getCombatOddsSpecific(pAttacker,pDefender,n_A,iNeededRoundsAttacker);
// }
}//for
szTempBuffer.Format(SETCOLR L"Withdraw " ENDCOLR L"%0.2f%% (" SETCOLR L"%dXP" ENDCOLR L") (" SETCOLR L"%0.1fHP" ENDCOLR L"," SETCOLR L"%dHP" ENDCOLR L")",
TEXT_COLOR("COLOR_POSITIVE_TEXT"),100.0f*PullOutOdds,TEXT_COLOR("COLOR_POSITIVE_TEXT"),GC.getDefineINT("EXPERIENCE_FROM_WITHDRAWL"),
TEXT_COLOR("COLOR_POSITIVE_TEXT"), E_HP_Att_Withdraw/PullOutOdds, TEXT_COLOR("COLOR_NEGATIVE_TEXT"), iDefenderHitLimit);
}
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
if ((pAttacker->withdrawalProbability()) > 0)
{
for (int n_D = 0; n_D < iNeededRoundsAttacker; n_D++) {
RetreatOdds += getCombatOddsSpecific(pAttacker,pDefender,iNeededRoundsDefender-1,n_D);
}//for
}
for (int n_D = 0; n_D < iNeededRoundsAttacker; n_D++)
{
DefenderKillOdds += getCombatOddsSpecific(pAttacker,pDefender,iNeededRoundsDefender,n_D);
}//for
//DefenderKillOdds = 1.0f - (AttackerKillOdds + RetreatOdds + PullOutOdds);//this gives slight negative numbers sometimes, I think
if ((pAttacker->withdrawalProbability()) > 0)
{
szTempBuffer.Format(SETCOLR L"Retreat " ENDCOLR L"%0.2f%% (" SETCOLR L"%dXP" ENDCOLR L") (" SETCOLR L"%dHP" ENDCOLR L"," SETCOLR L"%.1fHP" ENDCOLR L")",
TEXT_COLOR("COLOR_POSITIVE_TEXT"),100.0f*RetreatOdds,TEXT_COLOR("COLOR_POSITIVE_TEXT"),GC.getDefineINT("EXPERIENCE_FROM_WITHDRAWL"),
TEXT_COLOR("COLOR_POSITIVE_TEXT"),E_HP_Att_Retreat,TEXT_COLOR("COLOR_NEGATIVE_TEXT"),E_HP_Def_Defeat/(RetreatOdds+DefenderKillOdds));
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
}
szTempBuffer.Format(SETCOLR L"Defeat " ENDCOLR L"%0.2f%% (" SETCOLR L"%dXP" ENDCOLR L") (" SETCOLR L"%0.1fHP" ENDCOLR L")",
TEXT_COLOR("COLOR_NEGATIVE_TEXT"),100.0f*DefenderKillOdds,TEXT_COLOR("COLOR_NEGATIVE_TEXT"),iDefExperienceKill,TEXT_COLOR("COLOR_NEGATIVE_TEXT"),E_HP_Def_Defeat/(RetreatOdds+DefenderKillOdds));
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
szTempBuffer.Format(SETCOLR L"%d" ENDCOLR L"," SETCOLR L"%d" ENDCOLR L" HP per hit (" SETCOLR L"%d" ENDCOLR L"," SETCOLR L"%d" ENDCOLR L" hits). Odds: %.1f%%",
TEXT_COLOR("COLOR_POSITIVE_TEXT"), iDamageToDefender, TEXT_COLOR("COLOR_NEGATIVE_TEXT"), iDamageToAttacker,TEXT_COLOR("COLOR_POSITIVE_TEXT"),iNeededRoundsAttacker,TEXT_COLOR("COLOR_NEGATIVE_TEXT"), iNeededRoundsDefender,float(iAttackerOdds)*100.0f / float(GC.getDefineINT("COMBAT_DIE_SIDES")));
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
//szTempBuffer.Format(L"E[HP] = (" SETCOLR L"%d->%0.2fHP" ENDCOLR L"," SETCOLR L"%d->%0.1fHP" ENDCOLR ,
// TEXT_COLOR("COLOR_POSITIVE_TEXT"),(pAttacker->currHitPoints()),E_HP_Att,TEXT_COLOR("COLOR_NEGATIVE_TEXT"),(pDefender->currHitPoints()),E_HP_Def);
//szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
szTempBuffer.Format(SETCOLR L"Attacker Unharmed " ENDCOLR L"%0.2f%%\n" SETCOLR L"Defender Unharmed " ENDCOLR L"%0.2f%%",
TEXT_COLOR("COLOR_POSITIVE_TEXT"),100.0f*AttackerUnharmed,TEXT_COLOR("COLOR_NEGATIVE_TEXT"),100.0f*DefenderUnharmed);
szString.append(NEWLINE);szString.append(szTempBuffer.GetCString());
//if(pAttacker->combatLimit() < GC.getMAX_HIT_POINTS())
// {
// szTempBuffer.Format(L"Attacker combatLimit = %d",pAttacker->combatLimit());
// szString.append(NEWLINE);
// szString.append(szTempBuffer.GetCString());
// }//if
szTempBuffer.Format(L"--------------------------------------------");
szString.append(NEWLINE);
szString.append(szTempBuffer.GetCString());
/*************************************************************************************************/
/** ADVANCED COMBAT ODDS 13/02/09 PieceOfMind */
/** END */
/*************************************************************************************************/
My preference would be to leave it there for the moment.That makes perfect sense. Would it be too unclear to remove the defender's E[HP] from the retreat outcome? We know the Defeat outcome's E[HP] applies, but will other people figure that out easily?
If you want to make the level of detail configurable now, it's pretty simple. At the top of your changes, add this to grab the value from the XML:
Code:int iDetail = GC.getDefineINT("COMBAT_ODDS_DETAIL");
You might want to define constants for the different levels in a header file, or just use 0, 1, 2, ...
Code:// AdvancedCombatOdds.h #pragma once #ifndef ADVANCED_COMBAT_ODDS_H #define ADVANCED_COMBAT_ODDS_H #define ACO_DETAIL_LOW 0 #define ACO_DETAIL_MEDIUM 1 #define ACO_DETAIL_HIGH 2 #endif
Then it's just a matter of checking the detail before adding some text block.
Code:// only show E[HP] if at medium or higher if (iDetail >= ACO_DETAIL_MEDIUM) { ... }
To merge this with BUG, I'll only need to change the top line that grabs the current setting.
I've prefered using commas to this point because a "/" could cause one to think it's a fraction of some sort.
Perhaps if there are no retreat odds or withdraw odds it should be omitted though?
Actually up until now I'd also been avoiding colouring the actual percentages when possible because white text is a bit easier to read usually.
You might notice in my todo list was an idea that I could somehow increase the opacity of the display so the numbers are a bit easier to read when there is a lot of background detail behind them.
That header file you suggested is a new file right? I'll have to create AdvancedCombatOdds.h and just put those few lines in?
With regard to the options, did we want to allow greater customisability with the highest detail setting? If so, what would be the best way to go about it?
And since the inclusion of various statistics is included in every higher level of detail, I am thinking it would be better to use an integer to set the detail . . .
That's exactly what those #define statements do, only it makes the code more readable (words vs. numbers) and allows you to change the numbers without changing all the code--just the #defines in the header. The compiler only sees the numbers in the end, and it lines up with how BUG handles text dropdown options: their value is an integer from 0 to N-1, the index of the item selected.
You've convinced me about the odds of being unharmed. The only odd thing I see is splitting the odds of each HP for attacker and defender across two levels. All the other values that apply to both attacker and defender are shown in the same level, but I'm not particular about this one. I can see the value of the attacker HP odds details over the defender's.
If you want to get nuts, I did recently add sliders to BUG's options screen. We could have a slider for level of detail from 0 to 100! Okay, 10 to keep it reasonable.
bool isAcoOption(int iDetail, int iMinDetail, const char* szOption)
{
if (iDetail == ACO_DETAIL_CUSTOM)
{
return GC.getDefineBOOL(szOption);
// and for BUG
// return getBugOptionBOOL(szOption);
}
return iDetail >= iMinDetail;
}
...
// display odds that attacker survives unharmed
if (isAcoOption(iDetail, ACO_DETAIL_MEDIUM, "ACO_ODDS_UNHARMED_ATT"))
{
...
}