Объявления

Друзья, если не получается зарегистрироваться, напишите на почту vdv_forever@bk.ru.
Я оторву свою задницу от всех дел и обязательно Вас активирую! :smile10:
Добро пожаловать на геройский форум! :smile25:

Как создать плагин для HD мода

Герои Меча и Магии III: Возрождение Эрафии, Герои Меча и Магии III Дыхание Смерти, Герои Меча и Магии III Клинок Армагеддона, Герои Меча и Магии III Хроники Героев
offlineАватара пользователя
leiz  
Подмастерье
Подмастерье
 
Сообщения: 187
Зарегистрирован: 15 сен 2018, 07:58
Пол: Не указан
Поблагодарили: 95 раз.

Re: Как создать плагин для HD мода

Сообщение leiz » 27 авг 2021, 20:46

Ну, миелофона и машины времени у меня нет, чтобы уверенно рассуждать о том, что планировали разработчики. Да, ваши заклинания смотрятся естественно: если бы вы устроили мистификацию с якобы невыпущенным патчем SoD 3.3 (добавляющим новые заклинания) и продемонстрировали видео с этими заклинаниями - было бы очень убедительно. Еще не поздно, кстати (тему почистим, помолчим сколько требуется, а потом признаетесь в розыгрыше) :smile2:
Вернуться к началу

offlineRolex  
имя: Alex
Ветеран
Ветеран
 
Сообщения: 898
Зарегистрирован: 22 сен 2020, 18:58
Откуда: УКРАИНА
Пол: Мужчина
Поблагодарили: 53 раз.

Re: Как создать плагин для HD мода

Сообщение Rolex » 29 авг 2021, 20:26

