Эх, много планов. Написал "родные" функции взвешивания всех новых заклинаний, кроме трёх: Disease, Death Blow и Drain Life. Самый сложный кейс - Drain Life. Решил пока отсебятину не добавлять, а посмотреть, как родной AI будет ими пользоваться. Есть проблема с массовыми оригинальными заклинаниями, которым AI вешает уж очень большую ценность. Хотя, например, Expert Stone Skin почти всегда хуже Fear на самый опасный стек. Думаю, как это дело балансировать: для массовых же сумма идёт весов заклинаний, а Fear вешается на один отряд, но которое заклинание сильнее, сразу понятно. Оригинальный AI и кроме этого далёк от идеального. Это по воспоминаниям он хорош, но я провёл столько тестов, что могу с уверенностью сказать, что его, мягко говоря, есть куда улучшать. Также в планах статья по AI, ответственному за заклинания, начиная с базового метода type_spellvalue::get_raw_spell_value(), распадающегося на три функции оценки: бафов/дебафов, ударных заклинаний (одиночные и площадные идут вместе) и массовых (типа Armageddon), "нестандартные" (Land Mine, призыв, телепорт и т.п.) идут особняком. Думаю, я смогу объяснить работу по выбору заклинания AI от начала и до конца, но это очень большая тема и потребует много времени не только для полного понимания происходящего, но и для доступного описания
Кстати, функции оценки некоторые вполне приличные. Например, оценка Blind выходит на полный totalCombatValue отряда (на этой функции взвешивания пока базирую функцию взвешивания для Fear). Для справки: полная боевая ценность - это очень большая ценность. Лучше, наверноe, только оценка Fire Shield, - любимчика AI
Там два случая, как и для большинства оценок:
- Код: Выделить всё
if ( total_combat_value < this->combatParams.enemyActiveStacksCombatValue )
{
spellDuration = this->combatParams.expectedBattleDuration;
if ( data.spellDuration < spellDuration )
durationRatio = data.spellDuration / spellDuration;
else
durationRatio = 1.0;
if ( (army->creature.flags & CF_DONE) != 0
&& (durationRatio = durationRatio - 1.0 / spellDuration, durationRatio < 0.0) )
{
SpellDurationRatio = 0.0;
}
else
{
SpellDurationRatio = durationRatio;
}
effectiveCombatValue_0 = total_combat_value * SpellDurationRatio;
}
else
{
effectiveCombatValue_0 = (0.5 - sqrt(akSpelltraits[SPL_BLIND].effect[data.skillMastery] / 400.0))
* total_combat_value;
}
Есть претензии к оценке заклинаний призыва. Почему-то для них вместо Fight Value берётся AI Value. И вообще, отталкиваться от AI Value для заклинаний призыва не самая лучшая идея. Если масштабировать призыв по AI Value, то тех же Magic Elementals вызывалось бы с гулькин нос, и вызванный стек "стандартных" элементалей рвал бы, как Тузик грелку, отряд Magic Elementals или Firebirds. Сейчас более-менее нормальные числа, но мне не нравится, что AI, однажды призвав элементалей, начинает ими спамить. Возможно, это мой косяк, и проблема возникла из-за разрешения кастовать Firebirds и Sprites, не озираясь на тип призванных элементалей. Нужно будет затестить, есть ли такой эффект в оригинале после первого каста элементалей.
* * *
Оценка заклинания Prayer:
- Код: Выделить всё
int __thiscall type_AI_spellcaster::get_prayer_value(
type_AI_spellcaster *const this,
army *const army,
type_enchant_data data)
{
int defenseSkillValue; // edi
int result; // edi
int speed; // eax
int effect; // [esp+Ch] [ebp-4h]
army *AI_target; // [esp+18h] [ebp+8h]
effect = akSpelltraits[SPL_PRAYER].effect[data.skillMastery];
AI_target = army->AI_target;
defenseSkillValue = type_AI_spellcaster::get_defense_skill_value(this, army, data.spellDuration, effect);
result = type_AI_spellcaster::get_speed_value(this, army, effect, data.spellDuration) + defenseSkillValue;
if ( AI_target )
{
speed = army::GetSpeed(army);
if ( army::get_AI_target_time(army, speed) == 1 )
result += type_AI_spellcaster::get_attack_skill_value(this, army, AI_target, data.spellDuration, effect);
}
return result;
}
Как здесь всё просто, оказывается. Оценка бонуса к защите + оценка бонуса к скорости всегда, плюс оценка бонуса к атаке, если отряд может достать назначенный AI вражеский отряд* за один ход без бонуса к скорости, т.е. со своей текущей скоростью, что странно (или нет)**.
*) Этот отряд необязательно (и в большинстве случае не) тот, который будет атакован. AI_target - это отряд, который можно достать, с наилучшей предварительной оценкой. Иногда в качестве AI_target может выступать и вовсе уничтоженный отряд. Тут буду проверять, баг ли это, или такой отряд действительно назначается в качестве AI_target в отcутствие других целей в досягаемости одного хода (один ход в терминах игры называется "time").
**) Но есть функции оценки, где реально происходит симуляция. Т.е. строится "фиктивный" отряд с помощью конструктора army, а при необходимости и "фиктивный" оппонент, и проводится симуляция атаки. Правда, симуляция используется редко (примером может служить Frenzy, от функции взвешивания которого я отталкивался при написании функции взвешивания для Behemoth's Claws), хотя, естественно, и даёт лучшую оценку. В плагине NewSpells я стараюсь использовать симуляцию чаще. По поводу "или нет". Для "или нет" пришлось бы пересчитывать и AI_target (см. последние поля army), чем не заморочились, так что оценка Prayer страдает.