Объявления

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

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

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

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

Сообщение Rolex » 03 сен 2021, 06:40

AlexSpl писал(а):

Поэтому я считаю, что и Disease пусть можно будет кастовать на боевые машины. А вот Aging пусть нельзя будет кастовать на боевые машины: тут мы ухудшаем живучесть самой техники ("старим", что нонсенс), а не тех, кто ею управляет.

Все верно, я тоже так считаю, что Disease должен работать (накладываться на боевые машины) также, как и Prayer. А вот абилку Призрачных драконов Aging я даже проверил. Все верно Вы сделали, так как абилка также не накладывается на боевые машины.

AlexSpl писал(а):

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

Да, для универсальности, наверное, лучше будут лоухуки.

AlexSpl писал(а):

Можно ещё подумать над тем, чтобы убрать ответку на Эксперте Земли.

Поддерживаю. Это нужно делать в любом случае. А то у нас только скорость меняется. Как-то слобоватая разница между Basic и Expert. По сути то особой разницы и нет, ведь отряд в любом случае на любом уровне развития школы не может атаковать. Но для Эксперта этого мало. Обычно на Эксперте прибавка самая крутая идет.

AlexSpl писал(а):

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

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

AlexSpl писал(а):

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

В 2006-м еще не было ни HD мода, ни уж тем более patcher_x86.dll, ни SoD_SP, ни H3API, ни homm3.h, ни MMArchive, ни DefTool, ни HotA, ни MoP. В то время я о моддинге Героев вообще ничего не знал. Тогда с этим было гораздо сложнее.
Вернуться к началу

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 » 03 сен 2021, 07:05

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

Можно проверить, но я думаю, что для стрелков без стрел Melee AI включится автоматически.

Такой есть вопрос. Как поступать со скоростью? Понятно, что Fear - имбовое заклинание, которое, скорее всего, предназначалось для кампании, но у нас, кроме него, есть Disease, которое снижает скорость аж на 40%, и есть оригинальное заклинание Slow. Повесив все три, а то и два из них, можно снизить скорость отряда до 1. Может, подумать над тем, чтобы заклинания снижали первоначальную скорость отряда? Например, повесили Expert Fear (красный цвет в формуле ниже), снизили скорость на 75%, повесили следом Expert Disease (синий цвет) - и скорость отряда стала ещё на 40% меньше: (1 - 0,75) * (1 - 0,40) * Speed = 0,15 * Speed, т.е. у отряда остаётся только 15% его первоначальной скорости.

Я предлагаю сделать так, чтобы оставался только самый сильный эффект: например, после Expert Fear (-75% скорости), Expert Disease (-40%) скорость не снижает (40 < 75), а вот после Basic Fear (-25%) уже идёт снижение скорости до 60% первоначальной, т.к. 40 > 25. Можно ещё новым заклинаниям на Slow реагировать: если висит Slow, то понижать скорость только в том случае, если она уменьшится.

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

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

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

Сообщение Rolex » 03 сен 2021, 10:58

AlexSpl писал(а):

Я предлагаю сделать так, чтобы оставался только самый сильный эффект: например, после Expert Fear (-75% скорости), Expert Disease (-40%) скорость не снижает (40 < 75), а вот после Basic Fear (-25%) уже идёт снижение скорости до 60% первоначальной, т.к. 40 > 25. Можно ещё новым заклинаниям на Slow реагировать: если висит Slow, то понижать скорость только в том случае, если она уменьшится.

Можно конечно, но не знаю, честно говоря, нужно подумать. Получится как с длительностью при наложении закла и повторяющей его абилки на один отряд, когда окончательная длительность не суммируется, а обновляется относительно большего значения.