Тестируя Disease заметил, что эффект замедления идет с округлением вверх. То есть если есть хоть какой-то остаток, то он отсекается и прибавляется единичка, подобно HotA (хотя нет, там всегда +1, даже когда нет остатка). В то время как в оригинале, если есть остаток, то он отсекается без каких-либо прибавок, то есть округление всегда вниз. Как лучше даже и не знаю. Но первый вариант, наверное, будет более сбалансированным что-ли. :smile5:
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5587
Зарегистрирован: 17 сен 2010, 12:58
Пол: Мужчина
Награды: 14
Высшая медаль (1) Победителю турнира по HMM1_TE (2) Победителю этапа по HMM1 (1) Победителю этапа по HMM2 (1) Лучшему из лучших (1) 2 место 1 этапа по HMM1 (1)
3 место 1 этапа по HMM1 (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (2) Победителю турнира по KB (1) Грандмастер оффлайн-турниров (1) Боевой шлем (1)
Поблагодарили: 2185 раз.

Re: Как создать плагин для HD мода

Сообщение AlexSpl » 31 авг 2021, 04:18

Я специально отнимаю единичку, чтобы эффект снижения скорости был заметен при любой скорости отряда, но можно легко убрать.
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5587
Зарегистрирован: 17 сен 2010, 12:58
Пол: Мужчина
Награды: 14
Высшая медаль (1) Победителю турнира по HMM1_TE (2) Победителю этапа по HMM1 (1) Победителю этапа по HMM2 (1) Лучшему из лучших (1) 2 место 1 этапа по HMM1 (1)
3 место 1 этапа по HMM1 (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (2) Победителю турнира по KB (1) Грандмастер оффлайн-турниров (1) Боевой шлем (1)
Поблагодарили: 2185 раз.

Re: Как создать плагин для HD мода

Сообщение AlexSpl » 01 сен 2021, 14:13

Код: Выделить всё
#define _CRT_SECURE_NO_WARNINGS
#include <bitset>
#include "..\..\HotA\homm3.h"

#define SPELLS_MAX 128
#define ANIMS_MAX 128
#define ADVSPELLS_NUM 10
#define SPECABIL_NUM 11
#define ARTIFACTS_NUM 144

#define SPELLS_NUM 89
#define SPL_FEAR_NEW 81
#define SPL_POISON_NEW 82
#define SPL_DISEASE_NEW 83
#define SPL_AGING_NEW 84
#define SPL_DEATH_CLOUD_NEW 85
#define SPL_DEATH_BLOW_NEW 86
#define SPL_FIREBIRD_NEW 87
#define SPL_MAGIC_ELEMENTAL_NEW 88

#define ANIMS_NUM 89
#define ANIM_FEAR_NEW 83
#define ANIM_POISON_NEW 84
#define ANIM_DISEASE_NEW 85
#define ANIM_AGING_NEW 86
#define ANIM_DEATH_CLOUD_NEW 87
#define ANIM_DEATH_BLOW_NEW 88

Patcher* _P;
PatcherInstance* _PI;

struct _ComboArtInfo_ {
   int index;
   std::bitset<160> parts;
};

struct _MagicAnim_ {
   char* defName;
   char* name;
   int type;
};

#define o_ComboArtInfo (*(_ComboArtInfo_**)0x660B6C)
#define o_MagicAnim ((_MagicAnim_*)0x641E18)

_Spell_ spell[SPELLS_MAX];
_MagicAnim_ anim[ANIMS_MAX];
//char iniPath[MAX_PATH];

char spellIndirectTableA[] = {
   // Spells (starting from Quicksand #10)
    0,  1,  2,  2,  3,  4,  4,  4,  4,  4,
    5,  5,  5,  5,  4,  4,  4,  4,  4,  4,
    4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
    6,  4,  4,  4,  4,  4,  4,  4,  4,  4,
    4,  4,  4,  4,  4,  4,  4,  4,  4,  5,
    4,  4,  4,  7,  8,  9, 10, 10, 10, 10,
   // Special Abilities (you cannot cast them anyway)
   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   -1,
   // New Spells (starting from Fear #81)
    4,  4,  4,  4,  5,  4, 10, 10
};

char spellIndirectTableB[] = {
   // Spells (starting from Quicksand #10)
    0,  1,  2,  3,  4,  5,  6,  7,  8,  9,
   10, 11, 12, 13, 14, 15, 16, 17, 17, 17,
   17, 17, 17, 17, 17, 18, 17, 19, 20, 20,
   21, 17, 17, 22, 17, 17, 17, 23, 17, 17,
   17, 17, 17, 17, 17, 17, 17,  7, 17, 24,
   17, 17, 17, 25, 26, 27, 28, 29, 30, 31,
   // Special Abilities
   32, 33, 34, 17, 17, 34, 37, 37, 35, 37,
   36,
   // New Spells (starting from Fear #81)
   17, 17, 17, 17, 11, 17, 38, 39
};

char spellIndirectTableC[] = {
   // Spells (starting from Shield #27)
    0,  1,  2,  3,  4,  5,  6,  7, 32,  8,
   32, 32, 32, 32,  9, 10, 11, 12, 13, 14,
   32, 15, 16, 17, 18, 19, 20, 21, 22, 23,
   32, 24, 25, 26, 27, 28, 32, 32, 32, 32,
   32, 32, 32,
   // Special Abilities
   32, 29, 32, 30, 32, 31, -1, -1, -1, -1,
   -1,
   // New Spells (starting from Fear #81)
   32, 32, 33, 32, 32, 32, 32, 32
};

char spellIndirectTableD[] = {
   // Spells (starting from Weakness #45)
    0,  1,  9,  2,  9,  9,  9,  9,  3,  4,
    9,  9,  9,  9,  9,  5,  9,  9,  9,  9,
    9,  9,  9,  9,  9,
    // Special Abilities
    9,  9,  6,  7,  9,  8, -1, -1, -1, -1,
    -1,
    // New Spells (starting from Fear #81)
    9,  9, 10,  9,  9,  9,  9,  9
};

bool shrineSpells[SPELLS_MAX];

struct FearSpellParams {
   int SpeedPenalty;
};

FearSpellParams fearSpellParams[2][21];

struct DiseaseSpellParams {
   int AttackPenalty;
   int DefensePenalty;
   int SpeedPenalty;
};

DiseaseSpellParams diseaseSpell[4];
DiseaseSpellParams diseaseSpellParams[2][21];

struct AgingSpellParams {
   double HealthPenalty;
};

AgingSpellParams agingSpellParams[2][21];

int __stdcall afterInit(LoHook* h, HookContext* c)
{
   // Spell Table
   for (int i = 0; i < SPL_FEAR_NEW; ++i)
      spell[i] = o_Spell[i];

   // Fear
   spell[SPL_FEAR_NEW].type = -1;
   spell[SPL_FEAR_NEW].wav_name = "FearRoE.wav";
   spell[SPL_FEAR_NEW].animation_ix = ANIM_FEAR_NEW;
   spell[SPL_FEAR_NEW].flags = 0x20415;
   spell[SPL_FEAR_NEW].name = "Fear";
   spell[SPL_FEAR_NEW].short_name = "Fear";
   spell[SPL_FEAR_NEW].level = 4;
   spell[SPL_FEAR_NEW].school_flags = SSF_EARTH;
   spell[SPL_FEAR_NEW].mana_cost[0] = 16;
   spell[SPL_FEAR_NEW].mana_cost[1] = 8;
   spell[SPL_FEAR_NEW].mana_cost[2] = 8;
   spell[SPL_FEAR_NEW].mana_cost[3] = 8;
   spell[SPL_FEAR_NEW].eff_power = 0;
   spell[SPL_FEAR_NEW].effect[0] = 25;
   spell[SPL_FEAR_NEW].effect[1] = 25;
   spell[SPL_FEAR_NEW].effect[2] = 50;
   spell[SPL_FEAR_NEW].effect[3] = 75;
   spell[SPL_FEAR_NEW].chance2get_var[0] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[1] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[2] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[3] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[4] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[5] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[6] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[7] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[8] = 10;
   spell[SPL_FEAR_NEW].ai_value[0] = 50;
   spell[SPL_FEAR_NEW].ai_value[1] = 50;
   spell[SPL_FEAR_NEW].ai_value[2] = 100;
   spell[SPL_FEAR_NEW].ai_value[3] = 150;
   spell[SPL_FEAR_NEW].description[0] =
      "{Fear}\n\nStrikes an enemy unit with such fear that it becomes unable to attack and nearly unable to move.\n";
   spell[SPL_FEAR_NEW].description[1] =
      "{Basic Fear}\n\nStrikes an enemy unit with such fear that it becomes unable to attack and nearly unable to move.\n\nSpell Point cost is half that of Normal Fear.\n";
   spell[SPL_FEAR_NEW].description[2] =
      "{Advanced Fear}\n\nStrikes an enemy unit with such fear that it becomes unable to attack and nearly unable to move.\n\nPenalty to movement is twice that of Basic Fear.\n";
   spell[SPL_FEAR_NEW].description[3] =
      "{Expert Fear}\n\nStrikes an enemy unit with such fear that it becomes unable to attack and nearly unable to move.\n\nPenalty to movement is three times that of Basic Fear.\n";

   // Poison
   spell[SPL_POISON_NEW].type = -1;
   spell[SPL_POISON_NEW].wav_name = "Poison.wav";
   spell[SPL_POISON_NEW].animation_ix = ANIM_POISON_NEW;
   spell[SPL_POISON_NEW].flags = 0x1015;
   spell[SPL_POISON_NEW].name = "Poison";
   spell[SPL_POISON_NEW].short_name = "Poison";
   spell[SPL_POISON_NEW].level = 4;
   spell[SPL_POISON_NEW].school_flags = SSF_EARTH;
   spell[SPL_POISON_NEW].mana_cost[0] = 20;
   spell[SPL_POISON_NEW].mana_cost[1] = 16;
   spell[SPL_POISON_NEW].mana_cost[2] = 16;
   spell[SPL_POISON_NEW].mana_cost[3] = 16;
   spell[SPL_POISON_NEW].eff_power = 0;
   spell[SPL_POISON_NEW].effect[0] = 0;
   spell[SPL_POISON_NEW].effect[1] = 0;
   spell[SPL_POISON_NEW].effect[2] = 0;
   spell[SPL_POISON_NEW].effect[3] = 0;
   spell[SPL_POISON_NEW].chance2get_var[0] = 10;
   spell[SPL_POISON_NEW].chance2get_var[1] = 10;
   spell[SPL_POISON_NEW].chance2get_var[2] = 10;
   spell[SPL_POISON_NEW].chance2get_var[3] = 10;
   spell[SPL_POISON_NEW].chance2get_var[4] = 10;
   spell[SPL_POISON_NEW].chance2get_var[5] = 10;
   spell[SPL_POISON_NEW].chance2get_var[6] = 10;
   spell[SPL_POISON_NEW].chance2get_var[7] = 10;
   spell[SPL_POISON_NEW].chance2get_var[8] = 10;
   spell[SPL_POISON_NEW].ai_value[0] = 50;
   spell[SPL_POISON_NEW].ai_value[1] = 50;
   spell[SPL_POISON_NEW].ai_value[2] = 100;
   spell[SPL_POISON_NEW].ai_value[3] = 150;
   spell[SPL_POISON_NEW].description[0] =
      "{Poison}\n\nPoison Description.\n";
   spell[SPL_POISON_NEW].description[1] =
      "{Basic Poison}\n\nBasic Poison Description.\n";
   spell[SPL_POISON_NEW].description[2] =
      "{Advanced Poison}\n\nAdvanced Poison Description.\n";
   spell[SPL_POISON_NEW].description[3] =
      "{Expert Poison}\n\nExpert Poison Description.\n";

   // Disease
   spell[SPL_DISEASE_NEW].type = -1;
   spell[SPL_DISEASE_NEW].wav_name = "Disease.wav";
   spell[SPL_DISEASE_NEW].animation_ix = ANIM_DISEASE_NEW;
   spell[SPL_DISEASE_NEW].flags = 0x40045;
   spell[SPL_DISEASE_NEW].name = "Disease";
   spell[SPL_DISEASE_NEW].short_name = "Disease";
   spell[SPL_DISEASE_NEW].level = 4;
   spell[SPL_DISEASE_NEW].school_flags = SSF_EARTH;
   spell[SPL_DISEASE_NEW].mana_cost[0] = 16;
   spell[SPL_DISEASE_NEW].mana_cost[1] = 12;
   spell[SPL_DISEASE_NEW].mana_cost[2] = 12;
   spell[SPL_DISEASE_NEW].mana_cost[3] = 12;
   spell[SPL_DISEASE_NEW].eff_power = 0;
   spell[SPL_DISEASE_NEW].effect[0] = 2;
   spell[SPL_DISEASE_NEW].effect[1] = 2;
   spell[SPL_DISEASE_NEW].effect[2] = 4;
   spell[SPL_DISEASE_NEW].effect[3] = 4;
   spell[SPL_DISEASE_NEW].chance2get_var[0] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[1] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[2] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[3] = 5;
   spell[SPL_DISEASE_NEW].chance2get_var[4] = 10;
   spell[SPL_DISEASE_NEW].chance2get_var[5] = 5;
   spell[SPL_DISEASE_NEW].chance2get_var[6] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[7] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[8] = 5;
   spell[SPL_DISEASE_NEW].ai_value[0] = 50;
   spell[SPL_DISEASE_NEW].ai_value[1] = 50;
   spell[SPL_DISEASE_NEW].ai_value[2] = 100;
   spell[SPL_DISEASE_NEW].ai_value[3] = 150;
   spell[SPL_DISEASE_NEW].description[0] =
      "{Disease}\n\nDisease Description.\n";
   spell[SPL_DISEASE_NEW].description[1] =
      "{Basic Disease}\n\nBasic Disease Description.\n";
   spell[SPL_DISEASE_NEW].description[2] =
      "{Advanced Disease}\n\nAdvanced Disease Description.\n";
   spell[SPL_DISEASE_NEW].description[3] =
      "{Expert Disease}\n\nExpert Disease Description.\n";

   diseaseSpell[0].AttackPenalty = 2;
   diseaseSpell[0].DefensePenalty = 2;
   diseaseSpell[0].SpeedPenalty = 20;
   diseaseSpell[1].AttackPenalty = 2;
   diseaseSpell[1].DefensePenalty = 2;
   diseaseSpell[1].SpeedPenalty = 20;
   diseaseSpell[2].AttackPenalty = 4;
   diseaseSpell[2].DefensePenalty = 4;
   diseaseSpell[2].SpeedPenalty = 40;
   diseaseSpell[3].AttackPenalty = 4;
   diseaseSpell[3].DefensePenalty = 4;
   diseaseSpell[3].SpeedPenalty = 40;
   
   // Aging
   spell[SPL_AGING_NEW].type = -1;
   spell[SPL_AGING_NEW].wav_name = "Age.wav";
   spell[SPL_AGING_NEW].animation_ix = ANIM_AGING_NEW;
   spell[SPL_AGING_NEW].flags = 0x1015;
   spell[SPL_AGING_NEW].name = "Aging";
   spell[SPL_AGING_NEW].short_name = "Aging";
   spell[SPL_AGING_NEW].level = 5;
   spell[SPL_AGING_NEW].school_flags = SSF_FIRE;
   spell[SPL_AGING_NEW].mana_cost[0] = 25;
   spell[SPL_AGING_NEW].mana_cost[1] = 20;
   spell[SPL_AGING_NEW].mana_cost[2] = 20;
   spell[SPL_AGING_NEW].mana_cost[3] = 20;
   spell[SPL_AGING_NEW].eff_power = 0;
   spell[SPL_AGING_NEW].effect[0] = 30;
   spell[SPL_AGING_NEW].effect[1] = 30;
   spell[SPL_AGING_NEW].effect[2] = 40;
   spell[SPL_AGING_NEW].effect[3] = 50;
   spell[SPL_AGING_NEW].chance2get_var[0] = 10;
   spell[SPL_AGING_NEW].chance2get_var[1] = 10;
   spell[SPL_AGING_NEW].chance2get_var[2] = 10;
   spell[SPL_AGING_NEW].chance2get_var[3] = 10;
   spell[SPL_AGING_NEW].chance2get_var[4] = 10;
   spell[SPL_AGING_NEW].chance2get_var[5] = 10;
   spell[SPL_AGING_NEW].chance2get_var[6] = 10;
   spell[SPL_AGING_NEW].chance2get_var[7] = 10;
   spell[SPL_AGING_NEW].chance2get_var[8] = 10;
   spell[SPL_AGING_NEW].ai_value[0] = 50;
   spell[SPL_AGING_NEW].ai_value[1] = 50;
   spell[SPL_AGING_NEW].ai_value[2] = 100;
   spell[SPL_AGING_NEW].ai_value[3] = 150;
   spell[SPL_AGING_NEW].description[0] =
      "{Aging}\n\nAging Description.\n";
   spell[SPL_AGING_NEW].description[1] =
      "{Basic Aging}\n\nBasic Aging Description.\n";
   spell[SPL_AGING_NEW].description[2] =
      "{Advanced Aging}\n\nAdvanced Aging Description.\n";
   spell[SPL_AGING_NEW].description[3] =
      "{Expert Aging}\n\nExpert Aging Description.\n";

   // Death Cloud
   spell[SPL_DEATH_CLOUD_NEW].type = 0;
   spell[SPL_DEATH_CLOUD_NEW].wav_name = "Deathcld.wav";
   spell[SPL_DEATH_CLOUD_NEW].animation_ix = ANIM_DEATH_CLOUD_NEW;
   spell[SPL_DEATH_CLOUD_NEW].flags = 0x8281;
   spell[SPL_DEATH_CLOUD_NEW].name = "Death Cloud";
   spell[SPL_DEATH_CLOUD_NEW].short_name = "Death Cloud";
   spell[SPL_DEATH_CLOUD_NEW].level = 5;
   spell[SPL_DEATH_CLOUD_NEW].school_flags = SSF_EARTH;
   spell[SPL_DEATH_CLOUD_NEW].mana_cost[0] = 25;
   spell[SPL_DEATH_CLOUD_NEW].mana_cost[1] = 20;
   spell[SPL_DEATH_CLOUD_NEW].mana_cost[2] = 20;
   spell[SPL_DEATH_CLOUD_NEW].mana_cost[3] = 20;
   spell[SPL_DEATH_CLOUD_NEW].eff_power = 40;
   spell[SPL_DEATH_CLOUD_NEW].effect[0] = 30;
   spell[SPL_DEATH_CLOUD_NEW].effect[1] = 30;
   spell[SPL_DEATH_CLOUD_NEW].effect[2] = 60;
   spell[SPL_DEATH_CLOUD_NEW].effect[3] = 120;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[0] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[1] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[2] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[3] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[4] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[5] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[6] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[7] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[8] = 10;
   spell[SPL_DEATH_CLOUD_NEW].ai_value[0] = 50;
   spell[SPL_DEATH_CLOUD_NEW].ai_value[1] = 50;
   spell[SPL_DEATH_CLOUD_NEW].ai_value[2] = 100;
   spell[SPL_DEATH_CLOUD_NEW].ai_value[3] = 150;
   spell[SPL_DEATH_CLOUD_NEW].description[0] =
      "{Death Cloud}\n\nDeath Cloud Description.\n";
   spell[SPL_DEATH_CLOUD_NEW].description[1] =
      "{Basic Death Cloud}\n\nBasic Death Cloud Description.\n";
   spell[SPL_DEATH_CLOUD_NEW].description[2] =
      "{Advanced Death Cloud}\n\nAdvanced Death Cloud Description.\n";
   spell[SPL_DEATH_CLOUD_NEW].description[3] =
      "{Expert Death Cloud}\n\nExpert Death Cloud Description.\n";

   // Death Blow
   spell[SPL_DEATH_BLOW_NEW].type = 1;
   spell[SPL_DEATH_BLOW_NEW].wav_name = "Deathblo.wav";
   spell[SPL_DEATH_BLOW_NEW].animation_ix = ANIM_DEATH_BLOW_NEW;
   spell[SPL_DEATH_BLOW_NEW].flags = 0x1015;
   spell[SPL_DEATH_BLOW_NEW].name = "Death Blow";
   spell[SPL_DEATH_BLOW_NEW].short_name = "Death Blow";
   spell[SPL_DEATH_BLOW_NEW].level = 5;
   spell[SPL_DEATH_BLOW_NEW].school_flags = SSF_FIRE;
   spell[SPL_DEATH_BLOW_NEW].mana_cost[0] = 25;
   spell[SPL_DEATH_BLOW_NEW].mana_cost[1] = 20;
   spell[SPL_DEATH_BLOW_NEW].mana_cost[2] = 20;
   spell[SPL_DEATH_BLOW_NEW].mana_cost[3] = 20;
   spell[SPL_DEATH_BLOW_NEW].eff_power = 0;
   spell[SPL_DEATH_BLOW_NEW].effect[0] = 0;
   spell[SPL_DEATH_BLOW_NEW].effect[1] = 0;
   spell[SPL_DEATH_BLOW_NEW].effect[2] = 0;
   spell[SPL_DEATH_BLOW_NEW].effect[3] = 0;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[0] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[1] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[2] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[3] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[4] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[5] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[6] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[7] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[8] = 10;
   spell[SPL_DEATH_BLOW_NEW].ai_value[0] = 50;
   spell[SPL_DEATH_BLOW_NEW].ai_value[1] = 50;
   spell[SPL_DEATH_BLOW_NEW].ai_value[2] = 100;
   spell[SPL_DEATH_BLOW_NEW].ai_value[3] = 150;
   spell[SPL_DEATH_BLOW_NEW].description[0] =
      "{Death Blow}\n\nDeath Blow Description.\n";
   spell[SPL_DEATH_BLOW_NEW].description[1] =
      "{Basic Death Blow}\n\nBasic Death Blow Description.\n";
   spell[SPL_DEATH_BLOW_NEW].description[2] =
      "{Advanced Death Blow}\n\nAdvanced Death Blow Description.\n";
   spell[SPL_DEATH_BLOW_NEW].description[3] =
      "{Expert Death Blow}\n\nExpert Death Blow Description.\n";

   // Firebird
   spell[SPL_FIREBIRD_NEW].type = 0;
   spell[SPL_FIREBIRD_NEW].wav_name = "SumnElm.wav";
   spell[SPL_FIREBIRD_NEW].animation_ix = ID_NONE;
   spell[SPL_FIREBIRD_NEW].flags = 0x80001;
   spell[SPL_FIREBIRD_NEW].name = "Firebird";
   spell[SPL_FIREBIRD_NEW].short_name = "Firebird";
   spell[SPL_FIREBIRD_NEW].level = 5;
   spell[SPL_FIREBIRD_NEW].school_flags = SSF_FIRE;
   spell[SPL_FIREBIRD_NEW].mana_cost[0] = 30;
   spell[SPL_FIREBIRD_NEW].mana_cost[1] = 25;
   spell[SPL_FIREBIRD_NEW].mana_cost[2] = 25;
   spell[SPL_FIREBIRD_NEW].mana_cost[3] = 25;
   spell[SPL_FIREBIRD_NEW].eff_power = 0;
   spell[SPL_FIREBIRD_NEW].effect[0] = 50;
   spell[SPL_FIREBIRD_NEW].effect[1] = 50;
   spell[SPL_FIREBIRD_NEW].effect[2] = 75;
   spell[SPL_FIREBIRD_NEW].effect[3] = 100;
   spell[SPL_FIREBIRD_NEW].chance2get_var[0] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[1] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[2] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[3] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[4] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[5] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[6] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[7] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[8] = 10;
   spell[SPL_FIREBIRD_NEW].ai_value[0] = 50;
   spell[SPL_FIREBIRD_NEW].ai_value[1] = 50;
   spell[SPL_FIREBIRD_NEW].ai_value[2] = 100;
   spell[SPL_FIREBIRD_NEW].ai_value[3] = 150;
   spell[SPL_FIREBIRD_NEW].description[0] =
      "{Firebird}\n\nAllows you to summon firebirds.  Once cast, no other elemental types may be summoned.\n";
   spell[SPL_FIREBIRD_NEW].description[1] =
      "{Basic Firebird}\n\nAllows you to summon firebirds.  Once cast, no other elemental types may be summoned.\n";
   spell[SPL_FIREBIRD_NEW].description[2] =
      "{Advanced Firebird}\n\nAllows you to summon firebirds.  Once cast, no other elemental types may be summoned.\n\n" \
      "Summons approximately one-and-a-half times the number of units as Basic Summon Firebird.\n";
   spell[SPL_FIREBIRD_NEW].description[3] =
      "{Expert Firebird}\n\nAllows you to summon firebirds.  Once cast, no other elemental types may be summoned.\n\n" \
      "Summons approximately twice the number of units as Basic Summon Firebird.\n";

   // Magic Elemental
   spell[SPL_MAGIC_ELEMENTAL_NEW].type = 0;
   spell[SPL_MAGIC_ELEMENTAL_NEW].wav_name = "SumnElm.wav";
   spell[SPL_MAGIC_ELEMENTAL_NEW].animation_ix = ID_NONE;
   spell[SPL_MAGIC_ELEMENTAL_NEW].flags = 0x80001;
   spell[SPL_MAGIC_ELEMENTAL_NEW].name = "Magic Elemental";
   spell[SPL_MAGIC_ELEMENTAL_NEW].short_name = "Magic Elemental";
   spell[SPL_MAGIC_ELEMENTAL_NEW].level = 5;
   spell[SPL_MAGIC_ELEMENTAL_NEW].school_flags = SSF_AIR | SSF_FIRE | SSF_WATER | SSF_EARTH;
   spell[SPL_MAGIC_ELEMENTAL_NEW].mana_cost[0] = 30;
   spell[SPL_MAGIC_ELEMENTAL_NEW].mana_cost[1] = 25;
   spell[SPL_MAGIC_ELEMENTAL_NEW].mana_cost[2] = 25;
   spell[SPL_MAGIC_ELEMENTAL_NEW].mana_cost[3] = 25;
   spell[SPL_MAGIC_ELEMENTAL_NEW].eff_power = 0;
   spell[SPL_MAGIC_ELEMENTAL_NEW].effect[0] = 100;
   spell[SPL_MAGIC_ELEMENTAL_NEW].effect[1] = 100;
   spell[SPL_MAGIC_ELEMENTAL_NEW].effect[2] = 150;
   spell[SPL_MAGIC_ELEMENTAL_NEW].effect[3] = 200;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[0] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[1] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[2] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[3] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[4] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[5] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[6] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[7] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[8] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].ai_value[0] = 50;
   spell[SPL_MAGIC_ELEMENTAL_NEW].ai_value[1] = 50;
   spell[SPL_MAGIC_ELEMENTAL_NEW].ai_value[2] = 100;
   spell[SPL_MAGIC_ELEMENTAL_NEW].ai_value[3] = 150;
   spell[SPL_MAGIC_ELEMENTAL_NEW].description[0] =
      "{Magic Elemental}\n\nAllows you to summon magic elementals.  Once cast, no other elemental types may be summoned.\n";
   spell[SPL_MAGIC_ELEMENTAL_NEW].description[1] =
      "{Basic Magic Elemental}\n\nAllows you to summon magic elementals.  Once cast, no other elemental types may be summoned.\n";
   spell[SPL_MAGIC_ELEMENTAL_NEW].description[2] =
      "{Advanced Magic Elemental}\n\nAllows you to summon magic elementals.  Once cast, no other elemental types may be summoned.\n\n" \
      "Summons approximately one-and-a-half times the number of units as Basic Summon Magic Elemental.\n";
   spell[SPL_MAGIC_ELEMENTAL_NEW].description[3] =
      "{Expert Magic Elemental}\n\nAllows you to summon magic elementals.  Once cast, no other elemental types may be summoned.\n\n" \
      "Summons approximately twice the number of units as Basic Summon Magic Elemental.\n";

   _PI->WriteDword(0x687FA8, (int)&spell);

   // Magic Animation Table
   for (int i = 0; i < ANIM_FEAR_NEW; ++i)
      anim[i] = o_MagicAnim[i];

   // Fear
   anim[ANIM_FEAR_NEW].defName = "C07spE0.def";
   anim[ANIM_FEAR_NEW].name = "Fear";
   anim[ANIM_FEAR_NEW].type = 0x101;

   // Poison
   anim[ANIM_POISON_NEW].defName = "sp11_.def";
   anim[ANIM_POISON_NEW].name = "Poison";
   anim[ANIM_POISON_NEW].type = 1;

   // Disease
   anim[ANIM_DISEASE_NEW].defName = "sp05_.def";
   anim[ANIM_DISEASE_NEW].name = "Disease";
   anim[ANIM_DISEASE_NEW].type = 1;

   // Aging
   anim[ANIM_AGING_NEW].defName = "sp01_.def";
   anim[ANIM_AGING_NEW].name = "Aging";
   anim[ANIM_AGING_NEW].type = 1;

   // Death Cloud
   anim[ANIM_DEATH_CLOUD_NEW].defName = "sp04_.def";
   anim[ANIM_DEATH_CLOUD_NEW].name = "DeathCloud";
   anim[ANIM_DEATH_CLOUD_NEW].type = 1;

   // Death Blow
   anim[ANIM_DEATH_BLOW_NEW].defName = "sp03_.def";
   anim[ANIM_DEATH_BLOW_NEW].name = "DeathBlow";
   anim[ANIM_DEATH_BLOW_NEW].type = 1;

   int AnimAddrDefName[] = {0x43F77E, 0x43FB6A, 0x4963FB, 0x4965CF, 0x5A5036, 0x5A6B14, 0x5A7A74, 0x5A962C};
   int AnimAddrName[] = {0x4689C4, 0x49651A, 0x4966CD, 0x5A6D2D, 0x5A7B06};
   
   for (int i = 0; i < sizeof(AnimAddrDefName) / sizeof(int); ++i)
      _PI->WriteDword(AnimAddrDefName[i], (int)&anim->defName);

   for (int i = 0; i < sizeof(AnimAddrName) / sizeof(int); ++i)
      _PI->WriteDword(AnimAddrName[i], (int)&anim->name);

   _PI->WriteDword(0x43E503, (int)&anim->type);
   
   return EXEC_DEFAULT;
}

int __stdcall disableCreatureSpells(LoHook* h, HookContext* c)
{
   for (int i = SPL_STONE; i <= SPL_ACID_BREATH; ++i)
      o_GameMgr->disabled_shrines[i] = true;

   return EXEC_DEFAULT;
}

int __stdcall shrineSpellsInit(LoHook* h, HookContext* c)
{
   for (int i = SPL_STONE; i <= SPL_ACID_BREATH; ++i)
      o_GameMgr->disabled_shrines[i] = true;   
   
   memcpy(&shrineSpells, &o_GameMgr->disabled_shrines, sizeof(shrineSpells) / sizeof(bool));   

   c->return_address = 0x4C2641;
   return NO_EXEC_DEFAULT;
}

int __fastcall fillShrine(_GameMgr_* game, int unused_edx, int shrineFlags)
{
   int availSpellsNum = 0;

   for (int i = 0; i < SPELLS_NUM; ++i)
   {
      int lvl = o_Spell[i].level - 1;
      if (lvl < 5 && 1 << lvl & shrineFlags && o_Spell[i].school_flags && !shrineSpells[i])
         ++availSpellsNum;
   }

   int spell = 0;
   int chosenSpell = 0;

   if (availSpellsNum)
   {
      int rndSpell = Randint(0, availSpellsNum - 1);
     
      for (int i = 0; i < SPELLS_NUM; ++i)
      {
         int lvl = o_Spell[i].level - 1;
         if (lvl < 5 && 1 << lvl & shrineFlags && o_Spell[i].school_flags && !shrineSpells[i])
         {
            if (spell == rndSpell) break;
            ++spell;
         }
         ++chosenSpell;
      }
     
      shrineSpells[chosenSpell] = true;
   }
   else
   {
      spell = 0;
     
      for (int i = 0; i < SPELLS_NUM; ++i)
      {
         int lvl = o_Spell[i].level - 1;
         if (lvl < 5 && 1 << lvl & shrineFlags && o_Spell[i].school_flags && !game->disabled_shrines[i])
         {
            shrineSpells[i] = false;
            ++spell;
         }
      }
     
      chosenSpell = spell ? fillShrine(game, unused_edx, shrineFlags) : ID_NONE;
   }

   return chosenSpell;
}

// For shooters only?
bool __fastcall unitCanAttack(_BattleStack_* stack, int unused_edx, _BattleStack_* foeStack)
{
   if (!foeStack || stack == foeStack)
      return false;

   if (stack->active_spell_duration[SPL_BERSERK] || foeStack->active_spell_duration[SPL_BERSERK])
      return true;

   int side = stack->active_spell_duration[SPL_HYPNOTIZE] ? 1 - stack->side : stack->side;

   return side != foeStack->side;
}

int __stdcall setCursor(HiHook* h, _BattleMgr_* battleMgr, int hex)
{
   int cursorType = CALL_2(int, __thiscall, h->GetDefaultFunc(), battleMgr, hex);

   _BattleStack_* stack = &battleMgr->stack[battleMgr->current_side][battleMgr->current_stack_ix];

   if (stack && stack->active_spell_duration[SPL_FEAR_NEW] && (cursorType == 3 || cursorType == 15 || cursorType == 7))
      return 0;

   return cursorType;
}

int __stdcall applySpell(LoHook* h, HookContext* c)
{
   int spell = *(int*)(c->ebp + 8);
   _Hero_* hero = *(_Hero_**)(c->ebp + 0x14);
   _BattleStack_* stack = (_BattleStack_*)c->esi;
   int schoolLevel = c->eax;
   int effect = 0;
   
   switch (spell)
   {
   case SPL_FEAR_NEW:
      fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty = o_Spell[SPL_FEAR_NEW].effect[schoolLevel];

      if (!(stack->creature.flags & BCF_CANT_MOVE))
      {
         fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty =
            stack->creature.speed * fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty / 100;

         if (fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty < 1)
            fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty = 1;

         stack->creature.speed -= fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty;
         if (stack->creature.speed < 1)
         {
            fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty += stack->creature.speed - 1;
            stack->creature.speed = 1;
         }
      }

      break;

   case SPL_DISEASE_NEW:
      diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty = diseaseSpell[schoolLevel].AttackPenalty;
      diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty = diseaseSpell[schoolLevel].DefensePenalty;
      diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty = diseaseSpell[schoolLevel].SpeedPenalty; // %

      // optional, as we don't have heroes with Disease spell specialty yet
      if (hero)
      {
         effect = CALL_4(int, __thiscall, 0x4E6260, hero, SPL_DISEASE_NEW, hero->level, effect);
         diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty += effect;
         diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty += effect;
         diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty += effect; // %
      }

      stack->creature.attack -= diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty;
      stack->creature.defence -= diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty;
     
      // not allowing attack and defense to drop below 0
      if (stack->creature.attack < 0)
      {
         diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty += stack->creature.attack;
         stack->creature.attack = 0;
      }

      if (stack->creature.defence < 0)
      {
         diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty += stack->creature.defence;
         stack->creature.defence = 0;
      }

      if (!(stack->creature.flags & BCF_CANT_MOVE))
      {
         diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty =
            stack->creature.speed * diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty / 100;

         if (diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty < 1)
            diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty = 1;

         stack->creature.speed -= diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty;
         if (stack->creature.speed < 1)
         {
            diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty += stack->creature.speed - 1;
            stack->creature.speed = 1;
         }
      }
     
      break;

   case SPL_AGING:
   case SPL_AGING_NEW:
      {
      float healthMul = stack->Field<float>(0x4A4);
      double fullHealth = (double)stack->full_hp * healthMul;

      if (stack->active_spell_duration[SPL_AGING])
         fullHealth *= 0.5;

      if (stack->active_spell_duration[SPL_AGING_NEW])
      {
         agingSpellParams[stack->side][stack->index_on_side].HealthPenalty =
            1.0 - o_Spell[SPL_AGING_NEW].effect[schoolLevel] / 100.0;

         fullHealth *= agingSpellParams[stack->side][stack->index_on_side].HealthPenalty;
      }

      int health = (int)(fullHealth + 0.95);

      stack->creature.hit_points = health;

      if (health - 1 < stack->lost_hp)
         stack->lost_hp = health - 1;
      }

      break;

   default:
      return EXEC_DEFAULT;
   }
   
   c->return_address = 0x444D5C;
   return NO_EXEC_DEFAULT;
}

int __stdcall resetSpell(LoHook* h, HookContext* c)
{
   int spell = *(int*)(c->ebp + 8);
   _BattleStack_* stack = (_BattleStack_*)c->esi;

   switch (spell)
   {
   case SPL_FEAR_NEW:
      if (!(stack->creature.flags & BCF_CANT_MOVE))
         stack->creature.speed += fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty;
     
      break;

   case SPL_DISEASE_NEW:
      stack->creature.attack += diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty;
      stack->creature.defence += diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty;

      if (!(stack->creature.flags & BCF_CANT_MOVE))
         stack->creature.speed += diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty;
     
      break;
   
   case SPL_AGING:
   case SPL_AGING_NEW:
      {
      float healthMul = stack->Field<float>(0x4A4);
      double fullHealth = (double)stack->full_hp * healthMul;

      if (stack->active_spell_duration[SPL_AGING])
         fullHealth *= 0.5;

      if (stack->active_spell_duration[SPL_AGING_NEW])
         fullHealth *= agingSpellParams[stack->side][stack->index_on_side].HealthPenalty;

      int health = (int)(fullHealth + 0.95);

      stack->creature.hit_points = health;

      if (health - 1 < stack->lost_hp)
         stack->lost_hp = health - 1;
      }

      break;
   
   default:
      return EXEC_DEFAULT;
   }

   c->return_address = 0x4444E1;
   return NO_EXEC_DEFAULT;
}

int __stdcall DeathCloud(LoHook* h, HookContext* c)
{
   if (c->edx == SPL_DEATH_CLOUD_NEW)
   {
      CALL_5(void, __thiscall, 0x5A4C80, o_BattleMgr, *(int*)(c->ebp + 0xC), SPL_DEATH_CLOUD_NEW, c->esi, *(int*)(c->ebp + 0x1C));
   
      c->return_address = 0x5A2368;
      return NO_EXEC_DEFAULT;
   }

   return EXEC_DEFAULT;
}

int __stdcall SummonCreatures(LoHook* h, HookContext* c)
{
   if (c->ecx == 38 || c->ecx == 39)
   {
      int spell;
      int creature;

      switch (c->ecx)
      {
      case 38:
         spell = SPL_FIREBIRD_NEW;
         creature = CID_FIREBIRD;
         break;
      case 39:
         spell = SPL_MAGIC_ELEMENTAL_NEW;
         creature = CID_MAGIC_ELEMENTAL;
         break;
      }

      CALL_5(void, __thiscall, 0x5A7390, o_BattleMgr, spell, creature, *(int*)(c->ebp + 0x1C), c->esi);
   
      c->return_address = 0x5A2368;
      return NO_EXEC_DEFAULT;
   }

   return EXEC_DEFAULT;
}

int __stdcall setSummonedCreaturesNumber(LoHook* h, HookContext* c)
{
   int creature = *(int*)(c->ebp + 0xC);
   int sp = *(int*)(c->ebp + 0x10);
   int schoolLevel = *(int*)(c->ebp + 0x14);

   if (creature == CID_FIREBIRD)
      c->esi = o_Spell[SPL_FIREBIRD_NEW].effect[schoolLevel] * sp / 100;
   else if (creature == CID_MAGIC_ELEMENTAL)
      c->esi = o_Spell[SPL_MAGIC_ELEMENTAL_NEW].effect[schoolLevel] * sp / 100;
   else
      return EXEC_DEFAULT;

   if (c->esi < 1) c->esi = 1;
   
   c->return_address = 0x5A7526;
   return NO_EXEC_DEFAULT;
}

int __stdcall checkSummonedCreaturesType(LoHook* h, HookContext* c)
{
   int spell = c->ebx;

   switch (spell)
   {
   case SPL_FIRE_ELEMENTAL:
      c->eax = CID_FIRE_ELEMENTAL;
      break;
   case SPL_EARTH_ELEMENTAL:
      c->eax = CID_EARTH_ELEMENTAL;
      break;
   case SPL_WATER_ELEMENTAL:
      c->eax = CID_WATER_ELEMENTAL;
      break;
   case SPL_AIR_ELEMENTAL:
      c->eax = CID_AIR_ELEMENTAL;
      break;
   case SPL_FIREBIRD_NEW:
      c->eax = CID_FIREBIRD;
      break;
   case SPL_MAGIC_ELEMENTAL_NEW:
      c->eax = CID_MAGIC_ELEMENTAL;
      break;
   default:
      c->eax = ID_NONE;
   }
   
   c->return_address = 0x59F8B7;
   return NO_EXEC_DEFAULT;
}

void __fastcall updateSpellsFromArtifacts(_Hero_* hero)
{
   for (int iSpell = 0; iSpell < SPELLS_MAX; ++iSpell)
      if (hero->spell[iSpell] > 1) hero->spell[iSpell] = 0;

   for (int iSlot = 0; iSlot < 19; ++iSlot)
   {
      if (hero->doll_art[iSlot].id != ID_NONE)
      {
         if (hero->doll_art[iSlot].id == AID_SPELL_SCROLL)
         {
            if (hero->doll_art[iSlot].mod < SPELLS_NUM && !hero->spell[hero->doll_art[iSlot].mod])
               hero->spell[hero->doll_art[iSlot].mod] = 2;
         }
         else if (o_ArtInfo[hero->doll_art[iSlot].id].new_spell)
         {
            std::bitset<SPELLS_MAX> spells;
            CALL_2(int, __fastcall, 0x4D95C0, &spells, hero->doll_art[iSlot].id);

            for (std::size_t iSpell = 0; iSpell < spells.size(); ++iSpell)
            {
               if (spells[iSpell] && !hero->spell[iSpell])
                  hero->spell[iSpell] = 2;
            }

            int comboArtIndex = o_ArtInfo[hero->doll_art[iSlot].id].supercomposite;
            if (comboArtIndex != -1)
            {
               for (int iArt = 0; iArt < ARTIFACTS_NUM; ++iArt)
               {
                  if (o_ComboArtInfo[comboArtIndex].parts[iArt] && o_ArtInfo[iArt].new_spell)
                  {
                     std::bitset<SPELLS_MAX> spells;
                     CALL_2(int, __fastcall, 0x4D95C0, &spells, iArt);

                     for (std::size_t iSpell = 0; iSpell < spells.size(); ++iSpell)
                     {
                        if (spells[iSpell] && !hero->spell[iSpell])
                           hero->spell[iSpell] = 2;
                     }
                  }
               }
            }
         }
      }
   }
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
   static bool plugin_On = false;
   if ( DLL_PROCESS_ATTACH == ul_reason_for_call )
   {
      if ( !plugin_On )
      {
         plugin_On = true;
         _P = GetPatcher();
         _PI = _P->CreateInstance("HD.Plugin.H3.NewSpells");

         //GetCurrentDirectoryA(sizeof(iniPath), iniPath);
         //strcat(iniPath, "\\NewSpells.ini");

         // Moving and Expanding Spell Table
         _PI->WriteLoHook(0x4EE1C1, afterInit);
               
         // nwcthereisnospoon
         _PI->WriteByte (0x402902, SPELLS_NUM);

         // "(Already learned)" Text
         _PI->WriteDword(0x40D97C, 0x3EA);
         
         // AI
         _PI->WriteDword(0x41FBDF, 0x3EA);
         _PI->WriteByte (0x41FC91, SPELLS_NUM);
         _PI->WriteDword(0x425C72, 0x3EA);
         _PI->WriteDword(0x425E98, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x427039, 0x3EA);
         _PI->WriteDword(0x427044, 0x3EA);
         _PI->WriteByte (0x427085, SPELLS_NUM);
         _PI->WriteDword(0x4329C1, 0x3EA);
         _PI->WriteDword(0x432A43, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x432CDE, 0x3EA);
         _PI->WriteDword(0x432D1E, SPELLS_NUM * sizeof(_Spell_));
         // ------------
         // Adventure AI
         // ------------
         _PI->WriteDword(0x430AE5, 0x3EA + SPL_DIMENSION_DOOR);
         _PI->WriteDword(0x4E57CC, 0x3EA + SPL_SUMMON_BOAT);
         _PI->WriteDword(0x56B346, 0x3EA + SPL_TOWN_PORTAL);
         _PI->WriteDword(0x56B7EA, 0x3EA + SPL_DIMENSION_DOOR);
         _PI->WriteDword(0x56B93F, 0x3EA + SPL_FLY);
         _PI->WriteDword(0x56B99A, 0x3EA + SPL_WATER_WALK);
         // Battle AI
         _PI->WriteDword(0x43C6F2, SPELLS_NUM * sizeof(_Spell_));
         // Master Genie AI Spell Weighting
         _PI->WriteDword(0x43C21B, SPELLS_NUM * sizeof(_Spell_));
         // AI Quick Battle
         _PI->WriteDword(0x433026, 0x3EA);
         _PI->WriteDword(0x433719, 0x3EA);
         _PI->WriteDword(0x43940C, 0x3EA);
         _PI->WriteDword(0x43C561, 0x3EA);
         // AI Spell Scrolls?
         _PI->WriteDword(0x52A97A, 0x3EA);
         _PI->WriteDword(0x52A9B6, SPELLS_NUM * sizeof(_Spell_));
         
         // Can cast?
         _PI->WriteDword(0x447551, SPELLS_NUM * sizeof(_Spell_));
         
         _PI->WriteDword(0x447C7D, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x447CC8, SPELLS_NUM * sizeof(_Spell_));
         
         // Cheats in battle
         _PI->WriteByte (0x471C57, SPELLS_NUM);
         
         // Clear _Hero_.spell[140] (optional)
         _PI->WriteDword(0x48647A, 0x23);
         _PI->WriteJmp  (0x486485, 0x486498);
         
         _PI->WriteByte (0x4864B0, SPELLS_NUM);
         
         // Load Game?
         _PI->WriteByte (0x48A34B, SPELLS_NUM);
         
         // Scholar Secondary Skill
         _PI->WriteByte (0x4A2743, SPELLS_NUM);

         // New Game?
         _PI->WriteByte (0x4C244D, SPELLS_NUM);
         _PI->WriteByte (0x4C246F, SPELLS_NUM);
         _PI->WriteDword(0x4C24C9, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteByte (0x4C250E, SPELLS_NUM);
         _PI->WriteDword(0x4C2557, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x4C260D, SPELLS_NUM * sizeof(_Spell_));

         _PI->WriteByte (0x4E67AC, SPELLS_NUM);
         
         // Cheat Menu?
         _PI->WriteByte (0x4F508B, SPELLS_NUM);
         _PI->WriteDword(0x4F50CE, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x4F5114, SPELLS_NUM * sizeof(_Spell_));
                           
         // Pyramids
         _PI->WriteByte (0x4C170D, SPELLS_NUM);
                 
         // Shrines
         _PI->WriteDword(0x4C92C5, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x4C9347, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x4C93C0, SPELLS_NUM * sizeof(_Spell_));
         
         _PI->WriteByte (0x4CEC4F, SPELLS_NUM);
         
         // Clear _Hero_.spell[140] (optional)
         _PI->WriteDword(0x4D8F2E, 0x23);
         _PI->WriteJmp  (0x4D8F36, 0x4D8F49);
         
         _PI->WriteByte (0x4D8F53, SPELLS_NUM);
         _PI->WriteByte (0x4D8F8A, SPELLS_NUM);
         
         // Tome of Air Magic
         _PI->WriteDword(0x4D962D, SPELLS_NUM * sizeof(_Spell_));
         // Tome of Fire Magic
         _PI->WriteDword(0x4D9681, SPELLS_NUM * sizeof(_Spell_));
         // Tome of Water Magic
         _PI->WriteDword(0x4D96D6, SPELLS_NUM * sizeof(_Spell_));
         // Tome of Earth Magic
         _PI->WriteDword(0x4D972E, SPELLS_NUM * sizeof(_Spell_));
         // Spellbinder's Hat
         _PI->WriteDword(0x4D9771, SPELLS_NUM * sizeof(_Spell_));
         
         // Scholars
         _PI->WriteByte (0x5012B7, SPELLS_NUM);
         
         _PI->WriteDword(0x527ACB, 0x3EA);
         _PI->WriteDword(0x527B08, SPELLS_NUM * sizeof(_Spell_));

         // RMG
         _PI->WriteDword(0x534C4B, SPELLS_NUM * sizeof(_Spell_));
         
         // RMG Spell Scrolls
         _PI->WriteDword(0x5353D5, SPELLS_NUM);
         _PI->WriteByte (0x53542B, SPELLS_NUM);
         
         // Spell Book
         _PI->WriteDword(0x59CD5E, 0x3EA);
         _PI->WriteDword(0x59CDBF, SPELLS_NUM * sizeof(_Spell_));

         // Cast Spell
         _PI->WriteDword(0x5A1AD6, SPELLS_NUM * sizeof(_Spell_));

         // Mage Guild
         _PI->WriteByte (0x5BEA2C, SPELLS_NUM);
         _PI->WriteByte (0x5BEA70, SPELLS_NUM);
         _PI->WriteByte (0x5BEAAD, SPELLS_NUM);
         _PI->WriteDword(0x5BEAFE, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x5BEB2C, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteByte (0x5BEB48, SPELLS_NUM);
         _PI->WriteByte (0x5BEB71, SPELLS_NUM);
         _PI->WriteByte (0x5BEBB2, SPELLS_NUM);
         _PI->WriteDword(0x5BEC05, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteByte (0x5BEC1B, SPELLS_NUM);
                 
         // Conflux Grail
         _PI->WriteDword(0x5BE512, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x5BE56E, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x5D7464, SPELLS_NUM);
         
         // ---------------------------
         // New _Hero_.spell[140] field
         // ---------------------------
         _PI->WriteCodePatch(0x4D8B72, "%n", 8);
         _PI->WriteCodePatch(0x4D8F7F, "%n", 8);
         _PI->WriteCodePatch(0x4D95AF, "%n", 7);

         // ----------------------------------------
         // New _GameMgr_.disabled_spells[140] field
         // ----------------------------------------
         _PI->WriteByte(0x4C16ED, 4); // Pyramids
         _PI->WriteByte(0x4C254C, 4); // Titan's Lightning Bolt
         _PI->WriteByte(0x4C25F1, 4); // Titan's Lightning Bolt
         _PI->WriteByte(0x501297, 4); // Scholars
         _PI->WriteByte(0x5BEA55, 4); // Mage Guild
                         
         _PI->WriteDword(0x52AE1C, 0x3EA);

         // ------
         // Battle
         // ------
         _PI->WriteByte (0x4963E9, ANIMS_NUM);
         _PI->WriteByte (0x4965BD, ANIMS_NUM);
         _PI->WriteByte (0x502405, SPELLS_NUM);
         _PI->WriteByte (0x502451, SPELLS_NUM);
         _PI->WriteByte (0x5024CF, SPELLS_NUM);
         _PI->WriteByte (0x502515, SPELLS_NUM);
         _PI->WriteByte (0x502E62, SPELLS_NUM);
         _PI->WriteByte (0x502EB7, SPELLS_NUM);
         _PI->WriteByte (0x59EFD9, SPELLS_NUM - 1 - ADVSPELLS_NUM);
         _PI->WriteDword(0x59EFE4, (int)&spellIndirectTableA);
         _PI->WriteByte (0x5A064E, SPELLS_NUM - 1 - ADVSPELLS_NUM);
         _PI->WriteDword(0x5A0659, (int)&spellIndirectTableB);
         _PI->WriteByte (0x4446EA, SPELLS_NUM - 1 - SPL_SHIELD);
         _PI->WriteDword(0x4446FD, (int)&spellIndirectTableC);
         _PI->WriteByte (0x444262, SPELLS_NUM - 1 - SPL_WEAKNESS);
         _PI->WriteDword(0x44427A, (int)&spellIndirectTableD);
                 
         // --------------------------------------------------
         // New _BattleStack_.active_spell_duration[162] field
         // --------------------------------------------------
         _PI->WriteDword(0x43787A, 162);
         _PI->WriteCodePatch(0x437880, "%n", 19);
         _PI->WriteDword(0x43D314, 162);
         _PI->WriteDword(0x43E3DF, SPELLS_NUM);
         _PI->WriteByte (0x443F63, SPELLS_NUM);
         _PI->WriteByte (0x446F03, SPELLS_NUM);
         _PI->WriteByte (0x5A1907, SPELLS_NUM);
         _PI->WriteByte (0x5A1995, SPELLS_NUM);
         _PI->WriteByte (0x5A84C8, SPELLS_NUM);
         _PI->WriteByte (0x5A852F, SPELLS_NUM);
         _PI->WriteCodePatch(0x4446D2, "%n", 7);
         _PI->WriteCodePatch(0x4446D2, "%n", 7);
         _PI->WriteJmp  (0x444694, 0x4450C2);
         
         // Disable creature spells
         _PI->WriteLoHook(0x4C2567, disableCreatureSpells);

         // Shrine Spells
         _PI->WriteLoHook(0x4C2625, shrineSpellsInit);
         _PI->WriteHiHook(0x4C9260, SPLICE_, DIRECT_, THISCALL_, fillShrine);
         
         // Fear, Disease, Aging
         _PI->WriteLoHook(0x444701, applySpell);
         _PI->WriteLoHook(0x44427E, resetSpell);

         // Death Cloud
         _PI->WriteLoHook(0x5A0F4C, DeathCloud);

         // Firebird, Magic Elemental
         _PI->WriteLoHook(0x59F889, checkSummonedCreaturesType);
         _PI->WriteLoHook(0x5A065D, SummonCreatures);
         _PI->WriteLoHook(0x5A7516, setSummonedCreaturesNumber);
         
         // Artifacts (incl. Spell Scrolls)
         _PI->WriteHiHook(0x4D9840, SPLICE_, DIRECT_, THISCALL_, updateSpellsFromArtifacts);

         // Can unit attack? Seems to be optional
         _PI->WriteHiHook(0x4425A0, SPLICE_, DIRECT_, THISCALL_, unitCanAttack);
         
         // Set cursor
         _PI->WriteHiHook(0x475DC0, SPLICE_, EXTENDED_, THISCALL_, setCursor);
      }
   }

   return TRUE;
}

Добавил Aging (закл должен работать даже в паре с одноимённой абилкой, но не знаю, как будет вместе с абилкой Poison). Для Fear реализовал снижение скорости. Изменил шансы выпадения Disease: теперь не появляется в добрых городах, в Necropolis вес 10, в остальных городах - 5 ("зеркальные" веса Prayer).

Сейчас перепишу функцию 0x4425A0, и отряды под Fear не смогут никого атаковать. Я вот думаю, разработчики ясно написали, что отряд под Fear не может атаковать, но имели ли они в виду этим, что отряд также не может отвечать на атаки?

* * *
Функция 0x4425A0, оказывается, не за это отвечает. Например, с помощью неё можно разрешить стрелять в рукопашной. Поэтому пусть остаётся, вдруг понадобится. А за невозможность атаки человеком неожиданно отвечает функция выбора курсора. Запретил стрелять и атаковать врукопашную под Fear, про каст в описании заклинания ничего не сказано (но если нужно, можно и каст запретить). Запрет на атаку для AI поищу позже. Обновил код выше.
Вернуться к началу

offlineRolex  
имя: Alex
Ветеран
Ветеран
 
Сообщения: 898
Зарегистрирован: 22 сен 2020, 18:58
Откуда: УКРАИНА
Пол: Мужчина
Поблагодарили: 53 раз.

Re: Как создать плагин для HD мода

Сообщение Rolex » 01 сен 2021, 18:02

AlexSpl писал(а):

Я специально отнимаю единичку, чтобы эффект снижения скорости был заметен при любой скорости отряда, но можно легко убрать.

Пускай будет так, как есть.

AlexSpl писал(а):

Я вот думаю, разработчики ясно написали, что отряд под Fear не может атаковать, но имели ли они в виду этим, что отряд также не может отвечать на атаки?

Я думаю, что да. Сильно ли ответка отличается от атаки? Это таже атака только атаковавшего тебя отряда. Атаковать не может, а ответить может. Как-то странно, как по мне. Если нет, то по сути мы получим подобие Страха Лазурных драконов и Печали за исключением того, что здесь отряд просто может походить, но толку от этого мало.
Бешенство, например, действует не только на время атаки, а в течении 1 раунда, то есть как на время атаки, так и на время ответки. Так что, я думаю, что ответку также нужно убирать.

AlexSpl писал(а):

Запретил стрелять и атаковать врукопашную под Fear, про каст в описании заклинания ничего не сказано (но если нужно, можно и каст запретить).

Ладно с Джиннами, Чародеями и другими кастующими на своих. А как быть со Сказочными драконами? У них же каст - это атака. Для них, наверное, нужно убирать.

Изображение

1) Смертельный взгляд - 5 уровень - 30 (при развитии школы - 25) - Магия огня
У меня появилась идея перенести в заклы крутешую абилку Могучих горгон. Я придумал как ее рабить на уровни и сделать так, чтобы ее можно было наложить на свой отряд любого уровня выдерживая при этом баланс.

Для начала немного о Смертельном взгялде из ФизМига:

СМЕРТЕЛЬНЫЙ ВЗГЛЯД
При ударе каждая Могучая горгона мгновенно убивает 1 существо в целевом отряде с шансом 10%, но не более N/10, где N – количество Могучих горгон. Округление вверх. Действует только на живых. Немагическая способность.

Например, 31 Могучая горгона после атаки может дополнительно убить до 4 существ (31/10) Смертельным взглядом. Однако ключевым фактором является то, что шанс срабатывания рассчитывается для каждой горгоны отдельно, а не для всего отряда. Иначе бы 31 горгона всегда убивала 4 существ Смертельным взглядом.

Вероятность хотя бы одного успешного срабатывания способности можно рассчитать по формуле:
Код: Выделить всё
1 – (1 – p)^N

где p – шанс срабатывания Смертельного взгляда, равный 0,1 (10%), N – количество Могучих горгон.

Если 5 Могучих горгон разделить на 5 отрядов по 1 горгоне, то вероятность однократного срабатывания Смертельного взгляда такая же, как если бы они находились в одном отряде.
Из формулы можно заметить, что вероятность срабатывания всегда меньше 1. Тем не менее, это не означает, что всегда есть шанс на неудачу. При вероятностях близких к 1, в математический закон вступают особенности компьютерного округления. Причем довольно сложно предсказать, как будет округлено значение 99,99 – до 100 или до 99.

Немного теории вероятностей: шанс выпадения Смертельного взгляда i раз у N горгон подчиняется нормальному распределению по формуле:
Код: Выделить всё
P(p, N, i) = C(N, i) * p^i * (1 – p)^(N-i)

где
Код: Выделить всё
C(N, i) = N! / ((N-i)! * i!) = ЧИСЛКОМБ(N; i)


кроме шанса максимального убийства:
Код: Выделить всё
P(max) = 1 – (1-p)^N – ∑1_(i=1)^(K-1) ▒ P(p, N, i)


где K = N/10 с округлением вверх – максимальное количество убийств для данного отряда горгон. Без этого ограничения горгоны имели бы теоретический шанс убивать Смертельным взглядом отряд равный своему количеству. С учётом этого ограничения весь «хвост» вероятностей, превышающих максимум, приходится на этот максимум.

Математическое ожидание – число, показывающее, сколько в среднем убьют горгоны, если это повторять бесконечное
количество раз, рассчитывается по формуле:
Код: Выделить всё
M = K * P(max) + ∑1_(i=1)^(K-1) ▒ i * P(p, N, i)


Таблица, которая демонстрирует некоторые пункты расчёта вероятности.
Изображение

Можно заметить по таблице, что шансы возрастают с количеством горгон в отряде. Т.е. 31 горгона имеет шанс убить максимум (4 существ) с шансом 38%, а 21 горгона имеет шанс убить максимум (3 существ) с шансом 35% – на 2% меньше.

Тем не менее, иногда выгодно делить горгон на несколько отрядов. Например, 2 горгоны имеют 19% вероятность убить 1 существо Смертельным взглядом. Если разделить их на 2 отряда по 1 горгоне, то по итогу, они всё ещё имеют 19% вероятность убить 1 существо Смертельным взглядом, но на самом деле появляется 1% шанс, что каждый из отрядов убьёт по 1 существу, а оставшиеся 18% – вероятность, что, хотя бы один из отрядов убьёт 1 существо.

Ещё пример: имеется 30 Могучих горгон (вероятности см.по таблице) в одном отряде. Разделим их в 2 отряда по 15 горгон – каждый из отрядов имеет шанс убить 2 существ Смертельным взглядом. Внезапно появилась 20% (45%*45%=20%) вероятность убить взглядом сразу 4-х врагов! У вас не было этого шанса, когда все 30 Могучих Горгон находятся в 1-ом отряде. И это при том, что минимальные шансы не ухудшились. Не имеет значения как именно разделять Могучих горгон, но, очевидно, не нужно создавать больших диспропорций, скажем, 20:10.

Стоит заметить, что делить отряды горгон числом 10*N+1 не нужно. Разделение такого количества как
11, 21, 31, 41 и т.д. не имеет смысла и даже вредно.

Еще один минус от деления горгон: если полученные отряды слишком малы, их легко уложить даже
низкоуровневыми войсками противника. Нужно помнить, что помимо взгляда горгоны наносят обычный урон, и
лучше, если после удара весь отряд противника будет убит, чем бить его несколькими отрядами и получить
ответный удар, если он выживет.

Вывод: не разделяйте Могучих горгон, пока не накопите их, по крайней мере, 32 особи.

С такой изумительной особенностью, главной целью Могучих горгон являются существа 7-го уровня. Не заботьтесь даже о снятии ответа другими войсками. Могучие горгоны могут выдержать приличный удар. Если у врага 13 Архангелов, а у вас 48 Могучих горгон (к тому времени как враг накопит 13 Архангелов, у вас уже вполне может быть большее количество) – не впадайте в отчаяние. Архангелы могут этого не знать, но они уже мертвецы. Своей первой атакой Могучие горгоны, скорее всего, убьют 4 Архангела. Ответным ударом Архангелы не убьют больше, чем 9 горгон. После двух последующих атак останется 2 или 3 Архангела и от 15-20 горгон. Надо иметь в виду, что такие параметры вашего героя как Защита и/или навык Доспехи ОЧЕНЬ сильно сдвигают преимущество в сторону горгон. Атаковать существ 6-го и ниже уровней имеет смысл, только если нет более сильных. Не тратьте горгон атакуя слабых существ.
Единственной неприятностью является факт, что Смертельный взгляд не действует на существ Некрополиса, Мумий, элементалей, горгулий и големов. Поэтому в противостоянии с Некрополисом, Башней или Сопряжением вы не можете так же сильно полагаться на Могучих горгон.

-------------------------------------------
Я предлагаю сделать следущее:

Могучие горгоны у нас существа 5-го уровня. Причем, вероятно, самые сильные среди всех существ 5-го уровня. Обычно абилка соответствует базовому уровню развития школы. Как правило, на Эксперте идет двухкратный прирост относительно Базового уровня.

Таким образом я предлагаю сделать для Базового/Продвинутого/Экспертного уровня - 10%/13,33%/20%.
Что будет соответствовать:
Код: Выделить всё
N/10, 2*N/15 (или N/7.5), N/5

N - кол-во существ в дружественном отряде на который наложен закл.

Обозначим наш процент с вероятностью которого будет убиваться Смертельным взглядом 1 вражеское существо на 1 существо из нашего отряда, который будет зависеть от уровня развития школы, за P.

Таким образом 100 / P - часть от всего стека, то есть максимально возможное кол-во существ убиваемых Смертельным взглядом за один раз.

Для всех существ 5-го уровня P будет фиксированным (за исключеним Элементалей земли/магмы) и будет зависеть только от уровня развития школы.

Кол-во убивамых Смертельных взглядом существ зависит от кол-ва существ в атакующем стеке. Очевидно, что оставляя процент неизменным и накладывая этот закл на более низкоуровневых существ, чей прирост гораздо больше, мы получим имбу. А для более высокоуровневых, мы, наооборот, потеряем в эффективности, так как их прирост меньше. А потому, чтобы выйти на тот же уровень убийств, мы должны пропорционально увеличению прироста уменьшать процент и наборот.

Для этого введем коэффициент k, где k = 3 / bg. Где bg - base growth - базовый прирост существ на который накладывается закл и 3 - это базовый прирост Могучих горгон и всех существ 5-го уровня за исключением Элементалей земли/магмы (там 4).

Итого мы получим следующую формулу для максимально возможного кол-ва убиваемых Смертельным взглядом существ:
Код: Выделить всё
N * P * k / 100 или 3 * N * P / 100 * bg


Например, для существ 4-го уровня, чей базовый прирост приемущественно равен 4 (за исключением Пегасов, там 5), мы получим:
Код: Выделить всё
3 * N * P / 4 * 100


Это будет 7,5%; 10%; 15% или
N/13.33; N/10; N/6.66

Для существ же 6-го уровня, чей базовый прирост равен 2, мы получим:
Код: Выделить всё
3 * N * P / 2 * 100


Это будет 15%; 20%; 30% или
N/6.66; N/5; N/3.33

По-моему, получился бы очень крутой закл 5-го уровня.

Изображение

2) Также можно добавлять из абилки Лордов вампиров закл:
Возрождение - 5 уровень - Мана 30 (при развитии школы - 25) - Магия Земли
Basic: Восстанавливаем 50% в здоровье/сущесвтах от нанесенного отрядом урона
Advanced: Восстанавливаем 75% в здоровье/сущесвтах от нанесенного отрядом урона
Expert: Восстанавливаем 100% в здоровье/сущесвтах от нанесенного отрядом урона

Изображение Изображение

3) Вызов фей - 3 уровень - Мана 18 (при развитии школы - 15) - Магия Воздуха или Все школы магии
None: Позволяет призвать на время боя Фей. Вызов Фей не мешает вызову после этого элементалей. Кол-во призваных Фей: СМ * 8 | Для Маленьких Фей: СМ * 10;
Basic: Позволяет призвать на время боя Фей. Вызов Фей не мешает вызову после этого элементалей. Кол-во призваных Фей: СМ * 8 | Для Маленьких Фей: СМ * 10;
Advanced: Позволяет призвать на время боя Фей. Вызов Фей не мешает вызову после этого элементалей. Кол-во призваных Фей: СМ * 12 | Для Маленьких Фей: СМ * 15;
Expert: Позволяет призвать на время боя Фей. Вызов Фей не мешает вызову после этого элементалей. Кол-во призваных Фей: СМ * 16 | Для Маленьких Фей: СМ * 20;

На выбор, что-то одно (Феи или Маленькие Феи), что больше нравится.

Изображение

4) *Регенерация - 3 уровень - Мана 15 (при развитии школы - 12) - Магия Воды
None: Позволяет отряду регенирировать 50 очков здоровья перед каждым своим ходом.
Basic: Позволяет отряду регенирировать 50 очков здоровья перед каждым своим ходом.
Advanced: Позволяет отряду регенирировать 100 очков здоровья перед каждым своим ходом.
Expert: Позволяет отряду регенирировать 200 очков здоровья перед каждым своим ходом.

