Почти полностью разобранная функция army::get_unit_combat_value(). Осталось только узнать сакральный смысл аргументов ArgVar_0 и ArgVar_1:
- Код: Выделить всё
// ArgVar_0 - AI пенальти к атаке
// ArgVar_1 - AI пенальти к защите
// ArgVar_2 - стрелковая атака или нет
//
// ArgVar - это аргумент, который используется как переменная. С такими названиями меньше путаницы с локальными переменными
double __thiscall army::get_unit_combat_value(const army *this, int ArgVar_0, int ArgVar_1, int ArgVar_2, int a5)
{
char isShooter; // bl
int AdjustedAttack; // eax
int AdjustedDefense; // eax
double ShieldMultiplier; // st7
int Side; // eax
hero *Hero; // eax
ECreatureType Type; // eax
hero *TempHero; // eax
int Side_0; // eax
int SpellDurationHypnotize; // edx
int Side_1; // ecx
int Side_2; // ecx
int Side_3; // ecx
int SpellDurationBless; // edx
int BaseMinDamage; // eax
int BaseMaxDamage; // ecx
int *damage; // eax
signed int CurseDamagePenalty; // edx
bool isDamageLessThan1; // cc
FCreatureFlags *Flags; // eax
double k; // st7
int StackCurrentHealth; // edi
int TotalHealth; // edx
army *FirstStack; // ecx
int Side_4; // esi
int StacksNum; // esi
int Side_5; // ecx
FCreatureFlags *__shifted(army,0x84) pFlags; // ecx
int StackCurrentHealth_0; // eax
double TotalHealth_0; // st7
double DefenseMod; // [esp+14h] [ebp-20h]
double BaseAvgDamage; // [esp+1Ch] [ebp-18h]
double Multiplier; // [esp+24h] [ebp-10h]
double Damage; // [esp+24h] [ebp-10h]
double EffectiveFightValue; // [esp+24h] [ebp-10h]
double AttackMod; // [esp+2Ch] [ebp-8h]
int DeltaAttack; // [esp+30h] [ebp-4h]
int CurseDamage; // [esp+40h] [ebp+Ch] FORCED BYREF
AdjustedAttack = army::get_adjusted_attack(this, 0, ArgVar_2);
DeltaAttack = AdjustedAttack - akCreatureTypeTraits[this->Type].Attack - ArgVar_0;
AdjustedDefense = army::get_adjusted_defense(this, 0, 1);
isShooter = ArgVar_2;
Multiplier = 1.0;
ArgVar_0 = AdjustedDefense - akCreatureTypeTraits[this->Type].Defense - ArgVar_1;
if ( ArgVar_2 )
{
if ( !this->SpellDuration[AIR_SHIELD] )
goto labelSkipMultiplier;
ShieldMultiplier = this->AirShield_Multiplier;
}
else
{
if ( !this->SpellDuration[SHIELD] )
goto labelSkipMultiplier;
ShieldMultiplier = this->Shield_Multiplier;
}
Multiplier = ShieldMultiplier;
labelSkipMultiplier:
if ( this->SpellDuration[STONE] )
Multiplier = Multiplier * 0.5; // Половинный модификатор под окаменением
if ( this->SpellDuration[HYPNOTIZE] )
Side = 1 - this->Side;
else
Side = this->Side;
Hero = gpCombatManager->Hero[Side];
if ( Hero )
Multiplier = hero::GetDefenseFactor(Hero) * Multiplier;
DefenseMod = (ArgVar_0 * 0.05 + 1.0) * Multiplier;
if ( isShooter )
{
Type = this->Type;
if ( Type != _BALLISTA_
&& Type != ARROW_TOWER
&& ((this->Flags & SHOOTER) == 0
|| this->Shots <= 0
|| ((TempHero = gpCombatManager->Hero[combatMonster_GetSide(this)]) == 0
|| !hero::IsWieldingArtifact(TempHero, BOW_OF_THE_SHARPSHOOTER))
&& army::enemy_is_adjacent(this, 0)
|| this->SpellDuration[FORGETFULNESS] && this->ForgetfulnessLevel >= ADVANCED) )
{
isShooter = 0;
LOBYTE(ArgVar_2) = 0; // Случаи, когда AI не считает отряд стрелком:
// Баллиста не считается стрелком, а рассматривается отдельно ниже;
// Стрелковая башня тоже не является стрелком при расчётах;
// Отряд с дистанционной атакой, у которого закончились стрелы;
// Блокированный отряд с дистанционной атакой;
// Отряд с дистанционной атакой под действием заклинания "Забывчивость" продвинутого или экспертного уровня
//
}
}
AttackMod = DeltaAttack * 0.05 + 1.0;
if ( !isShooter && (this->Flags & SHOOTER) != 0 )
AttackMod = AttackMod * 0.5; // Берём половинный модификатор для стрелков, которые не могут стрелять
if ( this->Type == _BALLISTA_ ) // Целый отдельный случай для Баллисты
{
SpellDurationHypnotize = this->SpellDuration[HYPNOTIZE];
Side_0 = this->Side; // Но не помню, чтобы Баллисту можно было загипнотизировать :P
Side_1 = SpellDurationHypnotize ? 1 - Side_0 : this->Side;
if ( gpCombatManager->Hero[Side_1] )
{
if ( SpellDurationHypnotize )
Side_2 = 1 - Side_0;
else
Side_2 = this->Side;
if ( gpCombatManager->Hero[Side_2]->SecondarySkills[ARTILLERY] > 1 )
AttackMod = AttackMod + AttackMod;
if ( SpellDurationHypnotize )
Side_3 = 1 - Side_0;
else
Side_3 = this->Side;
AttackMod = AttackMod * ArtilleryEfficiency[gpCombatManager->Hero[Side_3]->SecondarySkills[ARTILLERY]];
}
}
SpellDurationBless = this->SpellDuration[BLESS];
if ( SpellDurationBless || this->SpellDuration[CURSE] )
{
BaseMaxDamage = this->MaxDamage;
BaseMinDamage = this->MinDamage;
ArgVar_0 = BaseMinDamage + BaseMaxDamage;
BaseAvgDamage = (BaseMinDamage + BaseMaxDamage) / 2.0;
if ( SpellDurationBless )
{
ArgVar_0 = BaseMaxDamage + this->BlessDamageBonus;
Damage = ArgVar_0;
}
else if ( this->SpellDuration[CURSE] )
{
CurseDamagePenalty = this->CurseDamagePenalty;
ArgVar_0 = 1;
CurseDamage = BaseMinDamage - CurseDamagePenalty;
isDamageLessThan1 = BaseMinDamage - CurseDamagePenalty < 1;
damage = &ArgVar_0;
if ( !isDamageLessThan1 )
damage = &CurseDamage;
Damage = *damage;
}
else
{
Damage = (BaseMinDamage + BaseMaxDamage) / 2.0;
}
isShooter = ArgVar_2;
AttackMod = Damage / BaseAvgDamage * AttackMod;
}
if ( isShooter && (this->Flags & DOUBLEATTACK) != 0 )
AttackMod = AttackMod + AttackMod; // Если стрелок с двойной атакой, удваиваем модификатор
k = sqrt(AttackMod * DefenseMod);
Flags = this->Flags;
EffectiveFightValue = k * akCreatureTypeTraits[this->Type].FightValue;
if ( (Flags & (SUMMON|SIEGEWEAPON)) != 0 ) // Если призванное существо или осадное орудие
{
if ( (Flags & CLONE) != 0 )
StackCurrentHealth = 1; // Если клон, Здоровье = 1
else
StackCurrentHealth = this->AmountAlive * this->Health - this->HealthLost;
Side_4 = this->Side;
TotalHealth = 0;
ArgVar_2 = 0;
Side_5 = Side_4;
StacksNum = gpCombatManager->StacksNum[Side_4];
FirstStack = gpCombatManager->BattleStack[Side_5];
if ( StacksNum > 0 ) // BattleStack[Side_5][0], но не знаю, как сделать это в IDA
{
pFlags = &FirstStack->Flags;
do // Цикл по всем отрядам в армии героя
{
if ( (ADJ(pFlags)->Flags & (SUMMON|CANNOTMOVE|SIEGEWEAPON)) == 0 )
{ // Если не призванное существо и не боевая машина
if ( (ADJ(pFlags)->Flags & CLONE) != 0 )
StackCurrentHealth_0 = 1; // Если клон, Здоровье = 1
else
StackCurrentHealth_0 = ADJ(pFlags)->AmountAlive * ADJ(pFlags)->Health - ADJ(pFlags)->HealthLost;
TotalHealth += StackCurrentHealth_0;
}
pFlags += 0x152;
--StacksNum;
}
while ( StacksNum );
ArgVar_2 = TotalHealth;
}
if ( !StackCurrentHealth )
return 0.1; // Если отряд погиб, его ценность равна 0.1
// Данная функция, кстати, используется также и для принятия решения о воскрешении
TotalHealth_0 = ArgVar_2;
ArgVar_2 = TotalHealth + StackCurrentHealth;
return TotalHealth_0 * EffectiveFightValue / (TotalHealth + StackCurrentHealth);
} // Обретает смысл сумма в знаменателе:
// данная формула только для призванных существ и Катапульты,
// а они являются добавочным "здоровьем" для армии.
// В цикле подсчёта суммарного здоровья армии выше такие отряды пропускаются.
// Можете поделить числитель и знаменатель на TotalHealth (TotalHealth_0 = TotalHealth)
// и получится красивая формула
return EffectiveFightValue;
} // А это уже эффективная боевая ценность "нормальных" отрядов
Кстати, как понимать вот это:
- Код: Выделить всё
((TempHero = gpCombatManager->Hero[combatMonster_GetSide(this)]) == 0
|| !hero::IsWieldingArtifact(TempHero, BOW_OF_THE_SHARPSHOOTER)
Причём тут вообще Лук Снайпера и отсутствие героя?
Ааа, вспомнил. Отсутствие героя чисто техническая проверка, чтобы не закрашилась функция hero::IsWieldingArtifact(), а Лук Снайпера при том, что с ним стрелки стреляют в рукопашной
* * *
Из этой функции делаем важный вывод: если заклинание изменяет урон отряда, то для того чтобы AI правильно считал эффективную боевую ценность отряда под таким заклинанием, нужно добавлять код, учитывающий это, в данную функцию (подобно Bless и Curse).