Если глянуть на это с другой стороны по аналогии со связкой Молитва + Ускорение, то там прибавка суммируется. В итоге если взять минимальную скорость в 3 ед, максимальную в 21 и среднюю в 10-12, то относительно первоначально скорости существа мы получим прибавку в 300%, 43%, 75%-90%-110%. Так вот, наложив на Зомби или Гномов Экспертную Молитву и Экспертное Ускорение мы сделаем так, что существо с самой низкой скоростью в игре пробежит все поле боя по прямой и атакует врага, тем самым повысим их скорость на целых 300% относительно их первоначальной скорости.

Если смотреть на самых быстрых существ: Фениксы, Лазурные драконы и Архангелы, то мы получаем прибавку по Экспертной связке Ускорение и Молитва относительно их базовой скорости в 43%-47%-50%. То есть это тот минимум в процентном отношении на который должна снижать скорость связка Slow + Disease. Больше можно, хоть и не желательно, но точно не меньше.

Вся пробелма в том, что с Ускорением и Молитвой прибавка фиксированная, а с Slow и Disease у нас %, а в таком случае в итоге существа с более высокой скоростью потеряют больше единиц скорости и для них это снижение будет более выраженым относительно их базовой скорости.

В общем этот момент я оставляю на ваше усмотрение. :smile2:
Вернуться к началу

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 » 03 сен 2021, 14:42

Наладил взаимодействие между заклинаниями, влияющими на скорость. Теперь всё, как в оригинале.

Пример. Герой с силой магии 5 (без артефактов) вешает в 1-й раунд Expert Fear на отряд Master Genies (скорость 11). Скорость падает до [11 - 75%] = 2. Во 2-м раунде герой кастует Expert Slow, скорость остаётся равной 2, т.к. [11 - 50%] > [11 - 75%]. В 3-м раунде герой кастует Expert Disease, скорость по-прежнему 2, т.к. [11 - 40%] > [11 - 75%].

В 6-м раунде снимается Expert Fear. Следующий по силе эффект, влияющий на скорость, - Expert Slow. Скорость отряда Master Genie становится равной [11 - 50%] = 5. В 7-м раунде пропадает Expert Slow, но т.к. продолжает висеть Expert Disease, то скорость отряда равна [11 - 40%] = 6. В 8-м раунде скорость отряда Master Genie возвращается к первоначальной (11).

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

Вот код для тестирования других взаимодействий заклинаний, влияющих на скорость (например, в связке с Prayer и Haste):

 NewSpells
Код: Выделить всё
#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_AGE_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_AGE_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];

// These are not exactly optional tables, so don't remove them
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
};

char spellIndirectTableE[] = {
   // Spells (starting from Lightning Bolt #17)
    0, 16,  0, 16, 16, 16, 16,  1,  2, 16,
   16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
   16,  3,  4, 16,  5,  6, 16,  7, 16, 16,
   16, 16,  8,  8,  9,  9, 16, 16,  9, 16,
   16, 16, 10, 11, 12, 13, 16, 16, 16, 16,
   16, 16, 16,
   // Special Abilities
   14, 15, -1, -1, -1, -1, -1, -1, -1, -1,
   -1,
   // New Spells (starting from Fear #81)
   16, 16, 16, 16, 16, 16, 16, 16
};

bool shrineSpells[SPELLS_MAX];

struct FearSpellParams
{
   int SpeedMod;
};

FearSpellParams fearSpellParams[2][21];

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

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

struct SlowSpellParams
{
   int SpeedMod;
};

SlowSpellParams slowSpellParams[2][21];

struct AgeSpellParams
{
   int HealthMod;
};

AgeSpellParams ageSpellParams[2][21];