Регенерация является магическим аналогом природной способности Троллей восстанавливать потерянное здоровье перед ходом отряда. При этом в момент наложения заклинания здоровье не восстанавливается, идет лишь установка информации, что отряд получил эту способность. Заклинание всегда накладывается на один отряд.

* Так в MoP обстоят дела.
Я бы зделал так:
None: Позволяет отряду регенирировать 50% от потеряного здоровья перед каждым своим ходом, но не более 300.
Basic: Позволяет отряду регенирировать 50% от потеряного здоровья перед каждым своим ходом, но не более 300.
Advanced: Позволяет отряду регенирировать 75% от потеряного здоровья перед каждым своим ходом, но не более 300.
Expert: Позволяет отряду регенирировать 100% от потеряного здоровья перед каждым своим ходом, но не более 300.

(Базовое_здоровья - Текущее_(остаток)_здоровья) * 0.5/0.75/1.0

Изображение

5) Притяжение - 2 уровень - Мана 10 (при развитии школы - 8) - Магия Земли
None: Лишает вражеских существ возможности летать и игнорировать препятствия. Действует на существ 1-5 уровней.
Basic: Лишает вражеских существ возможности летать и игнорировать препятствия. Действует на существ 1-5 уровней.
Advanced: Лишает вражеских существ возможности летать и игнорировать препятствия. Действует на существ 1-6 уровней.
Expert: Лишает вражеских существ возможности летать и игнорировать препятствия. Действует на существ любого уровня.

Притяжение позволяет лишить вражеских существ способности к преодолению препятствий (флаг «Летает»). Это может быть особенно полезно при осаде города. Заклинание имеет массовое воздействие.

Изображение

6) Факел - 1 уровень - Мана 3 (при развитии школы - 2) - Магия Огня
None: Увеличивает радиус обзора героя в подземелье на 1 клетку до конца дня.
Basic: Увеличивает радиус обзора героя в подземелье на 1 клетку до конца дня.
Advanced: Увеличивает радиус обзора героя в подземелье на 2 клетки до конца дня.
Expert: Увеличивает радиус обзора героя в подземелье на 3 клетки до конца дня.

Экспертное колдовство Факела полностью компенсирует недостаток обзора тем, кто плохо видит под землей — а это все герои, кроме героев «подземных» городов — Инферно и Темницы.

Изображение

7) Вьюга - 5 уровень - Мана 25 (при развитии школы - 20) - Магия Воды
None: Наносит урон холодом выбранному отряду существ. Урон: 25 * СМ + 30
Basic: Наносит урон холодом выбранному отряду существ. Урон: 25 * СМ + 30
Advanced: Наносит урон холодом выбранному отряду существ. Урон: 25 * СМ + 60
Expert: Наносит урон холодом всем вражеским отрядам. Урон: 25 * СМ + 120
* Урон здесь можно поднимать и до 50 * СМ.

Вьюга — главное ударное заклинание Магии Воды. Наряду с Ледяной Молнией и Кольцом Холода, Вьюга входит в тройку так называемых «морозных заклинаний». Как и они, она наносит повышенный урон Огненным и Энергетическим Элементалям и, в то же время, бесполезна против Ледяных Элементалей.

Изображение

8) Сбор войск - 2 уровень - Мана 10 (при развитии школы - 8) - Магия Воздуха
None: Позволяет удаленно песетить ближайшее жилище существ, принадлежащее игроку. Действует в пределах экрана относительно героя.
Basic: Позволяет удаленно песетить ближайшее жилище существ, принадлежащее игроку. Действует в пределах экрана относительно героя.
Advanced: Позволяет по выбору удаленно песещать жилища существ, принадлежащие игроку. Действует в пределах экрана относительно героя.
Expert: Позволяет удаленно посещать любые жилища существ, принадлежащие игроку. Действует по всей карте приключений.

Сбор Войск - заклинаний, которое способно избавить героя от еженедельной беготни от одного жилища существ к другому. Теперь, с помощью Сбора Войск, это можно делать удаленно.
Экспертный Сбор Войск позволяет герою покупать существ в любом внешнем жилище под флагом родного королевства. Стоит лишь навести курсор на жилище и щелкнуть мышью.

Изображение

9) Подкрепление - 3 уровень - Мана 15 (при развитии школы - 12) - Магия Огня
None: Позволяет брать войска 1-го уровня из гарзнизона ближайшего города, принадлежащего игроку.
Basic: Позволяет брать войска 1-3 уровня из гарзнизона ближайшего города, принадлежащего игроку.
Advanced: Позволяет брать войска 1-5 уровня из гарзнизона любого города, принадлежащего игроку.
Expert: Позволяет брать войска всех уровней из гарзнизона любого города, принадлежащего игроку.

Изображение

