Попробовал ввести вспомогательный union:
- Код: Выделить всё
int __thiscall type_AI_spellcaster::get_frenzy_value(
type_AI_spellcaster *this,
army *army,
type_enchant_data_union unionData)
{
army *frenzyStack; // esi
army *frenzyTarget; // edi
int speed; // eax
int targetHealth; // eax
INT32 hitPoints; // ecx
bool canShoot; // bl
int avgDamage_0; // eax
bool canShoot_0; // al
int adjustedDamage; // ebx
int targetHealth_1; // eax
type_AI_spellcaster *spellCaster_0; // ecx
int frenzyWeight; // eax
signed int numberAlive; // [esp-Ch] [ebp-44h]
double avgDamage_1; // [esp+Ch] [ebp-2Ch]
double killedEnemyRatio; // [esp+14h] [ebp-24h]
int *attackValue; // [esp+1Ch] [ebp-1Ch]
int targetHealth_0; // [esp+20h] [ebp-18h] BYREF
type_AI_spellcaster_union spellCaster; // [esp+24h] [ebp-14h]
int avgDamage; // [esp+2Ch] [ebp-Ch]
auxStruct aux; // [esp+30h] [ebp-8h]
int health; // [esp+40h] [ebp+8h] FORCED BYREF
frenzyStack = army;
spellCaster.spellCaster[1] = this;
frenzyTarget = army->spellsData.frenzyTarget;
// Если нет цели, вес заклинания 0
if ( !frenzyTarget )
return 0;
speed = army::GetSpeed(army);
// Если отряд под Frenzy не может добраться до цели за 1 ход, вес заклинания 0
if ( army::get_AI_target_time(frenzyStack, speed) > 1 )
return 0;
// Может ли отряд под Frenzy стрелять?
aux.tmp = army::can_shoot(frenzyStack, 0);
// Получаем здоровье отряда под Frenzy
health = army::get_total_hit_points(frenzyStack, 0);
// Получаем здоровье цели
targetHealth = army::get_total_hit_points(frenzyTarget, 0);
hitPoints = frenzyStack->info.hitPoints;
targetHealth_0 = targetHealth;
canShoot = aux.tmp;
avgDamage = army::get_average_damage(frenzyStack, frenzyTarget, aux.tmp, (hitPoints + health - 1) / hitPoints, 1, 0);
// this->attackValue
attackValue = &spellCaster.spellCaster[1]->attackValue;
// Симулируем атаку
type_AI_combat_parameters::simulate_attack(
&spellCaster.spellCaster[1]->attackValue,
frenzyStack,
&health,
frenzyTarget,
&targetHealth_0,
aux.tmp,
0);
if ( !health )
return 0;
// Средний урон после атаки
avgDamage_0 = army::get_average_damage(
frenzyStack,
frenzyTarget,
canShoot,
(frenzyStack->info.hitPoints + health - 1) / frenzyStack->info.hitPoints,
1,
0);
if ( canShoot && (*&frenzyStack->info.flags & CF_TWO_ATTACKS) != 0 )
avgDamage_0 /= 2;
*&aux.tmp = avgDamage + avgDamage_0;
numberAlive = frenzyStack->numberAlive;
// 1 + avgDamageAfterAttack / avgDamage
*&aux = (avgDamage + avgDamage_0) / avgDamage;
canShoot_0 = army::can_shoot(frenzyStack, 0);
avgDamage = army::get_average_damage(frenzyStack, frenzyTarget, canShoot_0, numberAlive, 1, 0);
// Коэффициент потерь
killedEnemyRatio = *&aux;
avgDamage_1 = avgDamage;
adjustedDamage = (avgDamage_1 * *&aux);
targetHealth_1 = army::get_total_hit_points(frenzyTarget, 0);
*&aux.tmp = targetHealth_1;
if ( adjustedDamage > targetHealth_1 )
{
adjustedDamage = targetHealth_1;
// Помним, что в числителе - здоровье отряда противника
killedEnemyRatio = *&aux.tmp / avgDamage_1;
}
if ( adjustedDamage <= avgDamage )
return 0;
spellCaster_0 = spellCaster.spellCaster[1];
spellCaster.spellCaster[1] = spellCaster.spellCaster[1]->spellDuration;
// if (data->spellDuration < this->spellDuration)
if ( SLODWORD(unionData.data.spellDuration) < spellCaster.spellCaster[1] )
{
unionData.data.spellDuration = SLODWORD(unionData.data.spellDuration);
// data.spellDuration / this->spellDuration
unionData.data.spellDuration = unionData.data.spellDuration / spellCaster.spellCaster[1];
}
else
{
// Иначе 1.0
unionData.data.spellDuration = 1.0;
}
if ( (*&frenzyStack->info.flags & CF_DONE) != 0
&& (unionData.data.spellDuration = unionData.data.spellDuration - 1.0 / spellCaster.spellCaster[1],
unionData.data.spellDuration < 0.0) )
{
spellCaster.k = 0.0;
}
else
{
spellCaster.k = unionData.data.spellDuration;
}
unionData.data.spellDuration = army::get_total_combat_value(frenzyStack, *attackValue, spellCaster_0->defenseValue);
*&frenzyWeight = ((sqrt(killedEnemyRatio) - 1.0) * unionData.data.spellDuration * spellCaster.k);
return frenzyWeight;
}
Вроде, намного лучше стало. Должны же быть в IDA инструменты, помогающие бороться с повторным использованием переменных (причём для совершенно других типов)? Это ещё функция небольшая, а что делать, когда функция 10 килобайт с хардкорным реюзом адресного пространства? Там никакой декомпилятор не поможет
* * *
А реально, если в результате симуляции атаки отряд под Frenzy погибает, то комп не кастует Frenzy, но это не значит, что отряд не может погибнуть. Симуляция работает со средними уронами всё-таки.
Далее, вот это условие для Frenzy никогда не выполнится из-за того, что длительность Frenzy 1 раунд:
- Код: Выделить всё
// if (data->spellDuration < this->spellDuration)
if ( SLODWORD(unionData.data.spellDuration) < spellCaster.spellCaster[1] )
{
unionData.data.spellDuration = SLODWORD(unionData.data.spellDuration);
// data.spellDuration / this->spellDuration
unionData.data.spellDuration = unionData.data.spellDuration / spellCaster.spellCaster[1];
}
Поэтому в расчётах можно смело брать единицу (здесь идёт сравнение реальной длительности 1 и стандартной для заклинаний - сила_магии + артефакты_повышающие_длительность_заклинаний, а сила магии героя не может быть 0).
Соответственно, вот это тоже никогда не выполнится:
- Код: Выделить всё
if ( (*&frenzyStack->info.flags & CF_DONE) != 0
&& (unionData.data.spellDuration = unionData.data.spellDuration - 1.0 / spellCaster.spellCaster[1],
unionData.data.spellDuration < 0.0) )
{
spellCaster.k = 0.0;
}
Например, для героя с силой магии 10 и без артефактов - здесь идёт повторное использование переменной, поэтому заменим на k - k = 1.0 - 1.0 / 10 = 0.9.
Итого, вес Frenzy находится по формуле Weight(Frenzy) = [k * (sqrt(killedEnemyRatio) - 1)], где k = 1, если Frenzy кастуют до того, как отряд походил, и k = 1 - 1 / NormalSpellDuration, если после (не знаю, возможен ли в игре второй случай, но Frenzy может и на ответке сработать).
* * *
Судя по вот этому:
- Код: Выделить всё
type_AI_combat_parameters::simulate_attack(&spellCaster.spellCaster[1]->attackValue, frenzyStack, &health, frenzyTarget, &targetHealth_0, aux.tmp, 0);
и сигнатуре функции:
- Код: Выделить всё
public: void __cdecl type_AI_combat_parameters::simulate_attack(class army const &, long &, class army const &, long &, bool, long)const
можно сделать вывод, что type_AI_combat_parameters является частью структуры type_AI_spellcaster (оффсет 0x20). Есть мысли на этот счёт?
Текущая структура type_AI_combat_parameters:
- Код: Выделить всё
#pragma pack(push, 4)
struct type_AI_combat_parameters
{
int attack_modifier;
int defense_modifier;
char isOpponentDangerous;
char isBattleSimulated;
int casterTotalCombatValue;
int enemyTotalCombatValue;
int casterActiveStacksCombatValue;
int enemyActiveStacksCombatValue;
int dangerRating;
int side[2];
};
#pragma pack(pop)
Cтруктура type_AI_spellcaster с 0x20 оффсета:
- Код: Выделить всё
void *attackValue;
int defenseValue;
char IsFightValueCapped;
char field_29;
char field_2A;
char field_2B;
...
Тут, кстати, нужно исправить void* на int. attackValue и defenseValue - это атака и защита вражеского героя (как минимум в первом раунде, потом, может быть, модифицированные значения).
Более того, конструктор type_AI_combat_parameters вызывается первым делом в конструкторе type_AI_spellcaster:
- Код: Выделить всё
type_AI_combat_parameters::type_AI_combat_parameters(&this->attackValue, CombatManager, side);
И тоже смещение 0x20.