// Debug
void showDebugInfo(const char* format, ...)
{
   va_list args;
   va_start(args, format);

   vsprintf(o_TextBuffer, format, args);
   b_MsgBox(o_TextBuffer, MBX_OK);
   
   va_end(args);
}

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] = 75;
   spell[SPL_FEAR_NEW].effect[1] = 75;
   spell[SPL_FEAR_NEW].effect[2] = 50;
   spell[SPL_FEAR_NEW].effect[3] = 25;
   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] = 80;
   spell[SPL_DISEASE_NEW].effect[1] = 80;
   spell[SPL_DISEASE_NEW].effect[2] = 60;
   spell[SPL_DISEASE_NEW].effect[3] = 60;
   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].SpeedMod = 20;
   diseaseSpell[1].AttackPenalty = 2;
   diseaseSpell[1].DefensePenalty = 2;
   diseaseSpell[1].SpeedMod = 20;
   diseaseSpell[2].AttackPenalty = 4;
   diseaseSpell[2].DefensePenalty = 4;
   diseaseSpell[2].SpeedMod = 40;
   diseaseSpell[3].AttackPenalty = 4;
   diseaseSpell[3].DefensePenalty = 4;
   diseaseSpell[3].SpeedMod = 40;
   
   // Aging
   spell[SPL_AGE_NEW].type = -1;
   spell[SPL_AGE_NEW].wav_name = "Age.wav";
   spell[SPL_AGE_NEW].animation_ix = ANIM_AGE_NEW;
   spell[SPL_AGE_NEW].flags = 0x1015;
   spell[SPL_AGE_NEW].name = "Age";
   spell[SPL_AGE_NEW].short_name = "Age";
   spell[SPL_AGE_NEW].level = 5;
   spell[SPL_AGE_NEW].school_flags = SSF_FIRE;
   spell[SPL_AGE_NEW].mana_cost[0] = 25;
   spell[SPL_AGE_NEW].mana_cost[1] = 20;
   spell[SPL_AGE_NEW].mana_cost[2] = 20;
   spell[SPL_AGE_NEW].mana_cost[3] = 20;
   spell[SPL_AGE_NEW].eff_power = 0;
   spell[SPL_AGE_NEW].effect[0] = 70;
   spell[SPL_AGE_NEW].effect[1] = 70;
   spell[SPL_AGE_NEW].effect[2] = 60;
   spell[SPL_AGE_NEW].effect[3] = 50;
   spell[SPL_AGE_NEW].chance2get_var[0] = 10;
   spell[SPL_AGE_NEW].chance2get_var[1] = 10;
   spell[SPL_AGE_NEW].chance2get_var[2] = 10;
   spell[SPL_AGE_NEW].chance2get_var[3] = 10;
   spell[SPL_AGE_NEW].chance2get_var[4] = 10;
   spell[SPL_AGE_NEW].chance2get_var[5] = 10;
   spell[SPL_AGE_NEW].chance2get_var[6] = 10;
   spell[SPL_AGE_NEW].chance2get_var[7] = 10;
   spell[SPL_AGE_NEW].chance2get_var[8] = 10;
   spell[SPL_AGE_NEW].ai_value[0] = 50;
   spell[SPL_AGE_NEW].ai_value[1] = 50;
   spell[SPL_AGE_NEW].ai_value[2] = 100;
   spell[SPL_AGE_NEW].ai_value[3] = 150;
   spell[SPL_AGE_NEW].description[0] =
      "{Age}\n\nAge Description.\n";
   spell[SPL_AGE_NEW].description[1] =
      "{Basic Age}\n\nBasic Age Description.\n";
   spell[SPL_AGE_NEW].description[2] =
      "{Advanced Age}\n\nAdvanced Age Description.\n";
   spell[SPL_AGE_NEW].description[3] =
      "{Expert Age}\n\nExpert Age 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_AGE_NEW].defName = "sp01_.def";
   anim[ANIM_AGE_NEW].name = "Age";
   anim[ANIM_AGE_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;
}

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 initSpeedMods(LoHook* h, HookContext* c)
{
   for (int side = ATTACKER; side <= DEFENDER; ++side)
      for (int i = 0; i < 21; ++i)
      {
         o_BattleMgr->stack[side][i].Field<float>(0x4C8) = 1.0f;
         slowSpellParams[side][i].SpeedMod = 100;
         fearSpellParams[side][i].SpeedMod = 100;
         diseaseSpellParams[side][i].SpeedMod = 100;
      }
   
   return EXEC_DEFAULT;
}

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 spellSpecialtyEffect = 0;
   
   switch (spell)
   {
   case SPL_FEAR_NEW:
      // showDebugInfo("Speed = %d", stack->creature.speed);

      if (!(stack->creature.flags & BCF_CANT_MOVE))
      {
         fearSpellParams[stack->side][stack->index_on_side].SpeedMod =
            o_Spell[SPL_FEAR_NEW].effect[schoolLevel];

         int minSpeedMod = 100;

         if (stack->active_spell_duration[SPL_SLOW] && slowSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = slowSpellParams[stack->side][stack->index_on_side].SpeedMod;

         if (stack->active_spell_duration[SPL_DISEASE_NEW] && diseaseSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = diseaseSpellParams[stack->side][stack->index_on_side].SpeedMod;

         if (fearSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = fearSpellParams[stack->side][stack->index_on_side].SpeedMod;
               
         stack->Field<float>(0x4C8) = minSpeedMod / 100.0f;
      }
     
      break;

   case SPL_DISEASE_NEW:
      // Init
      diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty = diseaseSpell[schoolLevel].AttackPenalty;
      diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty = diseaseSpell[schoolLevel].DefensePenalty;
     
      // Optional, as we don't have heroes with Disease spell specialty yet
      if (hero)
      {
         spellSpecialtyEffect = CALL_4(int, __thiscall, 0x4E6260, hero, SPL_DISEASE_NEW, hero->level, 0);
         diseaseSpellParams[stack->side][stack->index_on_side].AttackPenalty += spellSpecialtyEffect;
         diseaseSpellParams[stack->side][stack->index_on_side].DefensePenalty += spellSpecialtyEffect;
      }

      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].SpeedMod =
            o_Spell[SPL_DISEASE_NEW].effect[schoolLevel] + spellSpecialtyEffect;

         int minSpeedMod = 100;

         if (stack->active_spell_duration[SPL_SLOW] && slowSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = slowSpellParams[stack->side][stack->index_on_side].SpeedMod;

         if (stack->active_spell_duration[SPL_FEAR_NEW] && fearSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = fearSpellParams[stack->side][stack->index_on_side].SpeedMod;

         if (diseaseSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = diseaseSpellParams[stack->side][stack->index_on_side].SpeedMod;
               
         stack->Field<float>(0x4C8) = minSpeedMod / 100.0f;
      }
     
      break;

   case SPL_SLOW:
      if (!(stack->creature.flags & BCF_CANT_MOVE))
      {
         // Removing Haste spell
         CALL_2(void, __thiscall, 0x444230, stack, SPL_HASTE);
         
         slowSpellParams[stack->side][stack->index_on_side].SpeedMod = *(int*)(c->ebp + 0x10);

         int minSpeedMod = 100;

         if (stack->active_spell_duration[SPL_FEAR_NEW] && fearSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = fearSpellParams[stack->side][stack->index_on_side].SpeedMod;

         if (stack->active_spell_duration[SPL_DISEASE_NEW] && diseaseSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = diseaseSpellParams[stack->side][stack->index_on_side].SpeedMod;

         if (slowSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = slowSpellParams[stack->side][stack->index_on_side].SpeedMod;
               
         stack->Field<float>(0x4C8) = minSpeedMod / 100.0f;

         // Slow animation
         stack->Field<int>(0x158) = (int)(stack->Field<int>(0x68) * 1.5);
      }
     
      break;

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

      int minHealthMod = 100;

      if (stack->active_spell_duration[SPL_AGE_NEW])
      {
         ageSpellParams[stack->side][stack->index_on_side].HealthMod = o_Spell[SPL_AGE_NEW].effect[schoolLevel];
         if (ageSpellParams[stack->side][stack->index_on_side].HealthMod < minHealthMod)
            minHealthMod = ageSpellParams[stack->side][stack->index_on_side].HealthMod;
      }

      if (stack->active_spell_duration[SPL_AGING] && 50 < minHealthMod)
         minHealthMod = 50;

      fullHealth *= minHealthMod / 100.0;

      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))
      {
         int minSpeedMod = 100;

         if (stack->active_spell_duration[SPL_SLOW] && slowSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = slowSpellParams[stack->side][stack->index_on_side].SpeedMod;

         if (stack->active_spell_duration[SPL_DISEASE_NEW] && diseaseSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = diseaseSpellParams[stack->side][stack->index_on_side].SpeedMod;

         stack->Field<float>(0x4C8) = minSpeedMod / 100.0f;
      }

      fearSpellParams[stack->side][stack->index_on_side].SpeedMod = 100;
      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))
      {
         int minSpeedMod = 100;

         if (stack->active_spell_duration[SPL_SLOW] && slowSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = slowSpellParams[stack->side][stack->index_on_side].SpeedMod;

         if (stack->active_spell_duration[SPL_FEAR_NEW] && fearSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = fearSpellParams[stack->side][stack->index_on_side].SpeedMod;

         stack->Field<float>(0x4C8) = minSpeedMod / 100.0f;
      }
     
      diseaseSpellParams[stack->side][stack->index_on_side].SpeedMod = 100;
      break;
   
   case SPL_SLOW:
      if (!(stack->creature.flags & BCF_CANT_MOVE))
      {
         int minSpeedMod = 100;

         if (stack->active_spell_duration[SPL_FEAR_NEW] && fearSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = fearSpellParams[stack->side][stack->index_on_side].SpeedMod;

         if (stack->active_spell_duration[SPL_DISEASE_NEW] && diseaseSpellParams[stack->side][stack->index_on_side].SpeedMod < minSpeedMod)
            minSpeedMod = diseaseSpellParams[stack->side][stack->index_on_side].SpeedMod;

         stack->Field<float>(0x4C8) = minSpeedMod / 100.0f;
      }

      // Slow animation
      stack->Field<int>(0x158) = stack->Field<int>(0x68);

      slowSpellParams[stack->side][stack->index_on_side].SpeedMod = 100;
      break;

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

      int minHealthMod = 100;

      if (stack->active_spell_duration[SPL_AGE_NEW] && ageSpellParams[stack->side][stack->index_on_side].HealthMod < minHealthMod)
         minHealthMod = ageSpellParams[stack->side][stack->index_on_side].HealthMod;

      if (stack->active_spell_duration[SPL_AGING] && 50 < minHealthMod)
         minHealthMod = 50;

      fullHealth *= minHealthMod / 100.0;

      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 cureNewSpells(LoHook* h, HookContext* c)
{
   _BattleStack_* stack = (_BattleStack_*)c->esi;

   CALL_2(void, __thiscall, 0x444230, stack, SPL_FEAR_NEW);
   CALL_2(void, __thiscall, 0x444230, stack, SPL_POISON_NEW);
   CALL_2(void, __thiscall, 0x444230, stack, SPL_DISEASE_NEW);
   CALL_2(void, __thiscall, 0x444230, stack, SPL_AGE_NEW);

   return EXEC_DEFAULT;
}

int __stdcall getStackMagicVulnerability(LoHook* h, HookContext* c)
{
   _CreatureInfo_* creature = (_CreatureInfo_*)c->edi;
   int creatureId = *(int*)(c->ebp - 8);
   int spell = *(int*)(c->ebp - 0x10);

   bool immune = false;

   switch (spell)
   {
   case SPL_FEAR_NEW:
      if (!(creature->flags & BCF_ALIVE))
         immune = true;
      break;
   case SPL_POISON_NEW:
      if (!(creature->flags & BCF_ALIVE) || creatureId == CID_STONE_GARGOYLE || creatureId == CID_OBSIDIAN_GARGOYLE)
         immune = true;
      break;
   // Let's allow to cast Disease on battle machines
   case SPL_DISEASE_NEW:
      if (!(creature->flags & BCF_ALIVE) && !(creature->flags & BCF_CANT_MOVE))
         immune = true;
      break;
   case SPL_AGE_NEW:
      if (!(creature->flags & BCF_ALIVE))
         immune = true;
      break;
   default:
      return EXEC_DEFAULT;
   }

   c->return_address = immune ? 0x44A3E8 : 0x44A416;
   return NO_EXEC_DEFAULT;
}

// ------------------
// 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_Fear(type_AI_spellcaster* caster, int unused_edx, _BattleStack_* stack, type_enchant_data data)
{
   return 10000;
}

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

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

int __fastcall AI_Weight_Age(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 __fastcall AI_Weight_DeathCloud(type_AI_spellcaster* caster, int unused_edx, _BattleStack_* stack, type_enchant_data data)
{
   return 10000;
}

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

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

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

int __stdcall getSpellWeight(HiHook* h, _BattleMgr_* battleMgr, int spell)
{
   switch (spell)
   {
   case SPL_FEAR_NEW:
      return (int)AI_Weight_Fear;
      break;
   case SPL_POISON_NEW:
      return (int)AI_Weight_Poison;
      break;
   case SPL_DISEASE_NEW:
      return (int)AI_Weight_Disease;
      break;
   case SPL_AGE_NEW:
      return (int)AI_Weight_Age;
      break;
   case SPL_DEATH_CLOUD_NEW:
      return (int)AI_Weight_DeathCloud;
      break;
   case SPL_DEATH_BLOW_NEW:
      return (int)AI_Weight_DeathBlow;
      break;
   case SPL_FIREBIRD_NEW:
      return (int)AI_Weight_SummonFirebird;
      break;
   case SPL_MAGIC_ELEMENTAL_NEW:
      return (int)AI_Weight_SummonMagicElemental;
      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);
         _PI->WriteDword(0x44A264, (int)&spellIndirectTableE);
                 
         // --------------------------------------------------
         // 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, Age
         _PI->WriteLoHook(0x444701, applySpell);
         _PI->WriteLoHook(0x44427E, resetSpell);
         _PI->WriteHiHook(0x43B2E0, SPLICE_, EXTENDED_, THISCALL_, getSpellWeight);

         // Allow to cure new spells
         _PI->WriteLoHook(0x4462FD, cureNewSpells);

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

         // Summon Firebird, Summon Magic Elemental
         _PI->WriteLoHook(0x59F889, checkSummonedCreaturesType);
         _PI->WriteLoHook(0x5A065D, SummonCreatures);
         _PI->WriteLoHook(0x5A7516, setSummonedCreaturesNumber);
         
         // Stack's Magic Vulnerability
         _PI->WriteLoHook(0x44A268, 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);

         _PI->WriteLoHook(0x4ADF71, initSpeedMods);
         
         int speedModJmpAddr[] = {0x441CE3, 0x441E06, 0x44857E, 0x4485BC, 0x4485FC, 0x44867A, 0x448A05};
         for (int i = 0; i < sizeof(speedModJmpAddr) / sizeof(int); ++i)
            _PI->WriteHexPatch(speedModJmpAddr[i], "90 90");
      }
   }

   return TRUE;
}

Защита 15 на 2-й и 3-й картинках не баг. Заскринил не в защитной стойке. Хотя я не понял, почему именно 16. Надо смотреть эффекты.

* * *
UPD: Добавил функции-заглушки взвешивания для всех новых заклинаний. Теперь AI должен кастовать все. Пересмотрел иммунитеты: они теперь такие же, как у оригинальных спецабилок, за исключением того, что Disease можно кастовать на боевые машины (такое решение принято). Добавил функцию лечения новых заклинаний, кроме Age (думаю позже переписать её целиком). Погонял несколько боёв. Вроде, всё работает :smile1:

NewSpellsAlpha.zip
(365.62 КБ) Скачиваний: 142
Вернуться к началу

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

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

Сообщение Rolex » 03 сен 2021, 20:16

Раздачу иммунитетов с хайхука на лоухуки для универсальности я так понял еще не переписовали. Ах да, чтобы не забыть, нам же еще для Fear нужно убрать ответку на Эксперте Земли.
Вернуться к началу

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 » 03 сен 2021, 20:27

Цитата:
Раздачу иммунитетов с хайхука на лоухуки для универсальности я так понял еще не переписовали. Ах да, чтобы не забыть, нам же еще для Fear нужно убрать ответку на Эксперте Земли.

Первое сделал. Теперь можно переписать иммунитеты вообще для всех заклинаний, а не только новых (причём можно делать не только стандартные иммунитеты). Второе пока не делал.

Много времени уходит на тесты. Сам себе тестер :smile1: Вот как обстоят дела с клонами существ, на которых висят новые заклинания? Что там с воскрешением? Кому тестить? Конечно же, мне :smile1:

Следующим добавлю функционал для заклинания Poison, и останется добавить только функционал для Death Blow. Дальше - только функции для AI и плагин готов (по крайней мере, для широкого тестирования).

* * *
 Код с добавленным Poison
NewSpells.txt
(57.35 КБ) Скачиваний: 142

Теперь можно тестировать всевозможные связки абилок Poison и Age и заклинаний Poison и Age. Я бы эти заклинания отправил на 2-3 уровень, значительно понизив эффект, но добавив ещё что-нибудь интересное. Так как продолжительность Poison у нас равна СМ, то 50% здоровья будут достигнуты очень быстро при 15% каждый ход. Можно добавить рандома, например, чтобы не так быстро. Ещё интересная идея для Disease появилась: может, каждый раунд перекидывать ослабленный закл на соседние с заболевшим отряды? Я думаю, это прикольно.

* * *
Такой вопрос. Что будет в оригинале, если отряд старят, на следующий раунд отравляют, а после того, как яд проходит, снова старят? Допустим, отряду не наносится урон во время атаки, и этот отряд - Титаны. Какое у них будет здоровье после вышеописанных событий?

NewSpellsAlpha.zip
(365.9 КБ) Скачиваний: 134
Вернуться к началу

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

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

Сообщение Rolex » 04 сен 2021, 10:55

AlexSpl писал(а):

Следующим добавлю функционал для заклинания Poison, и останется добавить только функционал для Death Blow. Дальше - только функции для AI и плагин готов (по крайней мере, для широкого тестирования).

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

AlexSpl писал(а):

Теперь можно тестировать всевозможные связки абилок Poison и Age и заклинаний Poison и Age. Я бы эти заклинания отправил на 2-3 уровень, значительно понизив эффект, но добавив ещё что-нибудь интересное.

Думаю, не стоит, лучше так и оставить. Я всегда все тщательно анализирую и взвешиваю, стараюсь брать за основу процент из абилки и уже на основе этого уходить в меньшую или большую сторону для разбивки на уровни. Для Disease логично идти в сторону увеличения подобно Молитве. А для Aging слишком жирно снижать более чем на 50% базовое здоровья. Тогда уж слишком имбовое заклинание получится.

Для 2-3 уровня у нас будут другие заклы: Притяжение, Регенерация и Вызов фей.

AlexSpl писал(а):

Так как продолжительность Poison у нас равна СМ, то 50% здоровья будут достигнуты очень быстро при 15% каждый ход. Можно добавить рандома, например, чтобы не так быстро.

Я вообще немножко по-другому планировал сделать. Чтобы 30%/40%/50% в зависимости от уровня развития школы всегда достигались в течении 3 раундов, поэтому я и предагал 10%/13,33%/16,66%, если в целых, то 10%/13%/16%. Кстати, а можно ли сделать чтобы и дробная часть учитывалась?

В оригинале же у Poison 10%. За один цикл в 3 раунда можно оснизить на 30%. Но если будут потворные наложения, то максимум на 50%. Я также думал над тем, чтобы сделать 6%/8%/10%, тогда цикл увеличится до 5 раундов. Тоже норм. Но вот если у врага есть Лечение, то 6% в сравнении с 10% как-то слабовато.

Вы не в курсе почему после окончания действия абилки Poison в оригинале Здоровья отряда на который был наложен Яд не восстанавливается? Это такая задумка или баг? Самое интереное, что оно не восстанавливается даже после гибели и воскрешения целевого отряда. А вот Лечение полностью излечивает от Яда и его последсвий даже тогда, когда на отряде уже нет Яда. Мне кажется это баг.
А сможете сделать так, чтобы после окончания дейсвия Poison базовое здоровье отряда восстанавливалось (как с нашим заклом, так и с абилкой в оригинале), как это происходит с Aging?

AlexSpl писал(а):

Ещё интересная идея для Disease появилась: может, каждый раунд перекидывать ослабленный закл на соседние с заболевшим отряды? Я думаю, это прикольно.

Это может быть и прикольно, но я все же думаю разработчики вряд ли бы так сделали.

AlexSpl писал(а):

Такой вопрос. Что будет в оригинале, если отряд старят, на следующий раунд отравляют, а после того, как яд проходит, снова старят? Допустим, отряду не наносится урон во время атаки, и этот отряд - Титаны. Какое у них будет здоровье после вышеописанных событий?

Интересно получается, они половинят друг друга. У меня получилось снизить базовое здоровья Титана в 4 раза с 300 до 75! Но вот ниже 75 здоровье уже не падало. Яд уже не срабатывал. Получается каждое из них за базу берет уже сниженное другим здоровье: 300 - 50% = 150; 150 - 50% = 75. То есть в связке Poison и Aging страшная сила, которая может снизить здоровья отряда на целых 75% (300 - 75% = 75). На Лазурного дракона правда не действует. Но вот здоровье Кристального дракона можно с 800 до 200 опустить. :smile8:

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

---

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

---

Изображение Изображение Изображение Изображение Изображение Изображение Изображение
Вернуться к началу

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 » 04 сен 2021, 11:19

Цитата:
Интересно получается, они половинят друг друга. У меня получилось снизить базовое здоровья Титана в 4 раза с 300 до 75! Но вот ниже 75 здоровье уже не падало. Яд уже не срабатывал. Получается каждое из них за базу берет уже сниженное другим здоровье: 300 - 50% = 150; 150 - 50% = 75. То есть в связке Poison и Aging страшная сила, которая может снизить здоровья отряда на целых 75% (300 - 75% = 75). На Лазурного дракона правда не действует. Но вот здоровье Кристального дракона можно с 800 до 200 опустить.

Тогда у нас всё правильно работает. Я думал, что это баг.

Сделал, чтобы Fear на Эксперте не позволял отряду отвечать на атаки. Ещё экспертный Fear теперь будет снимать Counterstrike. Хотел добавить иммунитет к Counterstrike, но много технических сложностей, а потом подумал: пусть Counterstrike работает (тактика защиты от Expert Fear с помощью Counterstrike может быть полезна, особенно на авторских картах, где любят строить бои на малоизвестных фишках).
Вернуться к началу

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

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

Сообщение Rolex » 04 сен 2021, 11:40

AlexSpl писал(а):

Хотел добавить иммунитет к Counterstrike, но много технических сложностей

Ну, не знаю, по-моему, более высокоуровневое заклинание должно быть в приоритете. То есть по идее под Expert Fear по логике Counterstrike работать не должен. Ответок быть не должно. Это реально сложно сделать?
Вернуться к началу

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 » 04 сен 2021, 12:31

NewSpells.txt
Актуальная версия (альфа)
(59.25 КБ) Скачиваний: 139

Добавил функции взвешивания для Poison и Age (оригинальные с небольшими изменениями).
Вернуться к началу

Пред.След.

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

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

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

cron