10) Встреча героев - 5 уровень - Мана 30 (при развитии школы - 25) - Магия Огня
None: Позволяет удаленно встретиться с ближайшим свои героем
Basic: Позволяет удаленно встретиться с ближайшим свои героем
Advanced: Позволяет удаленно встретится с любым своим героем
Expert: Позволяет удаленно встретится с любым своим или союзным героем

Если Городской Портал требует перемещения героя, а Подкрепления позволяют взять из города только армию гарнизона, то Встреча Героев позволяет обмениваться всем, чем только могут обмениваться герои. При этом даже действует Грамотность.
---
В MoP есть еще Вызов светлячков - существ первого уровня (2/4/6/8 * СМ), его я заменил на Вызов Фей 3 уровня с большим кол-вом.

Кстати, в MoP через чит-меню если получать все заклы, то приходят только дефолтные. Новых, которые были добавлены непосредственно в MoP, нет. Вы же этот момент учшли. В нашем случае через чит-меню приходят все заклы, как дефолтные, так и наши новые.

Итого, у нас сейчас 8 штук заклов. Я придумал еще 3. Плюс 7 взял из MoP. Если все добавить получится 18 новых заклинаний. Дальше придумать что-то новое и интресное уже будет сложней. Да хорошую анимацию сдеать будет непросто.

Я вот нагуглил, что с помощью плагина Hex-Rays к IDA Pro можно ASM-код конвертировать в C-код. Также, вроде, есть аналоги: Hopper Disassembler, ODA (Online Disassembler), Retargetable Decompiler. То есть в теории можно большую часть кода MoP перегнать в C-код. Заклы в MoP уже написаны и прекрасно работают, их только разобрать и переписать на плюсы.

IDA Pro 7.0 + Hex-Rays Decompilers 7.0 с лекарством в комплекте можно взять ОТСЮДА.

Что-то интересное (Смертельный взгляд, Возрождение) или несложное в реализации (Вызов Фей, Регенерация, Притяжение) можно добавить и сейчас, к остальному же, наверное, лучше вернутся уже после того, как будет полностью готово уже начатое.

Картинки к заклам запаковал в архив. К первым 3 сделал сам, остальные выдрал из MoP. Плюс выдрал анимацию Притяжения и Вьюги (Регенерация и Возрождения есть в ресурсах оригинала). Все в архиве:
Вложения
New_Spell_Images_Animations.rar
(363.38 КБ) Скачиваний: 155
Последний раз редактировалось Rolex 01 сен 2021, 21:10, всего редактировалось 2 раз(а).
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5587
Зарегистрирован: 17 сен 2010, 12:58
Пол: Мужчина
Награды: 14
Высшая медаль (1) Победителю турнира по HMM1_TE (2) Победителю этапа по HMM1 (1) Победителю этапа по HMM2 (1) Лучшему из лучших (1) 2 место 1 этапа по HMM1 (1)
3 место 1 этапа по HMM1 (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (2) Победителю турнира по KB (1) Грандмастер оффлайн-турниров (1) Боевой шлем (1)
Поблагодарили: 2185 раз.

Re: Как создать плагин для HD мода

Сообщение AlexSpl » 01 сен 2021, 18:34

С большим кол-вом заклинаний серьёзно возрастает объём кода для AI. Пусть идеи тоже будут в теме, но сначала нужно допилить то, что начато :smile1:
Вернуться к началу

offlineRolex  
имя: Alex
Ветеран
Ветеран
 
Сообщения: 898
Зарегистрирован: 22 сен 2020, 18:58
Откуда: УКРАИНА
Пол: Мужчина
Поблагодарили: 53 раз.

Re: Как создать плагин для HD мода

Сообщение Rolex » 01 сен 2021, 21:44

Я вот подумал, у нас при развитии школы для Fear падает только скорость. Думаю, оптимально будет если на Basic и Advanced не будет атаки, но будет ответка, а на Expert - ни атаки, ни ответки.
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5587
Зарегистрирован: 17 сен 2010, 12:58
Пол: Мужчина
Награды: 14
Высшая медаль (1) Победителю турнира по HMM1_TE (2) Победителю этапа по HMM1 (1) Победителю этапа по HMM2 (1) Лучшему из лучших (1) 2 место 1 этапа по HMM1 (1)
3 место 1 этапа по HMM1 (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (2) Победителю турнира по KB (1) Грандмастер оффлайн-турниров (1) Боевой шлем (1)
Поблагодарили: 2185 раз.

Re: Как создать плагин для HD мода

Сообщение AlexSpl » 01 сен 2021, 21:52

1. Поправил генерацию новых заклинаний в магических гильдиях (теперь новые заклинания всегда разрешены; запрет будет в ini, а не в редакторе карт).
2. Разрешил кастовать компу Disease. Правда, функция оценки пока выглядит очень скромно:

Код: Выделить всё
int __fastcall AI_Weight_Disease(_Struct_* s, int unused_edx, _BattleStack_* stack, int a1, int a2, int a3, int a4, int a5)
{
   return 1000000;
}

Зато кастует :smile20: В готовом плагине возьму в качестве основы функцию взвешивания Prayer.

Код: Выделить всё
#define _CRT_SECURE_NO_WARNINGS
#include <bitset>
#include "..\..\HotA\homm3.h"

#define SPELLS_MAX 128
#define ANIMS_MAX 128
#define ADVSPELLS_NUM 10
#define SPECABIL_NUM 11
#define ARTIFACTS_NUM 144

#define SPELLS_NUM 89
#define SPL_FEAR_NEW 81
#define SPL_POISON_NEW 82
#define SPL_DISEASE_NEW 83
#define SPL_AGING_NEW 84
#define SPL_DEATH_CLOUD_NEW 85
#define SPL_DEATH_BLOW_NEW 86
#define SPL_FIREBIRD_NEW 87
#define SPL_MAGIC_ELEMENTAL_NEW 88

#define ANIMS_NUM 89
#define ANIM_FEAR_NEW 83
#define ANIM_POISON_NEW 84
#define ANIM_DISEASE_NEW 85
#define ANIM_AGING_NEW 86
#define ANIM_DEATH_CLOUD_NEW 87
#define ANIM_DEATH_BLOW_NEW 88

Patcher* _P;
PatcherInstance* _PI;

struct _ComboArtInfo_ {
   int index;
   std::bitset<160> parts;
};

struct _MagicAnim_ {
   char* defName;
   char* name;
   int type;
};

#define o_ComboArtInfo (*(_ComboArtInfo_**)0x660B6C)
#define o_MagicAnim ((_MagicAnim_*)0x641E18)

_Spell_ spell[SPELLS_MAX];
_MagicAnim_ anim[ANIMS_MAX];
//char iniPath[MAX_PATH];

char spellIndirectTableA[] = {
   // Spells (starting from Quicksand #10)
    0,  1,  2,  2,  3,  4,  4,  4,  4,  4,
    5,  5,  5,  5,  4,  4,  4,  4,  4,  4,
    4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
    6,  4,  4,  4,  4,  4,  4,  4,  4,  4,
    4,  4,  4,  4,  4,  4,  4,  4,  4,  5,
    4,  4,  4,  7,  8,  9, 10, 10, 10, 10,
   // Special Abilities (you cannot cast them anyway)
   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   -1,
   // New Spells (starting from Fear #81)
    4,  4,  4,  4,  5,  4, 10, 10
};

char spellIndirectTableB[] = {
   // Spells (starting from Quicksand #10)
    0,  1,  2,  3,  4,  5,  6,  7,  8,  9,
   10, 11, 12, 13, 14, 15, 16, 17, 17, 17,
   17, 17, 17, 17, 17, 18, 17, 19, 20, 20,
   21, 17, 17, 22, 17, 17, 17, 23, 17, 17,
   17, 17, 17, 17, 17, 17, 17,  7, 17, 24,
   17, 17, 17, 25, 26, 27, 28, 29, 30, 31,
   // Special Abilities
   32, 33, 34, 17, 17, 34, 37, 37, 35, 37,
   36,
   // New Spells (starting from Fear #81)
   17, 17, 17, 17, 11, 17, 38, 39
};

char spellIndirectTableC[] = {
   // Spells (starting from Shield #27)
    0,  1,  2,  3,  4,  5,  6,  7, 32,  8,
   32, 32, 32, 32,  9, 10, 11, 12, 13, 14,
   32, 15, 16, 17, 18, 19, 20, 21, 22, 23,
   32, 24, 25, 26, 27, 28, 32, 32, 32, 32,
   32, 32, 32,
   // Special Abilities
   32, 29, 32, 30, 32, 31, -1, -1, -1, -1,
   -1,
   // New Spells (starting from Fear #81)
   32, 32, 33, 32, 32, 32, 32, 32
};

char spellIndirectTableD[] = {
   // Spells (starting from Weakness #45)
    0,  1,  9,  2,  9,  9,  9,  9,  3,  4,
    9,  9,  9,  9,  9,  5,  9,  9,  9,  9,
    9,  9,  9,  9,  9,
    // Special Abilities
    9,  9,  6,  7,  9,  8, -1, -1, -1, -1,
    -1,
    // New Spells (starting from Fear #81)
    9,  9, 10,  9,  9,  9,  9,  9
};

bool shrineSpells[SPELLS_MAX];

struct FearSpellParams {
   int SpeedPenalty;
};

FearSpellParams fearSpellParams[2][21];

struct DiseaseSpellParams {
   int AttackPenalty;
   int DefensePenalty;
   int SpeedPenalty;
};

DiseaseSpellParams diseaseSpell[4];
DiseaseSpellParams diseaseSpellParams[2][21];

struct AgingSpellParams {
   double HealthPenalty;
};

AgingSpellParams agingSpellParams[2][21];

int __stdcall afterInit(LoHook* h, HookContext* c)
{
   // Spell Table
   for (int i = 0; i < SPL_FEAR_NEW; ++i)
      spell[i] = o_Spell[i];

   // Fear
   spell[SPL_FEAR_NEW].type = -1;
   spell[SPL_FEAR_NEW].wav_name = "FearRoE.wav";
   spell[SPL_FEAR_NEW].animation_ix = ANIM_FEAR_NEW;
   spell[SPL_FEAR_NEW].flags = 0x20415;
   spell[SPL_FEAR_NEW].name = "Fear";
   spell[SPL_FEAR_NEW].short_name = "Fear";
   spell[SPL_FEAR_NEW].level = 4;
   spell[SPL_FEAR_NEW].school_flags = SSF_EARTH;
   spell[SPL_FEAR_NEW].mana_cost[0] = 16;
   spell[SPL_FEAR_NEW].mana_cost[1] = 8;
   spell[SPL_FEAR_NEW].mana_cost[2] = 8;
   spell[SPL_FEAR_NEW].mana_cost[3] = 8;
   spell[SPL_FEAR_NEW].eff_power = 0;
   spell[SPL_FEAR_NEW].effect[0] = 25;
   spell[SPL_FEAR_NEW].effect[1] = 25;
   spell[SPL_FEAR_NEW].effect[2] = 50;
   spell[SPL_FEAR_NEW].effect[3] = 75;
   spell[SPL_FEAR_NEW].chance2get_var[0] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[1] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[2] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[3] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[4] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[5] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[6] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[7] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[8] = 10;
   spell[SPL_FEAR_NEW].ai_value[0] = 50;
   spell[SPL_FEAR_NEW].ai_value[1] = 50;
   spell[SPL_FEAR_NEW].ai_value[2] = 100;
   spell[SPL_FEAR_NEW].ai_value[3] = 150;
   spell[SPL_FEAR_NEW].description[0] =
      "{Fear}\n\nStrikes an enemy unit with such fear that it becomes unable to attack and nearly unable to move.\n";
   spell[SPL_FEAR_NEW].description[1] =
      "{Basic Fear}\n\nStrikes an enemy unit with such fear that it becomes unable to attack and nearly unable to move.\n\nSpell Point cost is half that of Normal Fear.\n";
   spell[SPL_FEAR_NEW].description[2] =
      "{Advanced Fear}\n\nStrikes an enemy unit with such fear that it becomes unable to attack and nearly unable to move.\n\nPenalty to movement is twice that of Basic Fear.\n";
   spell[SPL_FEAR_NEW].description[3] =
      "{Expert Fear}\n\nStrikes an enemy unit with such fear that it becomes unable to attack and nearly unable to move.\n\nPenalty to movement is three times that of Basic Fear.\n";

   // Poison
   spell[SPL_POISON_NEW].type = -1;
   spell[SPL_POISON_NEW].wav_name = "Poison.wav";
   spell[SPL_POISON_NEW].animation_ix = ANIM_POISON_NEW;
   spell[SPL_POISON_NEW].flags = 0x1015;
   spell[SPL_POISON_NEW].name = "Poison";
   spell[SPL_POISON_NEW].short_name = "Poison";
   spell[SPL_POISON_NEW].level = 4;
   spell[SPL_POISON_NEW].school_flags = SSF_EARTH;
   spell[SPL_POISON_NEW].mana_cost[0] = 20;
   spell[SPL_POISON_NEW].mana_cost[1] = 16;
   spell[SPL_POISON_NEW].mana_cost[2] = 16;
   spell[SPL_POISON_NEW].mana_cost[3] = 16;
   spell[SPL_POISON_NEW].eff_power = 0;
   spell[SPL_POISON_NEW].effect[0] = 0;
   spell[SPL_POISON_NEW].effect[1] = 0;
   spell[SPL_POISON_NEW].effect[2] = 0;
   spell[SPL_POISON_NEW].effect[3] = 0;
   spell[SPL_POISON_NEW].chance2get_var[0] = 10;
   spell[SPL_POISON_NEW].chance2get_var[1] = 10;
   spell[SPL_POISON_NEW].chance2get_var[2] = 10;
   spell[SPL_POISON_NEW].chance2get_var[3] = 10;
   spell[SPL_POISON_NEW].chance2get_var[4] = 10;
   spell[SPL_POISON_NEW].chance2get_var[5] = 10;
   spell[SPL_POISON_NEW].chance2get_var[6] = 10;
   spell[SPL_POISON_NEW].chance2get_var[7] = 10;
   spell[SPL_POISON_NEW].chance2get_var[8] = 10;
   spell[SPL_POISON_NEW].ai_value[0] = 50;
   spell[SPL_POISON_NEW].ai_value[1] = 50;
   spell[SPL_POISON_NEW].ai_value[2] = 100;
   spell[SPL_POISON_NEW].ai_value[3] = 150;
   spell[SPL_POISON_NEW].description[0] =
      "{Poison}\n\nPoison Description.\n";
   spell[SPL_POISON_NEW].description[1] =
      "{Basic Poison}\n\nBasic Poison Description.\n";
   spell[SPL_POISON_NEW].description[2] =
      "{Advanced Poison}\n\nAdvanced Poison Description.\n";
   spell[SPL_POISON_NEW].description[3] =
      "{Expert Poison}\n\nExpert Poison Description.\n";

   // Disease
   spell[SPL_DISEASE_NEW].type = -1;
   spell[SPL_DISEASE_NEW].wav_name = "Disease.wav";
   spell[SPL_DISEASE_NEW].animation_ix = ANIM_DISEASE_NEW;
   spell[SPL_DISEASE_NEW].flags = 0x40045;
   spell[SPL_DISEASE_NEW].name = "Disease";
   spell[SPL_DISEASE_NEW].short_name = "Disease";
   spell[SPL_DISEASE_NEW].level = 4;
   spell[SPL_DISEASE_NEW].school_flags = SSF_EARTH;
   spell[SPL_DISEASE_NEW].mana_cost[0] = 16;
   spell[SPL_DISEASE_NEW].mana_cost[1] = 12;
   spell[SPL_DISEASE_NEW].mana_cost[2] = 12;
   spell[SPL_DISEASE_NEW].mana_cost[3] = 12;
   spell[SPL_DISEASE_NEW].eff_power = 0;
   spell[SPL_DISEASE_NEW].effect[0] = 2;
   spell[SPL_DISEASE_NEW].effect[1] = 2;
   spell[SPL_DISEASE_NEW].effect[2] = 4;
   spell[SPL_DISEASE_NEW].effect[3] = 4;
   spell[SPL_DISEASE_NEW].chance2get_var[0] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[1] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[2] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[3] = 5;
   spell[SPL_DISEASE_NEW].chance2get_var[4] = 10;
   spell[SPL_DISEASE_NEW].chance2get_var[5] = 5;
   spell[SPL_DISEASE_NEW].chance2get_var[6] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[7] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[8] = 5;
   spell[SPL_DISEASE_NEW].ai_value[0] = 50;
   spell[SPL_DISEASE_NEW].ai_value[1] = 50;
   spell[SPL_DISEASE_NEW].ai_value[2] = 100;
   spell[SPL_DISEASE_NEW].ai_value[3] = 150;
   spell[SPL_DISEASE_NEW].description[0] =
      "{Disease}\n\nDisease Description.\n";
   spell[SPL_DISEASE_NEW].description[1] =
      "{Basic Disease}\n\nBasic Disease Description.\n";
   spell[SPL_DISEASE_NEW].description[2] =
      "{Advanced Disease}\n\nAdvanced Disease Description.\n";
   spell[SPL_DISEASE_NEW].description[3] =
      "{Expert Disease}\n\nExpert Disease Description.\n";

   diseaseSpell[0].AttackPenalty = 2;
   diseaseSpell[0].DefensePenalty = 2;
   diseaseSpell[0].SpeedPenalty = 20;
   diseaseSpell[1].AttackPenalty = 2;
   diseaseSpell[1].DefensePenalty = 2;
   diseaseSpell[1].SpeedPenalty = 20;
   diseaseSpell[2].AttackPenalty = 4;
   diseaseSpell[2].DefensePenalty = 4;
   diseaseSpell[2].SpeedPenalty = 40;
   diseaseSpell[3].AttackPenalty = 4;
   diseaseSpell[3].DefensePenalty = 4;
   diseaseSpell[3].SpeedPenalty = 40;
   
   // Aging
   spell[SPL_AGING_NEW].type = -1;
   spell[SPL_AGING_NEW].wav_name = "Age.wav";
   spell[SPL_AGING_NEW].animation_ix = ANIM_AGING_NEW;
   spell[SPL_AGING_NEW].flags = 0x1015;
   spell[SPL_AGING_NEW].name = "Aging";
   spell[SPL_AGING_NEW].short_name = "Aging";
   spell[SPL_AGING_NEW].level = 5;
   spell[SPL_AGING_NEW].school_flags = SSF_FIRE;
   spell[SPL_AGING_NEW].mana_cost[0] = 25;
   spell[SPL_AGING_NEW].mana_cost[1] = 20;
   spell[SPL_AGING_NEW].mana_cost[2] = 20;
   spell[SPL_AGING_NEW].mana_cost[3] = 20;
   spell[SPL_AGING_NEW].eff_power = 0;
   spell[SPL_AGING_NEW].effect[0] = 30;
   spell[SPL_AGING_NEW].effect[1] = 30;
   spell[SPL_AGING_NEW].effect[2] = 40;
   spell[SPL_AGING_NEW].effect[3] = 50;
   spell[SPL_AGING_NEW].chance2get_var[0] = 10;
   spell[SPL_AGING_NEW].chance2get_var[1] = 10;
   spell[SPL_AGING_NEW].chance2get_var[2] = 10;
   spell[SPL_AGING_NEW].chance2get_var[3] = 10;
   spell[SPL_AGING_NEW].chance2get_var[4] = 10;
   spell[SPL_AGING_NEW].chance2get_var[5] = 10;
   spell[SPL_AGING_NEW].chance2get_var[6] = 10;
   spell[SPL_AGING_NEW].chance2get_var[7] = 10;
   spell[SPL_AGING_NEW].chance2get_var[8] = 10;
   spell[SPL_AGING_NEW].ai_value[0] = 50;
   spell[SPL_AGING_NEW].ai_value[1] = 50;
   spell[SPL_AGING_NEW].ai_value[2] = 100;
   spell[SPL_AGING_NEW].ai_value[3] = 150;
   spell[SPL_AGING_NEW].description[0] =
      "{Aging}\n\nAging Description.\n";
   spell[SPL_AGING_NEW].description[1] =
      "{Basic Aging}\n\nBasic Aging Description.\n";
   spell[SPL_AGING_NEW].description[2] =
      "{Advanced Aging}\n\nAdvanced Aging Description.\n";
   spell[SPL_AGING_NEW].description[3] =
      "{Expert Aging}\n\nExpert Aging Description.\n";

   // Death Cloud
   spell[SPL_DEATH_CLOUD_NEW].type = 0;
   spell[SPL_DEATH_CLOUD_NEW].wav_name = "Deathcld.wav";
   spell[SPL_DEATH_CLOUD_NEW].animation_ix = ANIM_DEATH_CLOUD_NEW;
   spell[SPL_DEATH_CLOUD_NEW].flags = 0x8281;
   spell[SPL_DEATH_CLOUD_NEW].name = "Death Cloud";
   spell[SPL_DEATH_CLOUD_NEW].short_name = "Death Cloud";
   spell[SPL_DEATH_CLOUD_NEW].level = 5;
   spell[SPL_DEATH_CLOUD_NEW].school_flags = SSF_EARTH;
   spell[SPL_DEATH_CLOUD_NEW].mana_cost[0] = 25;
   spell[SPL_DEATH_CLOUD_NEW].mana_cost[1] = 20;
   spell[SPL_DEATH_CLOUD_NEW].mana_cost[2] = 20;
   spell[SPL_DEATH_CLOUD_NEW].mana_cost[3] = 20;
   spell[SPL_DEATH_CLOUD_NEW].eff_power = 40;
   spell[SPL_DEATH_CLOUD_NEW].effect[0] = 30;
   spell[SPL_DEATH_CLOUD_NEW].effect[1] = 30;
   spell[SPL_DEATH_CLOUD_NEW].effect[2] = 60;
   spell[SPL_DEATH_CLOUD_NEW].effect[3] = 120;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[0] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[1] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[2] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[3] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[4] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[5] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[6] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[7] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[8] = 10;
   spell[SPL_DEATH_CLOUD_NEW].ai_value[0] = 50;
   spell[SPL_DEATH_CLOUD_NEW].ai_value[1] = 50;
   spell[SPL_DEATH_CLOUD_NEW].ai_value[2] = 100;
   spell[SPL_DEATH_CLOUD_NEW].ai_value[3] = 150;
   spell[SPL_DEATH_CLOUD_NEW].description[0] =
      "{Death Cloud}\n\nDeath Cloud Description.\n";
   spell[SPL_DEATH_CLOUD_NEW].description[1] =
      "{Basic Death Cloud}\n\nBasic Death Cloud Description.\n";
   spell[SPL_DEATH_CLOUD_NEW].description[2] =
      "{Advanced Death Cloud}\n\nAdvanced Death Cloud Description.\n";
   spell[SPL_DEATH_CLOUD_NEW].description[3] =
      "{Expert Death Cloud}\n\nExpert Death Cloud Description.\n";

   // Death Blow
   spell[SPL_DEATH_BLOW_NEW].type = 1;
   spell[SPL_DEATH_BLOW_NEW].wav_name = "Deathblo.wav";
   spell[SPL_DEATH_BLOW_NEW].animation_ix = ANIM_DEATH_BLOW_NEW;
   spell[SPL_DEATH_BLOW_NEW].flags = 0x1015;
   spell[SPL_DEATH_BLOW_NEW].name = "Death Blow";
   spell[SPL_DEATH_BLOW_NEW].short_name = "Death Blow";
   spell[SPL_DEATH_BLOW_NEW].level = 5;
   spell[SPL_DEATH_BLOW_NEW].school_flags = SSF_FIRE;
   spell[SPL_DEATH_BLOW_NEW].mana_cost[0] = 25;
   spell[SPL_DEATH_BLOW_NEW].mana_cost[1] = 20;
   spell[SPL_DEATH_BLOW_NEW].mana_cost[2] = 20;
   spell[SPL_DEATH_BLOW_NEW].mana_cost[3] = 20;
   spell[SPL_DEATH_BLOW_NEW].eff_power = 0;
   spell[SPL_DEATH_BLOW_NEW].effect[0] = 0;
   spell[SPL_DEATH_BLOW_NEW].effect[1] = 0;
   spell[SPL_DEATH_BLOW_NEW].effect[2] = 0;
   spell[SPL_DEATH_BLOW_NEW].effect[3] = 0;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[0] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[1] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[2] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[3] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[4] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[5] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[6] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[7] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[8] = 10;
   spell[SPL_DEATH_BLOW_NEW].ai_value[0] = 50;
   spell[SPL_DEATH_BLOW_NEW].ai_value[1] = 50;
   spell[SPL_DEATH_BLOW_NEW].ai_value[2] = 100;
   spell[SPL_DEATH_BLOW_NEW].ai_value[3] = 150;
   spell[SPL_DEATH_BLOW_NEW].description[0] =
      "{Death Blow}\n\nDeath Blow Description.\n";
   spell[SPL_DEATH_BLOW_NEW].description[1] =
      "{Basic Death Blow}\n\nBasic Death Blow Description.\n";
   spell[SPL_DEATH_BLOW_NEW].description[2] =
      "{Advanced Death Blow}\n\nAdvanced Death Blow Description.\n";
   spell[SPL_DEATH_BLOW_NEW].description[3] =
      "{Expert Death Blow}\n\nExpert Death Blow Description.\n";

   // Firebird
   spell[SPL_FIREBIRD_NEW].type = 0;
   spell[SPL_FIREBIRD_NEW].wav_name = "SumnElm.wav";
   spell[SPL_FIREBIRD_NEW].animation_ix = ID_NONE;
   spell[SPL_FIREBIRD_NEW].flags = 0x80001;
   spell[SPL_FIREBIRD_NEW].name = "Summon Firebird";
   spell[SPL_FIREBIRD_NEW].short_name = "Firebird";
   spell[SPL_FIREBIRD_NEW].level = 5;
   spell[SPL_FIREBIRD_NEW].school_flags = SSF_FIRE;
   spell[SPL_FIREBIRD_NEW].mana_cost[0] = 30;
   spell[SPL_FIREBIRD_NEW].mana_cost[1] = 25;
   spell[SPL_FIREBIRD_NEW].mana_cost[2] = 25;
   spell[SPL_FIREBIRD_NEW].mana_cost[3] = 25;
   spell[SPL_FIREBIRD_NEW].eff_power = 0;
   spell[SPL_FIREBIRD_NEW].effect[0] = 50;
   spell[SPL_FIREBIRD_NEW].effect[1] = 50;
   spell[SPL_FIREBIRD_NEW].effect[2] = 75;
   spell[SPL_FIREBIRD_NEW].effect[3] = 100;
   spell[SPL_FIREBIRD_NEW].chance2get_var[0] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[1] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[2] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[3] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[4] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[5] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[6] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[7] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[8] = 10;
   spell[SPL_FIREBIRD_NEW].ai_value[0] = 50;
   spell[SPL_FIREBIRD_NEW].ai_value[1] = 50;
   spell[SPL_FIREBIRD_NEW].ai_value[2] = 100;
   spell[SPL_FIREBIRD_NEW].ai_value[3] = 150;
   spell[SPL_FIREBIRD_NEW].description[0] =
      "{Firebird}\n\nAllows you to summon firebirds.  Once cast, no other elemental types may be summoned.\n";
   spell[SPL_FIREBIRD_NEW].description[1] =
      "{Basic Firebird}\n\nAllows you to summon firebirds.  Once cast, no other elemental types may be summoned.\n";
   spell[SPL_FIREBIRD_NEW].description[2] =
      "{Advanced Firebird}\n\nAllows you to summon firebirds.  Once cast, no other elemental types may be summoned.\n\n" \
      "Summons approximately one-and-a-half times the number of units as Basic Summon Firebird.\n";
   spell[SPL_FIREBIRD_NEW].description[3] =
      "{Expert Firebird}\n\nAllows you to summon firebirds.  Once cast, no other elemental types may be summoned.\n\n" \
      "Summons approximately twice the number of units as Basic Summon Firebird.\n";

   // Magic Elemental
   spell[SPL_MAGIC_ELEMENTAL_NEW].type = 0;
   spell[SPL_MAGIC_ELEMENTAL_NEW].wav_name = "SumnElm.wav";
   spell[SPL_MAGIC_ELEMENTAL_NEW].animation_ix = ID_NONE;
   spell[SPL_MAGIC_ELEMENTAL_NEW].flags = 0x80001;
   spell[SPL_MAGIC_ELEMENTAL_NEW].name = "Summon Magic Elemental";
   spell[SPL_MAGIC_ELEMENTAL_NEW].short_name = "Magic Elemental";
   spell[SPL_MAGIC_ELEMENTAL_NEW].level = 5;
   spell[SPL_MAGIC_ELEMENTAL_NEW].school_flags = SSF_AIR | SSF_FIRE | SSF_WATER | SSF_EARTH;
   spell[SPL_MAGIC_ELEMENTAL_NEW].mana_cost[0] = 30;
   spell[SPL_MAGIC_ELEMENTAL_NEW].mana_cost[1] = 25;
   spell[SPL_MAGIC_ELEMENTAL_NEW].mana_cost[2] = 25;
   spell[SPL_MAGIC_ELEMENTAL_NEW].mana_cost[3] = 25;
   spell[SPL_MAGIC_ELEMENTAL_NEW].eff_power = 0;
   spell[SPL_MAGIC_ELEMENTAL_NEW].effect[0] = 100;
   spell[SPL_MAGIC_ELEMENTAL_NEW].effect[1] = 100;
   spell[SPL_MAGIC_ELEMENTAL_NEW].effect[2] = 150;
   spell[SPL_MAGIC_ELEMENTAL_NEW].effect[3] = 200;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[0] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[1] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[2] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[3] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[4] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[5] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[6] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[7] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[8] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].ai_value[0] = 50;
   spell[SPL_MAGIC_ELEMENTAL_NEW].ai_value[1] = 50;
   spell[SPL_MAGIC_ELEMENTAL_NEW].ai_value[2] = 100;
   spell[SPL_MAGIC_ELEMENTAL_NEW].ai_value[3] = 150;
   spell[SPL_MAGIC_ELEMENTAL_NEW].description[0] =
      "{Magic Elemental}\n\nAllows you to summon magic elementals.  Once cast, no other elemental types may be summoned.\n";
   spell[SPL_MAGIC_ELEMENTAL_NEW].description[1] =
      "{Basic Magic Elemental}\n\nAllows you to summon magic elementals.  Once cast, no other elemental types may be summoned.\n";
   spell[SPL_MAGIC_ELEMENTAL_NEW].description[2] =
      "{Advanced Magic Elemental}\n\nAllows you to summon magic elementals.  Once cast, no other elemental types may be summoned.\n\n" \
      "Summons approximately one-and-a-half times the number of units as Basic Summon Magic Elemental.\n";
   spell[SPL_MAGIC_ELEMENTAL_NEW].description[3] =
      "{Expert Magic Elemental}\n\nAllows you to summon magic elementals.  Once cast, no other elemental types may be summoned.\n\n" \
      "Summons approximately twice the number of units as Basic Summon Magic Elemental.\n";

   _PI->WriteDword(0x687FA8, (int)&spell);

   // Magic Animation Table
   for (int i = 0; i < ANIM_FEAR_NEW; ++i)
      anim[i] = o_MagicAnim[i];

   // Fear
   anim[ANIM_FEAR_NEW].defName = "C07spE0.def";
   anim[ANIM_FEAR_NEW].name = "Fear";
   anim[ANIM_FEAR_NEW].type = 0x101;

   // Poison
   anim[ANIM_POISON_NEW].defName = "sp11_.def";
   anim[ANIM_POISON_NEW].name = "Poison";
   anim[ANIM_POISON_NEW].type = 1;

   // Disease
   anim[ANIM_DISEASE_NEW].defName = "sp05_.def";
   anim[ANIM_DISEASE_NEW].name = "Disease";
   anim[ANIM_DISEASE_NEW].type = 1;

   // Aging
   anim[ANIM_AGING_NEW].defName = "sp01_.def";
   anim[ANIM_AGING_NEW].name = "Aging";
   anim[ANIM_AGING_NEW].type = 1;

   // Death Cloud
   anim[ANIM_DEATH_CLOUD_NEW].defName = "sp04_.def";
   anim[ANIM_DEATH_CLOUD_NEW].name = "DeathCloud";
   anim[ANIM_DEATH_CLOUD_NEW].type = 1;

   // Death Blow
   anim[ANIM_DEATH_BLOW_NEW].defName = "sp03_.def";
   anim[ANIM_DEATH_BLOW_NEW].name = "DeathBlow";
   anim[ANIM_DEATH_BLOW_NEW].type = 1;

   int AnimAddrDefName[] = {0x43F77E, 0x43FB6A, 0x4963FB, 0x4965CF, 0x5A5036, 0x5A6B14, 0x5A7A74, 0x5A962C};
   int AnimAddrName[] = {0x4689C4, 0x49651A, 0x4966CD, 0x5A6D2D, 0x5A7B06};
   
   for (int i = 0; i < sizeof(AnimAddrDefName) / sizeof(int); ++i)
      _PI->WriteDword(AnimAddrDefName[i], (int)&anim->defName);

   for (int i = 0; i < sizeof(AnimAddrName) / sizeof(int); ++i)
      _PI->WriteDword(AnimAddrName[i], (int)&anim->name);

   _PI->WriteDword(0x43E503, (int)&anim->type);
   
   return EXEC_DEFAULT;
}

int __stdcall skipTownSetupForNewSpells(LoHook* h, HookContext* c)
{
   int spell = c->edi;

   if (spell > SPL_AIR_ELEMENTAL)
   {
      c->return_address = 0x5BEA4C;
      return NO_EXEC_DEFAULT;
   }

   return EXEC_DEFAULT;
}

int __stdcall disableCreatureSpells(LoHook* h, HookContext* c)
{
   for (int i = SPL_FEAR_NEW; i < SPELLS_NUM; ++i)
      o_GameMgr->disabled_shrines[i] = false;

   for (int i = SPL_STONE; i <= SPL_ACID_BREATH; ++i)
      o_GameMgr->disabled_shrines[i] = true;

   return EXEC_DEFAULT;
}

int __stdcall shrineSpellsInit(LoHook* h, HookContext* c)
{
   for (int i = SPL_FEAR_NEW; i < SPELLS_NUM; ++i)
      o_GameMgr->disabled_shrines[i] = false;

   for (int i = SPL_STONE; i <= SPL_ACID_BREATH; ++i)
      o_GameMgr->disabled_shrines[i] = true;   
   
   memcpy(&shrineSpells, &o_GameMgr->disabled_shrines, sizeof(shrineSpells) / sizeof(bool));   

   c->return_address = 0x4C2641;
   return NO_EXEC_DEFAULT;
}

int __fastcall fillShrine(_GameMgr_* game, int unused_edx, int shrineFlags)
{
   int availSpellsNum = 0;

   for (int i = 0; i < SPELLS_NUM; ++i)
   {
      int lvl = o_Spell[i].level - 1;
      if (lvl < 5 && 1 << lvl & shrineFlags && o_Spell[i].school_flags && !shrineSpells[i])
         ++availSpellsNum;
   }

   int spell = 0;
   int chosenSpell = 0;

   if (availSpellsNum)
   {
      int rndSpell = Randint(0, availSpellsNum - 1);
     
      for (int i = 0; i < SPELLS_NUM; ++i)
      {
         int lvl = o_Spell[i].level - 1;
         if (lvl < 5 && 1 << lvl & shrineFlags && o_Spell[i].school_flags && !shrineSpells[i])
         {
            if (spell == rndSpell) break;
            ++spell;
         }
         ++chosenSpell;
      }
     
      shrineSpells[chosenSpell] = true;
   }
   else
   {
      spell = 0;
     
      for (int i = 0; i < SPELLS_NUM; ++i)
      {
         int lvl = o_Spell[i].level - 1;
         if (lvl < 5 && 1 << lvl & shrineFlags && o_Spell[i].school_flags && !game->disabled_shrines[i])
         {
            shrineSpells[i] = false;
            ++spell;
         }
      }
     
      chosenSpell = spell ? fillShrine(game, unused_edx, shrineFlags) : ID_NONE;
   }

   return chosenSpell;
}

// For shooters only?
bool __fastcall unitCanAttack(_BattleStack_* stack, int unused_edx, _BattleStack_* foeStack)
{
   if (!foeStack || stack == foeStack)
      return false;

   if (stack->active_spell_duration[SPL_BERSERK] || foeStack->active_spell_duration[SPL_BERSERK])
      return true;

   int side = stack->active_spell_duration[SPL_HYPNOTIZE] ? 1 - stack->side : stack->side;

   return side != foeStack->side;
}

int __stdcall setCursor(HiHook* h, _BattleMgr_* battleMgr, int hex)
{
   int cursorType = CALL_2(int, __thiscall, h->GetDefaultFunc(), battleMgr, hex);

   _BattleStack_* stack = &battleMgr->stack[battleMgr->current_side][battleMgr->current_stack_ix];

   if (stack && stack->active_spell_duration[SPL_FEAR_NEW] && (cursorType == 3 || cursorType == 15 || cursorType == 7))
      return 0;

   return cursorType;
}

int __stdcall applySpell(LoHook* h, HookContext* c)
{
   int spell = *(int*)(c->ebp + 8);
   _Hero_* hero = *(_Hero_**)(c->ebp + 0x14);
   _BattleStack_* stack = (_BattleStack_*)c->esi;
   int schoolLevel = c->eax;
   int effect = 0;
   
   switch (spell)
   {
   case SPL_FEAR_NEW:
      fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty = o_Spell[SPL_FEAR_NEW].effect[schoolLevel];

      if (!(stack->creature.flags & BCF_CANT_MOVE))
      {
         fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty =
            stack->creature.speed * fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty / 100;

         if (fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty < 1)
            fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty = 1;

         stack->creature.speed -= fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty;
         if (stack->creature.speed < 1)
         {
            fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty += stack->creature.speed - 1;
            stack->creature.speed = 1;
         }
      }

      break;

   case SPL_DISEASE_NEW:
      diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty = diseaseSpell[schoolLevel].AttackPenalty;
      diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty = diseaseSpell[schoolLevel].DefensePenalty;
      diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty = diseaseSpell[schoolLevel].SpeedPenalty; // %

      // optional, as we don't have heroes with Disease spell specialty yet
      if (hero)
      {
         effect = CALL_4(int, __thiscall, 0x4E6260, hero, SPL_DISEASE_NEW, hero->level, effect);
         diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty += effect;
         diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty += effect;
         diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty += effect; // %
      }

      stack->creature.attack -= diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty;
      stack->creature.defence -= diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty;
     
      // not allowing attack and defense to drop below 0
      if (stack->creature.attack < 0)
      {
         diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty += stack->creature.attack;
         stack->creature.attack = 0;
      }

      if (stack->creature.defence < 0)
      {
         diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty += stack->creature.defence;
         stack->creature.defence = 0;
      }

      if (!(stack->creature.flags & BCF_CANT_MOVE))
      {
         diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty =
            stack->creature.speed * diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty / 100;

         if (diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty < 1)
            diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty = 1;

         stack->creature.speed -= diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty;
         if (stack->creature.speed < 1)
         {
            diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty += stack->creature.speed - 1;
            stack->creature.speed = 1;
         }
      }
     
      break;

   case SPL_AGING:
   case SPL_AGING_NEW:
      {
      float healthMul = stack->Field<float>(0x4A4);
      double fullHealth = (double)stack->full_hp * healthMul;

      if (stack->active_spell_duration[SPL_AGING])
         fullHealth *= 0.5;

      if (stack->active_spell_duration[SPL_AGING_NEW])
      {
         agingSpellParams[stack->side][stack->index_on_side].HealthPenalty =
            1.0 - o_Spell[SPL_AGING_NEW].effect[schoolLevel] / 100.0;

         fullHealth *= agingSpellParams[stack->side][stack->index_on_side].HealthPenalty;
      }

      int health = (int)(fullHealth + 0.95);

      stack->creature.hit_points = health;

      if (health - 1 < stack->lost_hp)
         stack->lost_hp = health - 1;
      }

      break;

   default:
      return EXEC_DEFAULT;
   }
   
   c->return_address = 0x444D5C;
   return NO_EXEC_DEFAULT;
}

int __stdcall resetSpell(LoHook* h, HookContext* c)
{
   int spell = *(int*)(c->ebp + 8);
   _BattleStack_* stack = (_BattleStack_*)c->esi;

   switch (spell)
   {
   case SPL_FEAR_NEW:
      if (!(stack->creature.flags & BCF_CANT_MOVE))
         stack->creature.speed += fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty;
     
      break;

   case SPL_DISEASE_NEW:
      stack->creature.attack += diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty;
      stack->creature.defence += diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty;

      if (!(stack->creature.flags & BCF_CANT_MOVE))
         stack->creature.speed += diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty;
     
      break;
   
   case SPL_AGING:
   case SPL_AGING_NEW:
      {
      float healthMul = stack->Field<float>(0x4A4);
      double fullHealth = (double)stack->full_hp * healthMul;

      if (stack->active_spell_duration[SPL_AGING])
         fullHealth *= 0.5;

      if (stack->active_spell_duration[SPL_AGING_NEW])
         fullHealth *= agingSpellParams[stack->side][stack->index_on_side].HealthPenalty;

      int health = (int)(fullHealth + 0.95);

      stack->creature.hit_points = health;

      if (health - 1 < stack->lost_hp)
         stack->lost_hp = health - 1;
      }

      break;
   
   default:
      return EXEC_DEFAULT;
   }

   c->return_address = 0x4444E1;
   return NO_EXEC_DEFAULT;
}

// ------------------
// AI Spell Weighting
// ------------------
int __fastcall AI_Weight_Disease(_Struct_* s, int unused_edx, _BattleStack_* stack, int a1, int a2, int a3, int a4, int a5)
{
   return 1000000;
}

int __fastcall AI_Weight_Aging(_Struct_* s, int unused_edx, _BattleStack_* stack, int a1, int a2, int a3, int a4, int a5)
{
   int value;
   
   if (s->Field<char>(0x1C))
      value = 0;
   else
      value = CALL_3(int, __thiscall, 0x442B80, stack, s->Field<int>(0x20), s->Field<int>(0x24)) / 3;

   //sprintf(o_TextBuffer, "{%s}\n\n%08X %d %d %d %d", stack->creature.name_plural, s, s->Field<char>(0x1C), s->Field<int>(0x20), s->Field<int>(0x24), value);
   //b_MsgBox(o_TextBuffer, MBX_OK);

   return value;
}

int __stdcall getSpellWeight(HiHook* h, _BattleMgr_* battleMgr, int spell)
{
   switch (spell)
   {
   case SPL_DISEASE_NEW:
      return (int)AI_Weight_Disease;
      break;
   case SPL_AGING_NEW:
      return (int)AI_Weight_Aging;
      break;
   default:
      return CALL_2(int, __thiscall, h->GetDefaultFunc(), battleMgr, spell);
   }
}

int __stdcall DeathCloud(LoHook* h, HookContext* c)
{
   if (c->edx == SPL_DEATH_CLOUD_NEW)
   {
      CALL_5(void, __thiscall, 0x5A4C80, o_BattleMgr, *(int*)(c->ebp + 0xC), SPL_DEATH_CLOUD_NEW, c->esi, *(int*)(c->ebp + 0x1C));
   
      c->return_address = 0x5A2368;
      return NO_EXEC_DEFAULT;
   }

   return EXEC_DEFAULT;
}

int __stdcall SummonCreatures(LoHook* h, HookContext* c)
{
   if (c->ecx == 38 || c->ecx == 39)
   {
      int spell;
      int creature;

      switch (c->ecx)
      {
      case 38:
         spell = SPL_FIREBIRD_NEW;
         creature = CID_FIREBIRD;
         break;
      case 39:
         spell = SPL_MAGIC_ELEMENTAL_NEW;
         creature = CID_MAGIC_ELEMENTAL;
         break;
      }

      CALL_5(void, __thiscall, 0x5A7390, o_BattleMgr, spell, creature, *(int*)(c->ebp + 0x1C), c->esi);
   
      c->return_address = 0x5A2368;
      return NO_EXEC_DEFAULT;
   }

   return EXEC_DEFAULT;
}

int __stdcall setSummonedCreaturesNumber(LoHook* h, HookContext* c)
{
   int creature = *(int*)(c->ebp + 0xC);
   int sp = *(int*)(c->ebp + 0x10);
   int schoolLevel = *(int*)(c->ebp + 0x14);

   if (creature == CID_FIREBIRD)
      c->esi = o_Spell[SPL_FIREBIRD_NEW].effect[schoolLevel] * sp / 100;
   else if (creature == CID_MAGIC_ELEMENTAL)
      c->esi = o_Spell[SPL_MAGIC_ELEMENTAL_NEW].effect[schoolLevel] * sp / 100;
   else
      return EXEC_DEFAULT;

   if (c->esi < 1) c->esi = 1;
   
   c->return_address = 0x5A7526;
   return NO_EXEC_DEFAULT;
}

int __stdcall checkSummonedCreaturesType(LoHook* h, HookContext* c)
{
   int spell = c->ebx;

   switch (spell)
   {
   case SPL_FIRE_ELEMENTAL:
      c->eax = CID_FIRE_ELEMENTAL;
      break;
   case SPL_EARTH_ELEMENTAL:
      c->eax = CID_EARTH_ELEMENTAL;
      break;
   case SPL_WATER_ELEMENTAL:
      c->eax = CID_WATER_ELEMENTAL;
      break;
   case SPL_AIR_ELEMENTAL:
      c->eax = CID_AIR_ELEMENTAL;
      break;
   case SPL_FIREBIRD_NEW:
      c->eax = CID_FIREBIRD;
      break;
   case SPL_MAGIC_ELEMENTAL_NEW:
      c->eax = CID_MAGIC_ELEMENTAL;
      break;
   default:
      c->eax = ID_NONE;
   }
   
   c->return_address = 0x59F8B7;
   return NO_EXEC_DEFAULT;
}

void __fastcall updateSpellsFromArtifacts(_Hero_* hero)
{
   for (int iSpell = 0; iSpell < SPELLS_MAX; ++iSpell)
      if (hero->spell[iSpell] > 1) hero->spell[iSpell] = 0;

   for (int iSlot = 0; iSlot < 19; ++iSlot)
   {
      if (hero->doll_art[iSlot].id != ID_NONE)
      {
         if (hero->doll_art[iSlot].id == AID_SPELL_SCROLL)
         {
            if (hero->doll_art[iSlot].mod < SPELLS_NUM && !hero->spell[hero->doll_art[iSlot].mod])
               hero->spell[hero->doll_art[iSlot].mod] = 2;
         }
         else if (o_ArtInfo[hero->doll_art[iSlot].id].new_spell)
         {
            std::bitset<SPELLS_MAX> spells;
            CALL_2(int, __fastcall, 0x4D95C0, &spells, hero->doll_art[iSlot].id);

            for (std::size_t iSpell = 0; iSpell < spells.size(); ++iSpell)
            {
               if (spells[iSpell] && !hero->spell[iSpell])
                  hero->spell[iSpell] = 2;
            }

            int comboArtIndex = o_ArtInfo[hero->doll_art[iSlot].id].supercomposite;
            if (comboArtIndex != -1)
            {
               for (int iArt = 0; iArt < ARTIFACTS_NUM; ++iArt)
               {
                  if (o_ComboArtInfo[comboArtIndex].parts[iArt] && o_ArtInfo[iArt].new_spell)
                  {
                     std::bitset<SPELLS_MAX> spells;
                     CALL_2(int, __fastcall, 0x4D95C0, &spells, iArt);

                     for (std::size_t iSpell = 0; iSpell < spells.size(); ++iSpell)
                     {
                        if (spells[iSpell] && !hero->spell[iSpell])
                           hero->spell[iSpell] = 2;
                     }
                  }
               }
            }
         }
      }
   }
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
   static bool plugin_On = false;
   if ( DLL_PROCESS_ATTACH == ul_reason_for_call )
   {
      if ( !plugin_On )
      {
         plugin_On = true;
         _P = GetPatcher();
         _PI = _P->CreateInstance("HD.Plugin.H3.NewSpells");

         //GetCurrentDirectoryA(sizeof(iniPath), iniPath);
         //strcat(iniPath, "\\NewSpells.ini");

         // Moving and Expanding Spell Table
         _PI->WriteLoHook(0x4EE1C1, afterInit);
               
         // nwcthereisnospoon
         _PI->WriteByte (0x402902, SPELLS_NUM);

         // "(Already learned)" Text
         _PI->WriteDword(0x40D97C, 0x3EA);
         
         // AI
         _PI->WriteDword(0x41FBDF, 0x3EA);
         _PI->WriteByte (0x41FC91, SPELLS_NUM);
         _PI->WriteDword(0x425C72, 0x3EA);
         _PI->WriteDword(0x425E98, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x427039, 0x3EA);
         _PI->WriteDword(0x427044, 0x3EA);
         _PI->WriteByte (0x427085, SPELLS_NUM);
         _PI->WriteDword(0x4329C1, 0x3EA);
         _PI->WriteDword(0x432A43, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x432CDE, 0x3EA);
         _PI->WriteDword(0x432D1E, SPELLS_NUM * sizeof(_Spell_));
         // ------------
         // Adventure AI
         // ------------
         _PI->WriteDword(0x430AE5, 0x3EA + SPL_DIMENSION_DOOR);
         _PI->WriteDword(0x4E57CC, 0x3EA + SPL_SUMMON_BOAT);
         _PI->WriteDword(0x56B346, 0x3EA + SPL_TOWN_PORTAL);
         _PI->WriteDword(0x56B7EA, 0x3EA + SPL_DIMENSION_DOOR);
         _PI->WriteDword(0x56B93F, 0x3EA + SPL_FLY);
         _PI->WriteDword(0x56B99A, 0x3EA + SPL_WATER_WALK);
         // Battle AI
         _PI->WriteDword(0x43C6F2, SPELLS_NUM * sizeof(_Spell_));
         // Master Genie AI Spell Weighting
         _PI->WriteDword(0x43C21B, SPELLS_NUM * sizeof(_Spell_));
         // AI Quick Battle
         _PI->WriteDword(0x433026, 0x3EA);
         _PI->WriteDword(0x433719, 0x3EA);
         _PI->WriteDword(0x43940C, 0x3EA);
         _PI->WriteDword(0x43C561, 0x3EA);
         // AI Spell Scrolls?
         _PI->WriteDword(0x52A97A, 0x3EA);
         _PI->WriteDword(0x52A9B6, SPELLS_NUM * sizeof(_Spell_));
         
         // Can cast?
         _PI->WriteDword(0x447551, SPELLS_NUM * sizeof(_Spell_));
         
         _PI->WriteDword(0x447C7D, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x447CC8, SPELLS_NUM * sizeof(_Spell_));
         
         // Cheats in battle
         _PI->WriteByte (0x471C57, SPELLS_NUM);
         
         // Clear _Hero_.spell[140] (optional)
         _PI->WriteDword(0x48647A, 0x23);
         _PI->WriteJmp  (0x486485, 0x486498);
         
         _PI->WriteByte (0x4864B0, SPELLS_NUM);
         
         // Load Game?
         _PI->WriteByte (0x48A34B, SPELLS_NUM);
         
         // Scholar Secondary Skill
         _PI->WriteByte (0x4A2743, SPELLS_NUM);

         // New Game?
         _PI->WriteByte (0x4C244D, SPELLS_NUM);
         _PI->WriteByte (0x4C246F, SPELLS_NUM);
         _PI->WriteDword(0x4C24C9, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteByte (0x4C250E, SPELLS_NUM);
         _PI->WriteDword(0x4C2557, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x4C260D, SPELLS_NUM * sizeof(_Spell_));

         _PI->WriteByte (0x4E67AC, SPELLS_NUM);
         
         // Cheat Menu?
         _PI->WriteByte (0x4F508B, SPELLS_NUM);
         _PI->WriteDword(0x4F50CE, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x4F5114, SPELLS_NUM * sizeof(_Spell_));
                           
         // Pyramids
         _PI->WriteByte (0x4C170D, SPELLS_NUM);
                 
         // Shrines
         _PI->WriteDword(0x4C92C5, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x4C9347, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x4C93C0, SPELLS_NUM * sizeof(_Spell_));
         
         _PI->WriteByte (0x4CEC4F, SPELLS_NUM);
         
         // Clear _Hero_.spell[140] (optional)
         _PI->WriteDword(0x4D8F2E, 0x23);
         _PI->WriteJmp  (0x4D8F36, 0x4D8F49);
         
         _PI->WriteByte (0x4D8F53, SPELLS_NUM);
         _PI->WriteByte (0x4D8F8A, SPELLS_NUM);
         
         // Tome of Air Magic
         _PI->WriteDword(0x4D962D, SPELLS_NUM * sizeof(_Spell_));
         // Tome of Fire Magic
         _PI->WriteDword(0x4D9681, SPELLS_NUM * sizeof(_Spell_));
         // Tome of Water Magic
         _PI->WriteDword(0x4D96D6, SPELLS_NUM * sizeof(_Spell_));
         // Tome of Earth Magic
         _PI->WriteDword(0x4D972E, SPELLS_NUM * sizeof(_Spell_));
         // Spellbinder's Hat
         _PI->WriteDword(0x4D9771, SPELLS_NUM * sizeof(_Spell_));
         
         // Scholars
         _PI->WriteByte (0x5012B7, SPELLS_NUM);
         
         _PI->WriteDword(0x527ACB, 0x3EA);
         _PI->WriteDword(0x527B08, SPELLS_NUM * sizeof(_Spell_));

         // RMG
         _PI->WriteDword(0x534C4B, SPELLS_NUM * sizeof(_Spell_));
         
         // RMG Spell Scrolls
         _PI->WriteDword(0x5353D5, SPELLS_NUM);
         _PI->WriteByte (0x53542B, SPELLS_NUM);
         
         // Spell Book
         _PI->WriteDword(0x59CD5E, 0x3EA);
         _PI->WriteDword(0x59CDBF, SPELLS_NUM * sizeof(_Spell_));

         // Cast Spell
         _PI->WriteDword(0x5A1AD6, SPELLS_NUM * sizeof(_Spell_));

         // Mage Guild
         _PI->WriteByte (0x5BEA2C, SPELLS_NUM);
         _PI->WriteByte (0x5BEA70, SPELLS_NUM);
         _PI->WriteByte (0x5BEAAD, SPELLS_NUM);
         _PI->WriteDword(0x5BEAFE, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x5BEB2C, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteByte (0x5BEB48, SPELLS_NUM);
         _PI->WriteByte (0x5BEB71, SPELLS_NUM);
         _PI->WriteByte (0x5BEBB2, SPELLS_NUM);
         _PI->WriteDword(0x5BEC05, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteByte (0x5BEC1B, SPELLS_NUM);
         _PI->WriteLoHook(0x5BEA36, skipTownSetupForNewSpells); // Temp
                 
         // Conflux Grail
         _PI->WriteDword(0x5BE512, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x5BE56E, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x5D7464, SPELLS_NUM);
         
         // ---------------------------
         // New _Hero_.spell[140] field
         // ---------------------------
         _PI->WriteCodePatch(0x4D8B72, "%n", 8);
         _PI->WriteCodePatch(0x4D8F7F, "%n", 8);
         _PI->WriteCodePatch(0x4D95AF, "%n", 7);

         // ----------------------------------------
         // New _GameMgr_.disabled_spells[140] field
         // ----------------------------------------
         _PI->WriteByte(0x4C16ED, 4); // Pyramids
         _PI->WriteByte(0x4C254C, 4); // Titan's Lightning Bolt
         _PI->WriteByte(0x4C25F1, 4); // Titan's Lightning Bolt
         _PI->WriteByte(0x501297, 4); // Scholars
         _PI->WriteByte(0x5BEA55, 4); // Mage Guild
                         
         _PI->WriteDword(0x52AE1C, 0x3EA);

         // ------
         // Battle
         // ------
         _PI->WriteByte (0x4963E9, ANIMS_NUM);
         _PI->WriteByte (0x4965BD, ANIMS_NUM);
         _PI->WriteByte (0x502405, SPELLS_NUM);
         _PI->WriteByte (0x502451, SPELLS_NUM);
         _PI->WriteByte (0x5024CF, SPELLS_NUM);
         _PI->WriteByte (0x502515, SPELLS_NUM);
         _PI->WriteByte (0x502E62, SPELLS_NUM);
         _PI->WriteByte (0x502EB7, SPELLS_NUM);
         _PI->WriteByte (0x59EFD9, SPELLS_NUM - 1 - ADVSPELLS_NUM);
         _PI->WriteDword(0x59EFE4, (int)&spellIndirectTableA);
         _PI->WriteByte (0x5A064E, SPELLS_NUM - 1 - ADVSPELLS_NUM);
         _PI->WriteDword(0x5A0659, (int)&spellIndirectTableB);
         _PI->WriteByte (0x4446EA, SPELLS_NUM - 1 - SPL_SHIELD);
         _PI->WriteDword(0x4446FD, (int)&spellIndirectTableC);
         _PI->WriteByte (0x444262, SPELLS_NUM - 1 - SPL_WEAKNESS);
         _PI->WriteDword(0x44427A, (int)&spellIndirectTableD);
                 
         // --------------------------------------------------
         // New _BattleStack_.active_spell_duration[162] field
         // --------------------------------------------------
         _PI->WriteDword(0x43787A, 162);
         _PI->WriteCodePatch(0x437880, "%n", 19);
         _PI->WriteDword(0x43D314, 162);
         _PI->WriteDword(0x43E3DF, SPELLS_NUM);
         _PI->WriteByte (0x443F63, SPELLS_NUM);
         _PI->WriteByte (0x446F03, SPELLS_NUM);
         _PI->WriteByte (0x5A1907, SPELLS_NUM);
         _PI->WriteByte (0x5A1995, SPELLS_NUM);
         _PI->WriteByte (0x5A84C8, SPELLS_NUM);
         _PI->WriteByte (0x5A852F, SPELLS_NUM);
         _PI->WriteCodePatch(0x4446D2, "%n", 7);
         _PI->WriteCodePatch(0x4446D2, "%n", 7);
         _PI->WriteJmp  (0x444694, 0x4450C2);
         
         // Disable creature spells
         _PI->WriteLoHook(0x4C2567, disableCreatureSpells);

         // Shrine Spells
         _PI->WriteLoHook(0x4C2625, shrineSpellsInit);
         _PI->WriteHiHook(0x4C9260, SPLICE_, DIRECT_, THISCALL_, fillShrine);
         
         // Fear, Disease, Aging
         _PI->WriteLoHook(0x444701, applySpell);
         _PI->WriteLoHook(0x44427E, resetSpell);
         _PI->WriteHiHook(0x43B2E0, SPLICE_, EXTENDED_, THISCALL_, getSpellWeight);

         // Death Cloud
         _PI->WriteLoHook(0x5A0F4C, DeathCloud);

         // Firebird, Magic Elemental
         _PI->WriteLoHook(0x59F889, checkSummonedCreaturesType);
         _PI->WriteLoHook(0x5A065D, SummonCreatures);
         _PI->WriteLoHook(0x5A7516, setSummonedCreaturesNumber);
         
         // Artifacts (incl. Spell Scrolls)
         _PI->WriteHiHook(0x4D9840, SPLICE_, DIRECT_, THISCALL_, updateSpellsFromArtifacts);

         // Can unit attack? Seems to be optional
         _PI->WriteHiHook(0x4425A0, SPLICE_, DIRECT_, THISCALL_, unitCanAttack);
         
         // Set cursor
         _PI->WriteHiHook(0x475DC0, SPLICE_, EXTENDED_, THISCALL_, setCursor);
      }
   }

   return TRUE;
}

По аналогии с Disease можно прикрутить функции взвешивания и для других новых заклинаний здесь:

Код: Выделить всё
int __stdcall getSpellWeight(HiHook* h, _BattleMgr_* battleMgr, int spell)
{
   switch (spell)
   {
   case SPL_DISEASE_NEW:
      return (int)AI_Weight_Disease;
      break;
   default:
      return CALL_2(int, __thiscall, h->GetDefaultFunc(), battleMgr, spell);
   }
}

Но чтобы AI грамотно кастовал, естественно, нужно очень хорошо подумать над самой функцией взвешивания (сейчас бы знания Ben80 пригодились) :smile2:

* * *
UPD: Добавил оценку Aging. Оказывается, она уже была в игре. Пока для всех уровней развития Школы Огня будет одинаковой, т.к. нужно понять, что означает деление на 3. Исправил аргументы функции взвешивания (что за структура идёт в ecx, предстоит выяснить; это не _BattleMgr_).

Можете раскоментить следующий код, чтобы посмотреть на то, какие Value AI назначает отрядам, на которые думает кастовать Aging:

Код: Выделить всё
//sprintf(o_TextBuffer, "{%s}\n\n%08X %d %d %d %d", stack->creature.name_plural, s, s->Field<char>(0x1C), s->Field<int>(0x20), s->Field<int>(0x24), value);
//b_MsgBox(o_TextBuffer, MBX_OK);
Вернуться к началу

offlineRoseKavalier  
Мастер
Мастер
 
Сообщения: 331
Зарегистрирован: 23 сен 2017, 17:00
Пол: Не указан
Поблагодарили: 234 раз.

Re: Как создать плагин для HD мода

Сообщение RoseKavalier » 02 сен 2021, 03:33

There you go:
Цитата:
type_AI_spellcaster::get_enchantment_function(enum SpellID)const)(class army const *, struct type_enchant_data)const


this pointer is omitted from signature, and type_enchant_data is a copied struct with one or more fields, depending on spell requirements.
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5587
Зарегистрирован: 17 сен 2010, 12:58
Пол: Мужчина
Награды: 14
Высшая медаль (1) Победителю турнира по HMM1_TE (2) Победителю этапа по HMM1 (1) Победителю этапа по HMM2 (1) Лучшему из лучших (1) 2 место 1 этапа по HMM1 (1)
3 место 1 этапа по HMM1 (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (2) Победителю турнира по KB (1) Грандмастер оффлайн-турниров (1) Боевой шлем (1)
Поблагодарили: 2185 раз.

Re: Как создать плагин для HD мода

Сообщение AlexSpl » 02 сен 2021, 04:31

Сделал пока так:

Код: Выделить всё
#define _CRT_SECURE_NO_WARNINGS
#include <bitset>
#include "..\..\HotA\homm3.h"

#define SPELLS_MAX 128
#define ANIMS_MAX 128
#define ADVSPELLS_NUM 10
#define SPECABIL_NUM 11
#define ARTIFACTS_NUM 144

#define SPELLS_NUM 89
#define SPL_FEAR_NEW 81
#define SPL_POISON_NEW 82
#define SPL_DISEASE_NEW 83
#define SPL_AGING_NEW 84
#define SPL_DEATH_CLOUD_NEW 85
#define SPL_DEATH_BLOW_NEW 86
#define SPL_FIREBIRD_NEW 87
#define SPL_MAGIC_ELEMENTAL_NEW 88

#define ANIMS_NUM 89
#define ANIM_FEAR_NEW 83
#define ANIM_POISON_NEW 84
#define ANIM_DISEASE_NEW 85
#define ANIM_AGING_NEW 86
#define ANIM_DEATH_CLOUD_NEW 87
#define ANIM_DEATH_BLOW_NEW 88

Patcher* _P;
PatcherInstance* _PI;

struct _ComboArtInfo_ {
   int index;
   std::bitset<160> parts;
};

struct _MagicAnim_ {
   char* defName;
   char* name;
   int type;
};

#define o_ComboArtInfo (*(_ComboArtInfo_**)0x660B6C)
#define o_MagicAnim ((_MagicAnim_*)0x641E18)

_Spell_ spell[SPELLS_MAX];
_MagicAnim_ anim[ANIMS_MAX];
//char iniPath[MAX_PATH];

char spellIndirectTableA[] = {
   // Spells (starting from Quicksand #10)
    0,  1,  2,  2,  3,  4,  4,  4,  4,  4,
    5,  5,  5,  5,  4,  4,  4,  4,  4,  4,
    4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
    6,  4,  4,  4,  4,  4,  4,  4,  4,  4,
    4,  4,  4,  4,  4,  4,  4,  4,  4,  5,
    4,  4,  4,  7,  8,  9, 10, 10, 10, 10,
   // Special Abilities (you cannot cast them anyway)
   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   -1,
   // New Spells (starting from Fear #81)
    4,  4,  4,  4,  5,  4, 10, 10
};

char spellIndirectTableB[] = {
   // Spells (starting from Quicksand #10)
    0,  1,  2,  3,  4,  5,  6,  7,  8,  9,
   10, 11, 12, 13, 14, 15, 16, 17, 17, 17,
   17, 17, 17, 17, 17, 18, 17, 19, 20, 20,
   21, 17, 17, 22, 17, 17, 17, 23, 17, 17,
   17, 17, 17, 17, 17, 17, 17,  7, 17, 24,
   17, 17, 17, 25, 26, 27, 28, 29, 30, 31,
   // Special Abilities
   32, 33, 34, 17, 17, 34, 37, 37, 35, 37,
   36,
   // New Spells (starting from Fear #81)
   17, 17, 17, 17, 11, 17, 38, 39
};

char spellIndirectTableC[] = {
   // Spells (starting from Shield #27)
    0,  1,  2,  3,  4,  5,  6,  7, 32,  8,
   32, 32, 32, 32,  9, 10, 11, 12, 13, 14,
   32, 15, 16, 17, 18, 19, 20, 21, 22, 23,
   32, 24, 25, 26, 27, 28, 32, 32, 32, 32,
   32, 32, 32,
   // Special Abilities
   32, 29, 32, 30, 32, 31, -1, -1, -1, -1,
   -1,
   // New Spells (starting from Fear #81)
   32, 32, 33, 32, 32, 32, 32, 32
};

char spellIndirectTableD[] = {
   // Spells (starting from Weakness #45)
    0,  1,  9,  2,  9,  9,  9,  9,  3,  4,
    9,  9,  9,  9,  9,  5,  9,  9,  9,  9,
    9,  9,  9,  9,  9,
    // Special Abilities
    9,  9,  6,  7,  9,  8, -1, -1, -1, -1,
    -1,
    // New Spells (starting from Fear #81)
    9,  9, 10,  9,  9,  9,  9,  9
};

bool shrineSpells[SPELLS_MAX];

struct FearSpellParams {
   int SpeedPenalty;
};

FearSpellParams fearSpellParams[2][21];

struct DiseaseSpellParams {
   int AttackPenalty;
   int DefensePenalty;
   int SpeedPenalty;
};

DiseaseSpellParams diseaseSpell[4];
DiseaseSpellParams diseaseSpellParams[2][21];

struct AgingSpellParams {
   double HealthPenalty;
};

AgingSpellParams agingSpellParams[2][21];

int __stdcall afterInit(LoHook* h, HookContext* c)
{
   // Spell Table
   for (int i = 0; i < SPL_FEAR_NEW; ++i)
      spell[i] = o_Spell[i];

   // Fear
   spell[SPL_FEAR_NEW].type = -1;
   spell[SPL_FEAR_NEW].wav_name = "FearRoE.wav";
   spell[SPL_FEAR_NEW].animation_ix = ANIM_FEAR_NEW;
   spell[SPL_FEAR_NEW].flags = 0x20415;
   spell[SPL_FEAR_NEW].name = "Fear";
   spell[SPL_FEAR_NEW].short_name = "Fear";
   spell[SPL_FEAR_NEW].level = 4;
   spell[SPL_FEAR_NEW].school_flags = SSF_EARTH;
   spell[SPL_FEAR_NEW].mana_cost[0] = 16;
   spell[SPL_FEAR_NEW].mana_cost[1] = 8;
   spell[SPL_FEAR_NEW].mana_cost[2] = 8;
   spell[SPL_FEAR_NEW].mana_cost[3] = 8;
   spell[SPL_FEAR_NEW].eff_power = 0;
   spell[SPL_FEAR_NEW].effect[0] = 25;
   spell[SPL_FEAR_NEW].effect[1] = 25;
   spell[SPL_FEAR_NEW].effect[2] = 50;
   spell[SPL_FEAR_NEW].effect[3] = 75;
   spell[SPL_FEAR_NEW].chance2get_var[0] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[1] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[2] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[3] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[4] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[5] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[6] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[7] = 10;
   spell[SPL_FEAR_NEW].chance2get_var[8] = 10;
   spell[SPL_FEAR_NEW].ai_value[0] = 50;
   spell[SPL_FEAR_NEW].ai_value[1] = 50;
   spell[SPL_FEAR_NEW].ai_value[2] = 100;
   spell[SPL_FEAR_NEW].ai_value[3] = 150;
   spell[SPL_FEAR_NEW].description[0] =
      "{Fear}\n\nStrikes an enemy unit with such fear that it becomes unable to attack and nearly unable to move.\n";
   spell[SPL_FEAR_NEW].description[1] =
      "{Basic Fear}\n\nStrikes an enemy unit with such fear that it becomes unable to attack and nearly unable to move.\n\nSpell Point cost is half that of Normal Fear.\n";
   spell[SPL_FEAR_NEW].description[2] =
      "{Advanced Fear}\n\nStrikes an enemy unit with such fear that it becomes unable to attack and nearly unable to move.\n\nPenalty to movement is twice that of Basic Fear.\n";
   spell[SPL_FEAR_NEW].description[3] =
      "{Expert Fear}\n\nStrikes an enemy unit with such fear that it becomes unable to attack and nearly unable to move.\n\nPenalty to movement is three times that of Basic Fear.\n";

   // Poison
   spell[SPL_POISON_NEW].type = -1;
   spell[SPL_POISON_NEW].wav_name = "Poison.wav";
   spell[SPL_POISON_NEW].animation_ix = ANIM_POISON_NEW;
   spell[SPL_POISON_NEW].flags = 0x1015;
   spell[SPL_POISON_NEW].name = "Poison";
   spell[SPL_POISON_NEW].short_name = "Poison";
   spell[SPL_POISON_NEW].level = 4;
   spell[SPL_POISON_NEW].school_flags = SSF_EARTH;
   spell[SPL_POISON_NEW].mana_cost[0] = 20;
   spell[SPL_POISON_NEW].mana_cost[1] = 16;
   spell[SPL_POISON_NEW].mana_cost[2] = 16;
   spell[SPL_POISON_NEW].mana_cost[3] = 16;
   spell[SPL_POISON_NEW].eff_power = 0;
   spell[SPL_POISON_NEW].effect[0] = 0;
   spell[SPL_POISON_NEW].effect[1] = 0;
   spell[SPL_POISON_NEW].effect[2] = 0;
   spell[SPL_POISON_NEW].effect[3] = 0;
   spell[SPL_POISON_NEW].chance2get_var[0] = 10;
   spell[SPL_POISON_NEW].chance2get_var[1] = 10;
   spell[SPL_POISON_NEW].chance2get_var[2] = 10;
   spell[SPL_POISON_NEW].chance2get_var[3] = 10;
   spell[SPL_POISON_NEW].chance2get_var[4] = 10;
   spell[SPL_POISON_NEW].chance2get_var[5] = 10;
   spell[SPL_POISON_NEW].chance2get_var[6] = 10;
   spell[SPL_POISON_NEW].chance2get_var[7] = 10;
   spell[SPL_POISON_NEW].chance2get_var[8] = 10;
   spell[SPL_POISON_NEW].ai_value[0] = 50;
   spell[SPL_POISON_NEW].ai_value[1] = 50;
   spell[SPL_POISON_NEW].ai_value[2] = 100;
   spell[SPL_POISON_NEW].ai_value[3] = 150;
   spell[SPL_POISON_NEW].description[0] =
      "{Poison}\n\nPoison Description.\n";
   spell[SPL_POISON_NEW].description[1] =
      "{Basic Poison}\n\nBasic Poison Description.\n";
   spell[SPL_POISON_NEW].description[2] =
      "{Advanced Poison}\n\nAdvanced Poison Description.\n";
   spell[SPL_POISON_NEW].description[3] =
      "{Expert Poison}\n\nExpert Poison Description.\n";

   // Disease
   spell[SPL_DISEASE_NEW].type = -1;
   spell[SPL_DISEASE_NEW].wav_name = "Disease.wav";
   spell[SPL_DISEASE_NEW].animation_ix = ANIM_DISEASE_NEW;
   spell[SPL_DISEASE_NEW].flags = 0x40045;
   spell[SPL_DISEASE_NEW].name = "Disease";
   spell[SPL_DISEASE_NEW].short_name = "Disease";
   spell[SPL_DISEASE_NEW].level = 4;
   spell[SPL_DISEASE_NEW].school_flags = SSF_EARTH;
   spell[SPL_DISEASE_NEW].mana_cost[0] = 16;
   spell[SPL_DISEASE_NEW].mana_cost[1] = 12;
   spell[SPL_DISEASE_NEW].mana_cost[2] = 12;
   spell[SPL_DISEASE_NEW].mana_cost[3] = 12;
   spell[SPL_DISEASE_NEW].eff_power = 0;
   spell[SPL_DISEASE_NEW].effect[0] = 2;
   spell[SPL_DISEASE_NEW].effect[1] = 2;
   spell[SPL_DISEASE_NEW].effect[2] = 4;
   spell[SPL_DISEASE_NEW].effect[3] = 4;
   spell[SPL_DISEASE_NEW].chance2get_var[0] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[1] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[2] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[3] = 5;
   spell[SPL_DISEASE_NEW].chance2get_var[4] = 10;
   spell[SPL_DISEASE_NEW].chance2get_var[5] = 5;
   spell[SPL_DISEASE_NEW].chance2get_var[6] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[7] = 0;
   spell[SPL_DISEASE_NEW].chance2get_var[8] = 5;
   spell[SPL_DISEASE_NEW].ai_value[0] = 50;
   spell[SPL_DISEASE_NEW].ai_value[1] = 50;
   spell[SPL_DISEASE_NEW].ai_value[2] = 100;
   spell[SPL_DISEASE_NEW].ai_value[3] = 150;
   spell[SPL_DISEASE_NEW].description[0] =
      "{Disease}\n\nDisease Description.\n";
   spell[SPL_DISEASE_NEW].description[1] =
      "{Basic Disease}\n\nBasic Disease Description.\n";
   spell[SPL_DISEASE_NEW].description[2] =
      "{Advanced Disease}\n\nAdvanced Disease Description.\n";
   spell[SPL_DISEASE_NEW].description[3] =
      "{Expert Disease}\n\nExpert Disease Description.\n";

   diseaseSpell[0].AttackPenalty = 2;
   diseaseSpell[0].DefensePenalty = 2;
   diseaseSpell[0].SpeedPenalty = 20;
   diseaseSpell[1].AttackPenalty = 2;
   diseaseSpell[1].DefensePenalty = 2;
   diseaseSpell[1].SpeedPenalty = 20;
   diseaseSpell[2].AttackPenalty = 4;
   diseaseSpell[2].DefensePenalty = 4;
   diseaseSpell[2].SpeedPenalty = 40;
   diseaseSpell[3].AttackPenalty = 4;
   diseaseSpell[3].DefensePenalty = 4;
   diseaseSpell[3].SpeedPenalty = 40;
   
   // Aging
   spell[SPL_AGING_NEW].type = -1;
   spell[SPL_AGING_NEW].wav_name = "Age.wav";
   spell[SPL_AGING_NEW].animation_ix = ANIM_AGING_NEW;
   spell[SPL_AGING_NEW].flags = 0x1015;
   spell[SPL_AGING_NEW].name = "Aging";
   spell[SPL_AGING_NEW].short_name = "Aging";
   spell[SPL_AGING_NEW].level = 5;
   spell[SPL_AGING_NEW].school_flags = SSF_FIRE;
   spell[SPL_AGING_NEW].mana_cost[0] = 25;
   spell[SPL_AGING_NEW].mana_cost[1] = 20;
   spell[SPL_AGING_NEW].mana_cost[2] = 20;
   spell[SPL_AGING_NEW].mana_cost[3] = 20;
   spell[SPL_AGING_NEW].eff_power = 0;
   spell[SPL_AGING_NEW].effect[0] = 30;
   spell[SPL_AGING_NEW].effect[1] = 30;
   spell[SPL_AGING_NEW].effect[2] = 40;
   spell[SPL_AGING_NEW].effect[3] = 50;
   spell[SPL_AGING_NEW].chance2get_var[0] = 10;
   spell[SPL_AGING_NEW].chance2get_var[1] = 10;
   spell[SPL_AGING_NEW].chance2get_var[2] = 10;
   spell[SPL_AGING_NEW].chance2get_var[3] = 10;
   spell[SPL_AGING_NEW].chance2get_var[4] = 10;
   spell[SPL_AGING_NEW].chance2get_var[5] = 10;
   spell[SPL_AGING_NEW].chance2get_var[6] = 10;
   spell[SPL_AGING_NEW].chance2get_var[7] = 10;
   spell[SPL_AGING_NEW].chance2get_var[8] = 10;
   spell[SPL_AGING_NEW].ai_value[0] = 50;
   spell[SPL_AGING_NEW].ai_value[1] = 50;
   spell[SPL_AGING_NEW].ai_value[2] = 100;
   spell[SPL_AGING_NEW].ai_value[3] = 150;
   spell[SPL_AGING_NEW].description[0] =
      "{Aging}\n\nAging Description.\n";
   spell[SPL_AGING_NEW].description[1] =
      "{Basic Aging}\n\nBasic Aging Description.\n";
   spell[SPL_AGING_NEW].description[2] =
      "{Advanced Aging}\n\nAdvanced Aging Description.\n";
   spell[SPL_AGING_NEW].description[3] =
      "{Expert Aging}\n\nExpert Aging Description.\n";

   // Death Cloud
   spell[SPL_DEATH_CLOUD_NEW].type = 0;
   spell[SPL_DEATH_CLOUD_NEW].wav_name = "Deathcld.wav";
   spell[SPL_DEATH_CLOUD_NEW].animation_ix = ANIM_DEATH_CLOUD_NEW;
   spell[SPL_DEATH_CLOUD_NEW].flags = 0x8281;
   spell[SPL_DEATH_CLOUD_NEW].name = "Death Cloud";
   spell[SPL_DEATH_CLOUD_NEW].short_name = "Death Cloud";
   spell[SPL_DEATH_CLOUD_NEW].level = 5;
   spell[SPL_DEATH_CLOUD_NEW].school_flags = SSF_EARTH;
   spell[SPL_DEATH_CLOUD_NEW].mana_cost[0] = 25;
   spell[SPL_DEATH_CLOUD_NEW].mana_cost[1] = 20;
   spell[SPL_DEATH_CLOUD_NEW].mana_cost[2] = 20;
   spell[SPL_DEATH_CLOUD_NEW].mana_cost[3] = 20;
   spell[SPL_DEATH_CLOUD_NEW].eff_power = 40;
   spell[SPL_DEATH_CLOUD_NEW].effect[0] = 30;
   spell[SPL_DEATH_CLOUD_NEW].effect[1] = 30;
   spell[SPL_DEATH_CLOUD_NEW].effect[2] = 60;
   spell[SPL_DEATH_CLOUD_NEW].effect[3] = 120;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[0] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[1] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[2] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[3] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[4] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[5] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[6] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[7] = 10;
   spell[SPL_DEATH_CLOUD_NEW].chance2get_var[8] = 10;
   spell[SPL_DEATH_CLOUD_NEW].ai_value[0] = 50;
   spell[SPL_DEATH_CLOUD_NEW].ai_value[1] = 50;
   spell[SPL_DEATH_CLOUD_NEW].ai_value[2] = 100;
   spell[SPL_DEATH_CLOUD_NEW].ai_value[3] = 150;
   spell[SPL_DEATH_CLOUD_NEW].description[0] =
      "{Death Cloud}\n\nDeath Cloud Description.\n";
   spell[SPL_DEATH_CLOUD_NEW].description[1] =
      "{Basic Death Cloud}\n\nBasic Death Cloud Description.\n";
   spell[SPL_DEATH_CLOUD_NEW].description[2] =
      "{Advanced Death Cloud}\n\nAdvanced Death Cloud Description.\n";
   spell[SPL_DEATH_CLOUD_NEW].description[3] =
      "{Expert Death Cloud}\n\nExpert Death Cloud Description.\n";

   // Death Blow
   spell[SPL_DEATH_BLOW_NEW].type = 1;
   spell[SPL_DEATH_BLOW_NEW].wav_name = "Deathblo.wav";
   spell[SPL_DEATH_BLOW_NEW].animation_ix = ANIM_DEATH_BLOW_NEW;
   spell[SPL_DEATH_BLOW_NEW].flags = 0x1015;
   spell[SPL_DEATH_BLOW_NEW].name = "Death Blow";
   spell[SPL_DEATH_BLOW_NEW].short_name = "Death Blow";
   spell[SPL_DEATH_BLOW_NEW].level = 5;
   spell[SPL_DEATH_BLOW_NEW].school_flags = SSF_FIRE;
   spell[SPL_DEATH_BLOW_NEW].mana_cost[0] = 25;
   spell[SPL_DEATH_BLOW_NEW].mana_cost[1] = 20;
   spell[SPL_DEATH_BLOW_NEW].mana_cost[2] = 20;
   spell[SPL_DEATH_BLOW_NEW].mana_cost[3] = 20;
   spell[SPL_DEATH_BLOW_NEW].eff_power = 0;
   spell[SPL_DEATH_BLOW_NEW].effect[0] = 0;
   spell[SPL_DEATH_BLOW_NEW].effect[1] = 0;
   spell[SPL_DEATH_BLOW_NEW].effect[2] = 0;
   spell[SPL_DEATH_BLOW_NEW].effect[3] = 0;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[0] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[1] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[2] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[3] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[4] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[5] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[6] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[7] = 10;
   spell[SPL_DEATH_BLOW_NEW].chance2get_var[8] = 10;
   spell[SPL_DEATH_BLOW_NEW].ai_value[0] = 50;
   spell[SPL_DEATH_BLOW_NEW].ai_value[1] = 50;
   spell[SPL_DEATH_BLOW_NEW].ai_value[2] = 100;
   spell[SPL_DEATH_BLOW_NEW].ai_value[3] = 150;
   spell[SPL_DEATH_BLOW_NEW].description[0] =
      "{Death Blow}\n\nDeath Blow Description.\n";
   spell[SPL_DEATH_BLOW_NEW].description[1] =
      "{Basic Death Blow}\n\nBasic Death Blow Description.\n";
   spell[SPL_DEATH_BLOW_NEW].description[2] =
      "{Advanced Death Blow}\n\nAdvanced Death Blow Description.\n";
   spell[SPL_DEATH_BLOW_NEW].description[3] =
      "{Expert Death Blow}\n\nExpert Death Blow Description.\n";

   // Firebird
   spell[SPL_FIREBIRD_NEW].type = 0;
   spell[SPL_FIREBIRD_NEW].wav_name = "SumnElm.wav";
   spell[SPL_FIREBIRD_NEW].animation_ix = ID_NONE;
   spell[SPL_FIREBIRD_NEW].flags = 0x80001;
   spell[SPL_FIREBIRD_NEW].name = "Firebird";
   spell[SPL_FIREBIRD_NEW].short_name = "Summon Firebird";
   spell[SPL_FIREBIRD_NEW].level = 5;
   spell[SPL_FIREBIRD_NEW].school_flags = SSF_FIRE;
   spell[SPL_FIREBIRD_NEW].mana_cost[0] = 30;
   spell[SPL_FIREBIRD_NEW].mana_cost[1] = 25;
   spell[SPL_FIREBIRD_NEW].mana_cost[2] = 25;
   spell[SPL_FIREBIRD_NEW].mana_cost[3] = 25;
   spell[SPL_FIREBIRD_NEW].eff_power = 0;
   spell[SPL_FIREBIRD_NEW].effect[0] = 50;
   spell[SPL_FIREBIRD_NEW].effect[1] = 50;
   spell[SPL_FIREBIRD_NEW].effect[2] = 75;
   spell[SPL_FIREBIRD_NEW].effect[3] = 100;
   spell[SPL_FIREBIRD_NEW].chance2get_var[0] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[1] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[2] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[3] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[4] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[5] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[6] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[7] = 10;
   spell[SPL_FIREBIRD_NEW].chance2get_var[8] = 10;
   spell[SPL_FIREBIRD_NEW].ai_value[0] = 50;
   spell[SPL_FIREBIRD_NEW].ai_value[1] = 50;
   spell[SPL_FIREBIRD_NEW].ai_value[2] = 100;
   spell[SPL_FIREBIRD_NEW].ai_value[3] = 150;
   spell[SPL_FIREBIRD_NEW].description[0] =
      "{Summon Firebird}\n\nAllows you to summon firebirds.  Once cast, no other elemental types may be summoned.\n";
   spell[SPL_FIREBIRD_NEW].description[1] =
      "{Basic Summon Firebird}\n\nAllows you to summon firebirds.  Once cast, no other elemental types may be summoned.\n";
   spell[SPL_FIREBIRD_NEW].description[2] =
      "{Advanced Summon Firebird}\n\nAllows you to summon firebirds.  Once cast, no other elemental types may be summoned.\n\n" \
      "Summons approximately one-and-a-half times the number of units as Basic Summon Firebird.\n";
   spell[SPL_FIREBIRD_NEW].description[3] =
      "{Expert Summon Firebird}\n\nAllows you to summon firebirds.  Once cast, no other elemental types may be summoned.\n\n" \
      "Summons approximately twice the number of units as Basic Summon Firebird.\n";

   // Magic Elemental
   spell[SPL_MAGIC_ELEMENTAL_NEW].type = 0;
   spell[SPL_MAGIC_ELEMENTAL_NEW].wav_name = "SumnElm.wav";
   spell[SPL_MAGIC_ELEMENTAL_NEW].animation_ix = ID_NONE;
   spell[SPL_MAGIC_ELEMENTAL_NEW].flags = 0x80001;
   spell[SPL_MAGIC_ELEMENTAL_NEW].name = "Magic Elemental";
   spell[SPL_MAGIC_ELEMENTAL_NEW].short_name = "Summon Magic Elemental";
   spell[SPL_MAGIC_ELEMENTAL_NEW].level = 5;
   spell[SPL_MAGIC_ELEMENTAL_NEW].school_flags = SSF_AIR | SSF_FIRE | SSF_WATER | SSF_EARTH;
   spell[SPL_MAGIC_ELEMENTAL_NEW].mana_cost[0] = 30;
   spell[SPL_MAGIC_ELEMENTAL_NEW].mana_cost[1] = 25;
   spell[SPL_MAGIC_ELEMENTAL_NEW].mana_cost[2] = 25;
   spell[SPL_MAGIC_ELEMENTAL_NEW].mana_cost[3] = 25;
   spell[SPL_MAGIC_ELEMENTAL_NEW].eff_power = 0;
   spell[SPL_MAGIC_ELEMENTAL_NEW].effect[0] = 100;
   spell[SPL_MAGIC_ELEMENTAL_NEW].effect[1] = 100;
   spell[SPL_MAGIC_ELEMENTAL_NEW].effect[2] = 150;
   spell[SPL_MAGIC_ELEMENTAL_NEW].effect[3] = 200;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[0] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[1] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[2] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[3] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[4] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[5] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[6] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[7] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].chance2get_var[8] = 10;
   spell[SPL_MAGIC_ELEMENTAL_NEW].ai_value[0] = 50;
   spell[SPL_MAGIC_ELEMENTAL_NEW].ai_value[1] = 50;
   spell[SPL_MAGIC_ELEMENTAL_NEW].ai_value[2] = 100;
   spell[SPL_MAGIC_ELEMENTAL_NEW].ai_value[3] = 150;
   spell[SPL_MAGIC_ELEMENTAL_NEW].description[0] =
      "{Summon Magic Elemental}\n\nAllows you to summon magic elementals.  Once cast, no other elemental types may be summoned.\n";
   spell[SPL_MAGIC_ELEMENTAL_NEW].description[1] =
      "{Basic Summon Magic Elemental}\n\nAllows you to summon magic elementals.  Once cast, no other elemental types may be summoned.\n";
   spell[SPL_MAGIC_ELEMENTAL_NEW].description[2] =
      "{Advanced Summon Magic Elemental}\n\nAllows you to summon magic elementals.  Once cast, no other elemental types may be summoned.\n\n" \
      "Summons approximately one-and-a-half times the number of units as Basic Summon Magic Elemental.\n";
   spell[SPL_MAGIC_ELEMENTAL_NEW].description[3] =
      "{Expert Summon Magic Elemental}\n\nAllows you to summon magic elementals.  Once cast, no other elemental types may be summoned.\n\n" \
      "Summons approximately twice the number of units as Basic Summon Magic Elemental.\n";

   _PI->WriteDword(0x687FA8, (int)&spell);

   // Magic Animation Table
   for (int i = 0; i < ANIM_FEAR_NEW; ++i)
      anim[i] = o_MagicAnim[i];

   // Fear
   anim[ANIM_FEAR_NEW].defName = "C07spE0.def";
   anim[ANIM_FEAR_NEW].name = "Fear";
   anim[ANIM_FEAR_NEW].type = 0x101;

   // Poison
   anim[ANIM_POISON_NEW].defName = "sp11_.def";
   anim[ANIM_POISON_NEW].name = "Poison";
   anim[ANIM_POISON_NEW].type = 1;

   // Disease
   anim[ANIM_DISEASE_NEW].defName = "sp05_.def";
   anim[ANIM_DISEASE_NEW].name = "Disease";
   anim[ANIM_DISEASE_NEW].type = 1;

   // Aging
   anim[ANIM_AGING_NEW].defName = "sp01_.def";
   anim[ANIM_AGING_NEW].name = "Aging";
   anim[ANIM_AGING_NEW].type = 1;

   // Death Cloud
   anim[ANIM_DEATH_CLOUD_NEW].defName = "sp04_.def";
   anim[ANIM_DEATH_CLOUD_NEW].name = "DeathCloud";
   anim[ANIM_DEATH_CLOUD_NEW].type = 1;

   // Death Blow
   anim[ANIM_DEATH_BLOW_NEW].defName = "sp03_.def";
   anim[ANIM_DEATH_BLOW_NEW].name = "DeathBlow";
   anim[ANIM_DEATH_BLOW_NEW].type = 1;

   int AnimAddrDefName[] = {0x43F77E, 0x43FB6A, 0x4963FB, 0x4965CF, 0x5A5036, 0x5A6B14, 0x5A7A74, 0x5A962C};
   int AnimAddrName[] = {0x4689C4, 0x49651A, 0x4966CD, 0x5A6D2D, 0x5A7B06};
   
   for (int i = 0; i < sizeof(AnimAddrDefName) / sizeof(int); ++i)
      _PI->WriteDword(AnimAddrDefName[i], (int)&anim->defName);

   for (int i = 0; i < sizeof(AnimAddrName) / sizeof(int); ++i)
      _PI->WriteDword(AnimAddrName[i], (int)&anim->name);

   _PI->WriteDword(0x43E503, (int)&anim->type);
   
   return EXEC_DEFAULT;
}

int __stdcall skipTownSetupForNewSpells(LoHook* h, HookContext* c)
{
   int spell = c->edi;

   if (spell > SPL_AIR_ELEMENTAL)
   {
      c->return_address = 0x5BEA4C;
      return NO_EXEC_DEFAULT;
   }

   return EXEC_DEFAULT;
}

int __stdcall disableCreatureSpells(LoHook* h, HookContext* c)
{
   for (int i = SPL_FEAR_NEW; i < SPELLS_NUM; ++i)
      o_GameMgr->disabled_shrines[i] = false;

   for (int i = SPL_STONE; i <= SPL_ACID_BREATH; ++i)
      o_GameMgr->disabled_shrines[i] = true;

   return EXEC_DEFAULT;
}

int __stdcall shrineSpellsInit(LoHook* h, HookContext* c)
{
   for (int i = SPL_FEAR_NEW; i < SPELLS_NUM; ++i)
      o_GameMgr->disabled_shrines[i] = false;

   for (int i = SPL_STONE; i <= SPL_ACID_BREATH; ++i)
      o_GameMgr->disabled_shrines[i] = true;   
   
   memcpy(&shrineSpells, &o_GameMgr->disabled_shrines, sizeof(shrineSpells) / sizeof(bool));   

   c->return_address = 0x4C2641;
   return NO_EXEC_DEFAULT;
}

int __fastcall fillShrine(_GameMgr_* game, int unused_edx, int shrineFlags)
{
   int availSpellsNum = 0;

   for (int i = 0; i < SPELLS_NUM; ++i)
   {
      int lvl = o_Spell[i].level - 1;
      if (lvl < 5 && 1 << lvl & shrineFlags && o_Spell[i].school_flags && !shrineSpells[i])
         ++availSpellsNum;
   }

   int spell = 0;
   int chosenSpell = 0;

   if (availSpellsNum)
   {
      int rndSpell = Randint(0, availSpellsNum - 1);
     
      for (int i = 0; i < SPELLS_NUM; ++i)
      {
         int lvl = o_Spell[i].level - 1;
         if (lvl < 5 && 1 << lvl & shrineFlags && o_Spell[i].school_flags && !shrineSpells[i])
         {
            if (spell == rndSpell) break;
            ++spell;
         }
         ++chosenSpell;
      }
     
      shrineSpells[chosenSpell] = true;
   }
   else
   {
      spell = 0;
     
      for (int i = 0; i < SPELLS_NUM; ++i)
      {
         int lvl = o_Spell[i].level - 1;
         if (lvl < 5 && 1 << lvl & shrineFlags && o_Spell[i].school_flags && !game->disabled_shrines[i])
         {
            shrineSpells[i] = false;
            ++spell;
         }
      }
     
      chosenSpell = spell ? fillShrine(game, unused_edx, shrineFlags) : ID_NONE;
   }

   return chosenSpell;
}

// For shooters only?
bool __fastcall unitCanAttack(_BattleStack_* stack, int unused_edx, _BattleStack_* foeStack)
{
   if (!foeStack || stack == foeStack)
      return false;

   if (stack->active_spell_duration[SPL_BERSERK] || foeStack->active_spell_duration[SPL_BERSERK])
      return true;

   int side = stack->active_spell_duration[SPL_HYPNOTIZE] ? 1 - stack->side : stack->side;

   return side != foeStack->side;
}

int __stdcall setCursorForFearSpell(HiHook* h, _BattleMgr_* battleMgr, int hex)
{
   int cursorType = CALL_2(int, __thiscall, h->GetDefaultFunc(), battleMgr, hex);

   _BattleStack_* stack = &battleMgr->stack[battleMgr->current_side][battleMgr->current_stack_ix];

   if (stack && stack->active_spell_duration[SPL_FEAR_NEW] && (cursorType == 3 || cursorType == 15 || cursorType == 7))
      return 0;

   return cursorType;
}

int __stdcall applySpell(LoHook* h, HookContext* c)
{
   int spell = *(int*)(c->ebp + 8);
   _Hero_* hero = *(_Hero_**)(c->ebp + 0x14);
   _BattleStack_* stack = (_BattleStack_*)c->esi;
   int schoolLevel = c->eax;
   int effect = 0;
   
   switch (spell)
   {
   case SPL_FEAR_NEW:
      fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty = o_Spell[SPL_FEAR_NEW].effect[schoolLevel];

      if (!(stack->creature.flags & BCF_CANT_MOVE))
      {
         fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty =
            stack->creature.speed * fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty / 100;

         if (fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty < 1)
            fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty = 1;

         stack->creature.speed -= fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty;
         if (stack->creature.speed < 1)
         {
            fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty += stack->creature.speed - 1;
            stack->creature.speed = 1;
         }
      }

      break;

   case SPL_DISEASE_NEW:
      diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty = diseaseSpell[schoolLevel].AttackPenalty;
      diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty = diseaseSpell[schoolLevel].DefensePenalty;
      diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty = diseaseSpell[schoolLevel].SpeedPenalty; // %

      // optional, as we don't have heroes with Disease spell specialty yet
      if (hero)
      {
         effect = CALL_4(int, __thiscall, 0x4E6260, hero, SPL_DISEASE_NEW, hero->level, effect);
         diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty += effect;
         diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty += effect;
         diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty += effect; // %
      }

      stack->creature.attack -= diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty;
      stack->creature.defence -= diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty;
     
      // not allowing attack and defense to drop below 0
      if (stack->creature.attack < 0)
      {
         diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty += stack->creature.attack;
         stack->creature.attack = 0;
      }

      if (stack->creature.defence < 0)
      {
         diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty += stack->creature.defence;
         stack->creature.defence = 0;
      }

      if (!(stack->creature.flags & BCF_CANT_MOVE))
      {
         diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty =
            stack->creature.speed * diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty / 100;

         if (diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty < 1)
            diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty = 1;

         stack->creature.speed -= diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty;
         if (stack->creature.speed < 1)
         {
            diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty += stack->creature.speed - 1;
            stack->creature.speed = 1;
         }
      }
     
      break;

   case SPL_AGING:
   case SPL_AGING_NEW:
      {
      float healthMul = stack->Field<float>(0x4A4);
      double fullHealth = (double)stack->full_hp * healthMul;

      if (stack->active_spell_duration[SPL_AGING])
         fullHealth *= 0.5;

      if (stack->active_spell_duration[SPL_AGING_NEW])
      {
         agingSpellParams[stack->side][stack->index_on_side].HealthPenalty =
            1.0 - o_Spell[SPL_AGING_NEW].effect[schoolLevel] / 100.0;

         fullHealth *= agingSpellParams[stack->side][stack->index_on_side].HealthPenalty;
      }

      int health = (int)(fullHealth + 0.95);

      stack->creature.hit_points = health;

      if (health - 1 < stack->lost_hp)
         stack->lost_hp = health - 1;
      }

      break;

   default:
      return EXEC_DEFAULT;
   }
   
   c->return_address = 0x444D5C;
   return NO_EXEC_DEFAULT;
}

int __stdcall resetSpell(LoHook* h, HookContext* c)
{
   int spell = *(int*)(c->ebp + 8);
   _BattleStack_* stack = (_BattleStack_*)c->esi;

   switch (spell)
   {
   case SPL_FEAR_NEW:
      if (!(stack->creature.flags & BCF_CANT_MOVE))
         stack->creature.speed += fearSpellParams[stack->side][stack->index_on_side].SpeedPenalty;
     
      break;

   case SPL_DISEASE_NEW:
      stack->creature.attack += diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty;
      stack->creature.defence += diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty;

      if (!(stack->creature.flags & BCF_CANT_MOVE))
         stack->creature.speed += diseaseSpellParams[stack->side][stack->index_on_side].SpeedPenalty;
     
      break;
   
   case SPL_AGING:
   case SPL_AGING_NEW:
      {
      float healthMul = stack->Field<float>(0x4A4);
      double fullHealth = (double)stack->full_hp * healthMul;

      if (stack->active_spell_duration[SPL_AGING])
         fullHealth *= 0.5;

      if (stack->active_spell_duration[SPL_AGING_NEW])
         fullHealth *= agingSpellParams[stack->side][stack->index_on_side].HealthPenalty;

      int health = (int)(fullHealth + 0.95);

      stack->creature.hit_points = health;

      if (health - 1 < stack->lost_hp)
         stack->lost_hp = health - 1;
      }

      break;
   
   default:
      return EXEC_DEFAULT;
   }

   c->return_address = 0x4444E1;
   return NO_EXEC_DEFAULT;
}

float __stdcall getStackMagicVulnerability(HiHook* h, int spell, int creatureId, _Hero_* defHero, _Hero_* attHero)
{
   int spellToCheck = spell;
   if (spell == SPL_POISON_NEW) spellToCheck = SPL_POISON;

   float stackMagicVulnerability = CALL_4(float, __fastcall, h->GetDefaultFunc(), spellToCheck, creatureId, defHero, attHero);
   _CreatureInfo_* creature = &o_pCreatureInfo[creatureId];

   // (!) For the following spells only (don't put other spells here)
   if (stackMagicVulnerability > 0.0f)
   switch (spell)
   {
   case SPL_FEAR_NEW:
      if (creature->flags & BCF_UNDEAD)
         stackMagicVulnerability = 0.0f;
      break;
   case SPL_DISEASE_NEW:
      if (creature->flags & BCF_UNDEAD)
         stackMagicVulnerability = 0.0f;
      break;
   case SPL_AGING_NEW:
      if (creature->flags & BCF_UNDEAD)
         stackMagicVulnerability = 0.0f;
      break;
   }

   return stackMagicVulnerability;
}

// ------------------
// AI Spell Weighting
// ------------------
struct type_AI_spellcaster : _Struct_
{
};

struct type_enchant_data : _Struct_
{
   int a1;
   int a2;
   int a3;
   int a4;
   int a5;
};

int get_total_combat_value(_BattleStack_* stack, int a1, int a2)
{
   return CALL_3(int, __thiscall, 0x442B80, stack, a1, a2);
}

int __fastcall AI_Weight_Disease(type_AI_spellcaster* caster, int unused_edx, _BattleStack_* stack, type_enchant_data data)
{
   return 1000000;
}

int __fastcall AI_Weight_Aging(type_AI_spellcaster* caster, int unused_edx, _BattleStack_* stack, type_enchant_data data)
{
   int value;
   
   if (caster->Field<char>(0x1C))
      value = 0;
   else
      value = get_total_combat_value(stack, caster->Field<int>(0x20), caster->Field<int>(0x24)) / 3;

   //sprintf(o_TextBuffer, "{%s}\n\n%08X %d %d %d %d", stack->creature.name_plural, caster,
   //     caster->Field<char>(0x1C), caster->Field<int>(0x20), caster->Field<int>(0x24), value);
   //b_MsgBox(o_TextBuffer, MBX_OK);

   return value;
}

int __stdcall getSpellWeight(HiHook* h, _BattleMgr_* battleMgr, int spell)
{
   switch (spell)
   {
   case SPL_DISEASE_NEW:
      return (int)AI_Weight_Disease;
      break;
   case SPL_AGING_NEW:
      return (int)AI_Weight_Aging;
      break;
   default:
      return CALL_2(int, __thiscall, h->GetDefaultFunc(), battleMgr, spell);
   }
}

int __stdcall DeathCloud(LoHook* h, HookContext* c)
{
   if (c->edx == SPL_DEATH_CLOUD_NEW)
   {
      CALL_5(void, __thiscall, 0x5A4C80, o_BattleMgr, *(int*)(c->ebp + 0xC), SPL_DEATH_CLOUD_NEW, c->esi, *(int*)(c->ebp + 0x1C));
   
      c->return_address = 0x5A2368;
      return NO_EXEC_DEFAULT;
   }

   return EXEC_DEFAULT;
}

int __stdcall SummonCreatures(LoHook* h, HookContext* c)
{
   if (c->ecx == 38 || c->ecx == 39)
   {
      int spell;
      int creature;

      switch (c->ecx)
      {
      case 38:
         spell = SPL_FIREBIRD_NEW;
         creature = CID_FIREBIRD;
         break;
      case 39:
         spell = SPL_MAGIC_ELEMENTAL_NEW;
         creature = CID_MAGIC_ELEMENTAL;
         break;
      }

      CALL_5(void, __thiscall, 0x5A7390, o_BattleMgr, spell, creature, *(int*)(c->ebp + 0x1C), c->esi);
   
      c->return_address = 0x5A2368;
      return NO_EXEC_DEFAULT;
   }

   return EXEC_DEFAULT;
}

int __stdcall setSummonedCreaturesNumber(LoHook* h, HookContext* c)
{
   int creature = *(int*)(c->ebp + 0xC);
   int sp = *(int*)(c->ebp + 0x10);
   int schoolLevel = *(int*)(c->ebp + 0x14);

   if (creature == CID_FIREBIRD)
      c->esi = o_Spell[SPL_FIREBIRD_NEW].effect[schoolLevel] * sp / 100;
   else if (creature == CID_MAGIC_ELEMENTAL)
      c->esi = o_Spell[SPL_MAGIC_ELEMENTAL_NEW].effect[schoolLevel] * sp / 100;
   else
      return EXEC_DEFAULT;

   if (c->esi < 1) c->esi = 1;
   
   c->return_address = 0x5A7526;
   return NO_EXEC_DEFAULT;
}

int __stdcall checkSummonedCreaturesType(LoHook* h, HookContext* c)
{
   int spell = c->ebx;

   switch (spell)
   {
   case SPL_FIRE_ELEMENTAL:
      c->eax = CID_FIRE_ELEMENTAL;
      break;
   case SPL_EARTH_ELEMENTAL:
      c->eax = CID_EARTH_ELEMENTAL;
      break;
   case SPL_WATER_ELEMENTAL:
      c->eax = CID_WATER_ELEMENTAL;
      break;
   case SPL_AIR_ELEMENTAL:
      c->eax = CID_AIR_ELEMENTAL;
      break;
   case SPL_FIREBIRD_NEW:
      c->eax = CID_FIREBIRD;
      break;
   case SPL_MAGIC_ELEMENTAL_NEW:
      c->eax = CID_MAGIC_ELEMENTAL;
      break;
   default:
      c->eax = ID_NONE;
   }
   
   c->return_address = 0x59F8B7;
   return NO_EXEC_DEFAULT;
}

int __stdcall skipMeleeAttackUnderFear(LoHook* h, HookContext* c)
{
   _BattleStack_* stack = o_BattleMgr->GetCurrentStack();
   
   if (stack && stack->active_spell_duration[SPL_FEAR_NEW])
   {
      c->return_address = 0x4220B3;
      return NO_EXEC_DEFAULT;
   }

   return EXEC_DEFAULT;
}

int __stdcall skipShootingUnderFear(LoHook* h, HookContext* c)
{
   _BattleStack_* stack = o_BattleMgr->GetCurrentStack();
   
   if (stack && stack->active_spell_duration[SPL_FEAR_NEW])
   {
      c->return_address = 0x41F263;
      return NO_EXEC_DEFAULT;
   }

   return EXEC_DEFAULT;
}

void __fastcall updateSpellsFromArtifacts(_Hero_* hero)
{
   for (int iSpell = 0; iSpell < SPELLS_MAX; ++iSpell)
      if (hero->spell[iSpell] > 1) hero->spell[iSpell] = 0;

   for (int iSlot = 0; iSlot < 19; ++iSlot)
   {
      if (hero->doll_art[iSlot].id != ID_NONE)
      {
         if (hero->doll_art[iSlot].id == AID_SPELL_SCROLL)
         {
            if (hero->doll_art[iSlot].mod < SPELLS_NUM && !hero->spell[hero->doll_art[iSlot].mod])
               hero->spell[hero->doll_art[iSlot].mod] = 2;
         }
         else if (o_ArtInfo[hero->doll_art[iSlot].id].new_spell)
         {
            std::bitset<SPELLS_MAX> spells;
            CALL_2(int, __fastcall, 0x4D95C0, &spells, hero->doll_art[iSlot].id);

            for (std::size_t iSpell = 0; iSpell < spells.size(); ++iSpell)
            {
               if (spells[iSpell] && !hero->spell[iSpell])
                  hero->spell[iSpell] = 2;
            }

            int comboArtIndex = o_ArtInfo[hero->doll_art[iSlot].id].supercomposite;
            if (comboArtIndex != -1)
            {
               for (int iArt = 0; iArt < ARTIFACTS_NUM; ++iArt)
               {
                  if (o_ComboArtInfo[comboArtIndex].parts[iArt] && o_ArtInfo[iArt].new_spell)
                  {
                     std::bitset<SPELLS_MAX> spells;
                     CALL_2(int, __fastcall, 0x4D95C0, &spells, iArt);

                     for (std::size_t iSpell = 0; iSpell < spells.size(); ++iSpell)
                     {
                        if (spells[iSpell] && !hero->spell[iSpell])
                           hero->spell[iSpell] = 2;
                     }
                  }
               }
            }
         }
      }
   }
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
   static bool plugin_On = false;
   if ( DLL_PROCESS_ATTACH == ul_reason_for_call )
   {
      if ( !plugin_On )
      {
         plugin_On = true;
         _P = GetPatcher();
         _PI = _P->CreateInstance("HD.Plugin.H3.NewSpells");

         //GetCurrentDirectoryA(sizeof(iniPath), iniPath);
         //strcat(iniPath, "\\NewSpells.ini");

         // Moving and Expanding Spell Table
         _PI->WriteLoHook(0x4EE1C1, afterInit);
               
         // nwcthereisnospoon
         _PI->WriteByte (0x402902, SPELLS_NUM);

         // "(Already learned)" Text
         _PI->WriteDword(0x40D97C, 0x3EA);
         
         // AI
         _PI->WriteDword(0x41FBDF, 0x3EA);
         _PI->WriteByte (0x41FC91, SPELLS_NUM);
         _PI->WriteDword(0x425C72, 0x3EA);
         _PI->WriteDword(0x425E98, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x427039, 0x3EA);
         _PI->WriteDword(0x427044, 0x3EA);
         _PI->WriteByte (0x427085, SPELLS_NUM);
         _PI->WriteDword(0x4329C1, 0x3EA);
         _PI->WriteDword(0x432A43, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x432CDE, 0x3EA);
         _PI->WriteDword(0x432D1E, SPELLS_NUM * sizeof(_Spell_));
         // ------------
         // Adventure AI
         // ------------
         _PI->WriteDword(0x430AE5, 0x3EA + SPL_DIMENSION_DOOR);
         _PI->WriteDword(0x4E57CC, 0x3EA + SPL_SUMMON_BOAT);
         _PI->WriteDword(0x56B346, 0x3EA + SPL_TOWN_PORTAL);
         _PI->WriteDword(0x56B7EA, 0x3EA + SPL_DIMENSION_DOOR);
         _PI->WriteDword(0x56B93F, 0x3EA + SPL_FLY);
         _PI->WriteDword(0x56B99A, 0x3EA + SPL_WATER_WALK);
         // ---------
         // Battle AI
         // ---------
         _PI->WriteDword(0x4397E6, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x43C6F2, SPELLS_NUM * sizeof(_Spell_));
         // Master Genie AI Spell Weighting
         _PI->WriteDword(0x43C21B, SPELLS_NUM * sizeof(_Spell_));
         // AI Quick Battle
         _PI->WriteDword(0x433026, 0x3EA);
         _PI->WriteDword(0x433719, 0x3EA);
         _PI->WriteDword(0x43940C, 0x3EA);
         _PI->WriteDword(0x43C561, 0x3EA);
         // AI Spell Scrolls?
         _PI->WriteDword(0x52A97A, 0x3EA);
         _PI->WriteDword(0x52A9B6, SPELLS_NUM * sizeof(_Spell_));
         
         // Can cast?
         _PI->WriteDword(0x447551, SPELLS_NUM * sizeof(_Spell_));
         
         _PI->WriteDword(0x447C7D, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x447CC8, SPELLS_NUM * sizeof(_Spell_));
         
         // Cheats in battle
         _PI->WriteByte (0x471C57, SPELLS_NUM);
         
         // Clear _Hero_.spell[140] (optional)
         _PI->WriteDword(0x48647A, 0x23);
         _PI->WriteJmp  (0x486485, 0x486498);
         
         _PI->WriteByte (0x4864B0, SPELLS_NUM);
         
         // Load Game?
         _PI->WriteByte (0x48A34B, SPELLS_NUM);
         
         // Scholar Secondary Skill
         _PI->WriteByte (0x4A2743, SPELLS_NUM);

         // New Game?
         _PI->WriteByte (0x4C244D, SPELLS_NUM);
         _PI->WriteByte (0x4C246F, SPELLS_NUM);
         _PI->WriteDword(0x4C24C9, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteByte (0x4C250E, SPELLS_NUM);
         _PI->WriteDword(0x4C2557, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x4C260D, SPELLS_NUM * sizeof(_Spell_));

         _PI->WriteByte (0x4E67AC, SPELLS_NUM);
         
         // Cheat Menu?
         _PI->WriteByte (0x4F508B, SPELLS_NUM);
         _PI->WriteDword(0x4F50CE, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x4F5114, SPELLS_NUM * sizeof(_Spell_));
                           
         // Pyramids
         _PI->WriteByte (0x4C170D, SPELLS_NUM);
                 
         // Shrines
         _PI->WriteDword(0x4C92C5, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x4C9347, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x4C93C0, SPELLS_NUM * sizeof(_Spell_));
         
         _PI->WriteByte (0x4CEC4F, SPELLS_NUM);
         
         // Clear _Hero_.spell[140] (optional)
         _PI->WriteDword(0x4D8F2E, 0x23);
         _PI->WriteJmp  (0x4D8F36, 0x4D8F49);
         
         _PI->WriteByte (0x4D8F53, SPELLS_NUM);
         _PI->WriteByte (0x4D8F8A, SPELLS_NUM);
         
         // Tome of Air Magic
         _PI->WriteDword(0x4D962D, SPELLS_NUM * sizeof(_Spell_));
         // Tome of Fire Magic
         _PI->WriteDword(0x4D9681, SPELLS_NUM * sizeof(_Spell_));
         // Tome of Water Magic
         _PI->WriteDword(0x4D96D6, SPELLS_NUM * sizeof(_Spell_));
         // Tome of Earth Magic
         _PI->WriteDword(0x4D972E, SPELLS_NUM * sizeof(_Spell_));
         // Spellbinder's Hat
         _PI->WriteDword(0x4D9771, SPELLS_NUM * sizeof(_Spell_));
         
         // Scholars
         _PI->WriteByte (0x5012B7, SPELLS_NUM);
         
         _PI->WriteDword(0x527ACB, 0x3EA);
         _PI->WriteDword(0x527B08, SPELLS_NUM * sizeof(_Spell_));

         // RMG
         _PI->WriteDword(0x534C4B, SPELLS_NUM * sizeof(_Spell_));
         
         // RMG Spell Scrolls
         _PI->WriteDword(0x5353D5, SPELLS_NUM);
         _PI->WriteByte (0x53542B, SPELLS_NUM);
         
         // Spell Book
         _PI->WriteDword(0x59CD5E, 0x3EA);
         _PI->WriteDword(0x59CDBF, SPELLS_NUM * sizeof(_Spell_));

         // Cast Spell
         _PI->WriteDword(0x5A1AD6, SPELLS_NUM * sizeof(_Spell_));

         // Mage Guild
         _PI->WriteByte (0x5BEA2C, SPELLS_NUM);
         _PI->WriteByte (0x5BEA70, SPELLS_NUM);
         _PI->WriteByte (0x5BEAAD, SPELLS_NUM);
         _PI->WriteDword(0x5BEAFE, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x5BEB2C, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteByte (0x5BEB48, SPELLS_NUM);
         _PI->WriteByte (0x5BEB71, SPELLS_NUM);
         _PI->WriteByte (0x5BEBB2, SPELLS_NUM);
         _PI->WriteDword(0x5BEC05, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteByte (0x5BEC1B, SPELLS_NUM);
         _PI->WriteLoHook(0x5BEA36, skipTownSetupForNewSpells); // Temp
                 
         // Conflux Grail
         _PI->WriteDword(0x5BE512, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x5BE56E, SPELLS_NUM * sizeof(_Spell_));
         _PI->WriteDword(0x5D7464, SPELLS_NUM);
         
         // ---------------------------
         // New _Hero_.spell[140] field
         // ---------------------------
         _PI->WriteCodePatch(0x4D8B72, "%n", 8);
         _PI->WriteCodePatch(0x4D8F7F, "%n", 8);
         _PI->WriteCodePatch(0x4D95AF, "%n", 7);

         // ----------------------------------------
         // New _GameMgr_.disabled_spells[140] field
         // ----------------------------------------
         _PI->WriteByte(0x4C16ED, 4); // Pyramids
         _PI->WriteByte(0x4C254C, 4); // Titan's Lightning Bolt
         _PI->WriteByte(0x4C25F1, 4); // Titan's Lightning Bolt
         _PI->WriteByte(0x501297, 4); // Scholars
         _PI->WriteByte(0x5BEA55, 4); // Mage Guild
                         
         _PI->WriteDword(0x52AE1C, 0x3EA);

         // ------
         // Battle
         // ------
         _PI->WriteByte (0x4963E9, ANIMS_NUM);
         _PI->WriteByte (0x4965BD, ANIMS_NUM);
         _PI->WriteByte (0x502405, SPELLS_NUM);
         _PI->WriteByte (0x502451, SPELLS_NUM);
         _PI->WriteByte (0x5024CF, SPELLS_NUM);
         _PI->WriteByte (0x502515, SPELLS_NUM);
         _PI->WriteByte (0x502E62, SPELLS_NUM);
         _PI->WriteByte (0x502EB7, SPELLS_NUM);
         _PI->WriteByte (0x59EFD9, SPELLS_NUM - 1 - ADVSPELLS_NUM);
         _PI->WriteDword(0x59EFE4, (int)&spellIndirectTableA);
         _PI->WriteByte (0x5A064E, SPELLS_NUM - 1 - ADVSPELLS_NUM);
         _PI->WriteDword(0x5A0659, (int)&spellIndirectTableB);
         _PI->WriteByte (0x4446EA, SPELLS_NUM - 1 - SPL_SHIELD);
         _PI->WriteDword(0x4446FD, (int)&spellIndirectTableC);
         _PI->WriteByte (0x444262, SPELLS_NUM - 1 - SPL_WEAKNESS);
         _PI->WriteDword(0x44427A, (int)&spellIndirectTableD);
         //_PI->WriteByte (0x44A259, SPELLS_NUM - 1 - SPL_LIGHTNING_BOLT);
                 
         // --------------------------------------------------
         // New _BattleStack_.active_spell_duration[162] field
         // --------------------------------------------------
         _PI->WriteDword(0x43787A, 162);
         _PI->WriteCodePatch(0x437880, "%n", 19);
         _PI->WriteDword(0x43D314, 162);
         _PI->WriteDword(0x43E3DF, SPELLS_NUM);
         _PI->WriteByte (0x443F63, SPELLS_NUM);
         _PI->WriteByte (0x446F03, SPELLS_NUM);
         _PI->WriteByte (0x5A1907, SPELLS_NUM);
         _PI->WriteByte (0x5A1995, SPELLS_NUM);
         _PI->WriteByte (0x5A84C8, SPELLS_NUM);
         _PI->WriteByte (0x5A852F, SPELLS_NUM);
         _PI->WriteCodePatch(0x4446D2, "%n", 7);
         _PI->WriteCodePatch(0x4446D2, "%n", 7);
         _PI->WriteJmp  (0x444694, 0x4450C2);
         
         // Disable creature spells
         _PI->WriteLoHook(0x4C2567, disableCreatureSpells);

         // Shrine Spells
         _PI->WriteLoHook(0x4C2625, shrineSpellsInit);
         _PI->WriteHiHook(0x4C9260, SPLICE_, DIRECT_, THISCALL_, fillShrine);
         
         // Fear, Disease, Aging
         _PI->WriteLoHook(0x444701, applySpell);
         _PI->WriteLoHook(0x44427E, resetSpell);
         _PI->WriteHiHook(0x43B2E0, SPLICE_, EXTENDED_, THISCALL_, getSpellWeight);

         // Death Cloud
         _PI->WriteLoHook(0x5A0F4C, DeathCloud);

         // Firebird, Magic Elemental
         _PI->WriteLoHook(0x59F889, checkSummonedCreaturesType);
         _PI->WriteLoHook(0x5A065D, SummonCreatures);
         _PI->WriteLoHook(0x5A7516, setSummonedCreaturesNumber);
         
         // Stack's Magic Vulnerability
         _PI->WriteHiHook(0x44A1A0, SPLICE_, EXTENDED_, FASTCALL_, getStackMagicVulnerability);
         
         // Artifacts (incl. Spell Scrolls)
         _PI->WriteHiHook(0x4D9840, SPLICE_, DIRECT_, THISCALL_, updateSpellsFromArtifacts);

         // Can unit attack? Seems to be optional
         _PI->WriteHiHook(0x4425A0, SPLICE_, DIRECT_, THISCALL_, unitCanAttack);
         
         // ------------
         //     Fear
         // ------------
         // Set cursor for human players
         _PI->WriteHiHook(0x475DC0, SPLICE_, EXTENDED_, THISCALL_, setCursorForFearSpell);
         // Not allowing to attack under Fear
         _PI->WriteLoHook(0x422088, skipMeleeAttackUnderFear);
         _PI->WriteLoHook(0x41F25C, skipShootingUnderFear);
      }
   }

   return TRUE;
}

Названия полей type_AI_spellcaster и type_enchant_data после буду угадывать :smile1: Главное пока, чтобы из стека 6 * 4 = 24 байта дворда выталкивалось (retn 18h).

А что-нибудь известно про флаги заклинаний AI? У Disease сейчас 0x40045. Как у Prayer, только убрал масскаст на свои отряды. И что там с кастом на боевые машины, SPF_NOT_ON_MACHINE? Aging идёт пока без флагов AI - 0x1015 (но комп его кастует). Не знаю, какой ему выставить.

Кто бы за меня покурил ФизМиГ хотя бы для тех заклинаний, которые делаем? :smile2: Prayer можно кастовать на боевые машины. По логике мы "благословляем" тех, кто ими управляет: скорость машин всё равно не увеличивается, а увеличение атаки и защиты можно повесить на прибавку параметров "обслуживающего персонала". Или как понимать иначе, что Молитву можно кастовать на машины? Поэтому я считаю, что и Disease пусть можно будет кастовать на боевые машины. А вот Aging пусть нельзя будет кастовать на боевые машины: тут мы ухудшаем живучесть самой техники ("старим", что нонсенс), а не тех, кто ею управляет.

* * *
UPD: Раздал иммунитеты новым Poison, Disease, Aging и Fear (пока попробовал хайхуком, но, по-хорошему, придётся ставить лоухуки для универсальности). У Poison теперь такие же иммунитеты, как у абилки. Disease, Aging и Fear не действуют на нежить. Aging и Fear - на боевые машины. Fear - заклинание разума.

UPD: Что ж, Fear - таким, как, мне кажется, его задумывали разработчики, - почти готов. Осталось только написать функцию оценки для AI. Можно ещё подумать над тем, чтобы убрать ответку на Эксперте Земли.

Теперь Fear работает и для AI. Отряды под этим заклинанием не могут атаковать. Я подумал, что ставить рукопашников в защитную стойку вместо атаки - это скучно. Поэтому они ходят так, как если бы не хотели атаковать врага врукопашную. Стрелкам, мне кажется, лучше всего просто защищаться.

Невероятно, но вот я почти вернул в игру заклинание, которое заинтересовало в 2006-м году, и благодаря наработкам многих поклонников игры, смог реализовать свою давнюю мечту :smile20:
Вернуться к началу

Пред.След.

Вернуться в Общий раздел

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 2

cron