Объявления
Поздравляем
Roman2211


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

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

Герои Меча и Магии III: Возрождение Эрафии, Герои Меча и Магии III Дыхание Смерти, Герои Меча и Магии III Клинок Армагеддона, Герои Меча и Магии III Хроники Героев
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 » 21 мар 2021, 17:33

Начал писать отражение для 4 площадных заклинаний и заметил проблему с одиночным Берсерком для AI. Дело в том, что одиночный Берсерк - это технически по-прежнему площадный Берсерк, но для одной цели. Поэтому указатель на отряд нельзя брать из edi (там будет 0), из-за чего получим вылет при касте человеком площадного заклинания. Пытаюсь исправить :smile1:

* * *
Так. А в каких случаях AI вообще Берсерк кастует? Что-то у меня не получается заставить его кастовать Берсерк (плагины все отключил). Недавно же видел, как он кастовал, или у меня глюки? :smile5:

Кажется, вспомнил. Проблема была в том, что наш одиночный Берсерк не отражается :smile1:
Вернуться к началу

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

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

Сообщение Rolex » 21 мар 2021, 19:40

Такс, у нас есть еще два специфичных и редкоиспользуемых закла - Волна смерти (атакуект всех, кроме нежити, вкл друж. отряды) и Уничтожение нежити (атакует только нежить, вкл друж. отряды). Так вот, сейчас Magic Mirror для них не работает. Получают урон все, даже те отряды, что под Зеркалом. Если мы кастуем, а у врага стоит Зеркало на одном отряде или на всех, то надо бы сделать так, чтобы вражеские отряды отражали закл, просто в пустоту, так как наши отряды будут атакованы и так. То есть для тех отрядов, которые отразили закл мы изначально проигрываем анимацию Magic Mirror, а после уже для тех вражексих отрядов которые не отразили его вместе с атакой наших отрядов мы проигрываем анимацию кастуемого закла. Аналогично, только наоборот, если враг кастит один из этих заклов, а мы под Зеркалом. А вот в случае если и мы и враг под Зеркалом и кто-то из нас кастует один из вышеперечисленых заклов, то тогда для отрядов обеих сторон мы проигрываем изначально анимацию Magic Mirror с его звуком для отразивших отрядов, а уже после на отряды обеих сторон накладываем закл на те отряды, которые его не отразили.
Последний раз редактировалось Rolex 21 мар 2021, 20:22, всего редактировалось 4 раз(а).
Вернуться к началу

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 » 21 мар 2021, 19:44

Версия для тестирования отражения и резиста для дамажащих AoE спеллов (перенаправление пока не реализовано):

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

Patcher* _P;
PatcherInstance* _PI;

bool showMagicMirrorAnim, showMagicResistAnim, showSpellAnim;
bool needMagicMirrorAnim[2][20], needMagicResistAnim[2][20], needSpellAnim[2][20];
int stackIndex;

#define AnimateStack (((bool(*)[20])&o_BattleMgr->Field<bool>(0x547C)))

bool magicMirrorLucky(_BattleStack_* stack, int spell)
{
    int effSchoolLevel = CALL_3(int, __thiscall, 0x4E52F0, o_BattleMgr->hero[stack->side], SPL_MAGIC_MIRROR, o_BattleMgr->spec_terr_type);
   
    return !(o_Spell[spell].flags & SPF_FRIENDLY_HAS_MASS) && (stack->side != o_BattleMgr->current_side || spell == SPL_BERSERK ) &&
        stack->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(1, 100) <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel];
}

_BattleStack_* getEnemyStackToRedirectTo(_BattleStack_* stack, int spell)
{
    // В целях демонстрации работы просто перенаправим заклинание на первый доступный вражеский отряд
    int sideFoe = 1 - stack->side;
    _BattleStack_* stackFoe = stackIndex < o_BattleMgr->stacks_count[sideFoe] &&
        !o_BattleMgr->stack[sideFoe][stackIndex].is_killed ? &o_BattleMgr->stack[sideFoe][stackIndex] : 0;

    ++stackIndex;
    return stackFoe;
}

int __stdcall newTarget(LoHook* h, HookContext* c)
{
    // Если нужно изменить оригинальный выбор цели, пропишите указатель на отряд в eaх
    // (!) Обязательно, чтобы работало отражение синглкаста AI (!)
    // В этом примере заклинание перенаправляется на вражеский отряд с индексом 0, даже если он погиб,
    // так что здесь нужно написать свой алгоритм выбора цели
    c->eax = (int)&o_BattleMgr->stack[o_BattleMgr->current_side][0];
    return EXEC_DEFAULT;
}

int __stdcall skipOrigSound_dataInit(LoHook* h, HookContext* c)
{
    int spell = *(int*)(c->ebp + 8);
    char switchCase = ((char*)0x5A2B48)[spell - 10];

    // Если не синглкаст
    if ( !c->edi )
    {
        showMagicMirrorAnim = false;
        showMagicResistAnim = false;
        showSpellAnim = false;
        memset(needMagicMirrorAnim, false, 40);
        memset(needMagicResistAnim, false, 40);
        memset(needSpellAnim, false, 40);
        stackIndex = 0;

        if ( switchCase == 17 || switchCase == 24 || o_BattleMgr->ShouldNotRenderBattle() )
        {
            c->return_address = 0x5A0646;
            return NO_EXEC_DEFAULT;
        }
    }
       
    return EXEC_DEFAULT;
}

int __stdcall reflectSpell(LoHook* h, HookContext* c)
{
    _Hero_* hero;
    _BattleStack_* stack;
    int spell, spellPower, schoolLevel;
    int casterSide = o_BattleMgr->current_side;

    switch ( h->GetAddress() ) {
        case 0x5A6A50:
            stack = (_BattleStack_*)c->esi;
            spell = *(int*)(c->ebp + 0xC);
            spellPower = *(int*)(c->ebp + 0x14);
            schoolLevel = *(int*)(c->ebp + 0x10);
            hero = *(_Hero_**)(c->ebp + 8);
            c->return_address = 0x5A6A83;
            break;
        case 0x5A20F4:
            stack = *(_BattleStack_**)(c->ebp + 0x14);
            spell = *(int*)(c->ebp + 8);
            spellPower = *(int*)(c->ebp + 0x1C);
            schoolLevel = c->esi;
            hero = *(_Hero_**)(c->ebp - 0x14);
            c->return_address = 0x5A210E;
            break;
        case 0x5A4D74:
            stack = (_BattleStack_*)c->edi;
            spell = c->esi;
            if ( stack ) needSpellAnim[stack->side][stack->index_on_side] = true;
            c->return_address = 0x5A4D89;
            break;
    }
   
    if ( stack ) {
        // Если сработало Magic Mirror, перенаправляем заклинание на вражеский отряд
        if ( magicMirrorLucky(stack, spell) )
        {
            showMagicMirrorAnim = true;
            needMagicMirrorAnim[stack->side][stack->index_on_side] = true;
           
            // Если это каст Frost Ring, Fireball, Inferno или Meteor Shower,
            // не наносим урон и пока никуда не перенаправляем
            if ( h->GetAddress() == 0x5A4D74 ) {
                needSpellAnim[stack->side][stack->index_on_side] = false;
                return NO_EXEC_DEFAULT;
            }

            _BattleStack_* stackFoe = getEnemyStackToRedirectTo(stack, spell);
            if ( stackFoe )
            {
                // Определяем, сработает ли резист против отражённого заклинания
                int resistPercentage = 100 * CALL_7(float, __thiscall, 0x5A83A0, o_BattleMgr, spell,
                    casterSide, stackFoe, 1, 1, *(int*)(c->ebp + 0x1C));

                if ( Randint(1, 100) <= resistPercentage )
                {
                    CALL_5(void, __thiscall, 0x444610, stackFoe, spell, spellPower, schoolLevel, hero);
                    showSpellAnim = true;
                    needSpellAnim[stackFoe->side][stackFoe->index_on_side] = true;
                }
                else if ( resistPercentage )
                {
                    showMagicResistAnim = true;
                    needMagicResistAnim[stackFoe->side][stackFoe->index_on_side] = true;
                }
            }
        }
        else {
            if ( Randint(1, 100) <= c->eax )
            {
                // Если это каст Frost Ring, Fireball, Inferno или Meteor Shower,
                // выполняем стандартный алгоритм
                if ( h->GetAddress() == 0x5A4D74 ) {
                    c->return_address = 0x5A4DA8;
                    return NO_EXEC_DEFAULT;
                }
               
                // Если не сработал резист, применяем эффект заклинания
                CALL_5(void, __thiscall, 0x444610, stack, spell, spellPower, schoolLevel, hero);
                showSpellAnim = true;
                needSpellAnim[stack->side][stack->index_on_side] = true;
            }
            // Если сработал резист, отмечаем отряд как требующий анимации резиста
            else if ( c->eax )
            {
                showMagicResistAnim = true;
                needMagicResistAnim[stack->side][stack->index_on_side] = true;

                // Если это каст Frost Ring, Fireball, Inferno или Meteor Shower,
                // не наносим урон
                if ( h->GetAddress() == 0x5A4D74 ) {
                    needSpellAnim[stack->side][stack->index_on_side] = false;
                    return NO_EXEC_DEFAULT;
                }
            }
        }
    }
   
    return NO_EXEC_DEFAULT;
}

int __stdcall massReflection(LoHook* h, HookContext* c)
{
    // Проигрываем массовую анимацию и звук Magic Mirror
    if ( showMagicMirrorAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, o_Spell[SPL_MAGIC_MIRROR].wav_name, -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicMirrorAnim, o_Spell[SPL_MAGIC_MIRROR].animation_ix, 0);
    }

    // Проигрываем массовую анимацию и звук резиста
    if ( showMagicResistAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, "MagicRes.wav", -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicResistAnim, 78, 0);
    }

    // Проигрываем массовую анимацию и звук кастуемого заклинания
    if ( showSpellAnim )
    {
        _Spell_* spell = (_Spell_*)*(int*)(c->ebp - 0x10);
        CALL_3(void, __fastcall, 0x59A890, spell->wav_name, -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needSpellAnim, spell->animation_ix, 0);
    }
   
    c->return_address = h->GetAddress() == 0x5A2156 ? 0x5A2168 : 0x5A13FC;
    return NO_EXEC_DEFAULT;
}

void __stdcall AoEDamageReflection(HiHook* h, _BattleMgr_* battleMgr, int a1, int spell, int a3, int a4)
{
    // Пока не реализовали собственное проигрывание анимации нанесения урона,
    // копируем отряды, нуждающиеся в ней, в оригинальный массив с флагами анимации
    memcpy(AnimateStack, needSpellAnim, 40);

    CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, a1, spell, a3, a4);
   
    // Проигрываем массовую анимацию и звук Magic Mirror
    if ( showMagicMirrorAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, o_Spell[SPL_MAGIC_MIRROR].wav_name, -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicMirrorAnim, o_Spell[SPL_MAGIC_MIRROR].animation_ix, 0);
    }

    // Проигрываем массовую анимацию и звук резиста
    if ( showMagicResistAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, "MagicRes.wav", -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicResistAnim, 78, 0);
    }
}

int __stdcall playMagicMirrorSound(LoHook* h, HookContext* c)
{
    CALL_3(void, __fastcall, 0x59A890, o_Spell[SPL_MAGIC_MIRROR].wav_name, -1, 3);
   
    return EXEC_DEFAULT;
}

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((char*)"HD.Plugin.NewMagicMirrorTest");

            // Пропускаем оригинальный звук заклинания, чтобы воспроизвезти его позже,
            // и инициализируем данные
            _PI->WriteLoHook(0x5A0634, skipOrigSound_dataInit);

            // Синглкаст
            _PI->WriteLoHook(0x5A05CC, newTarget);
           
            // Масскаст
            _PI->WriteLoHook(0x5A6A50, reflectSpell);
            _PI->WriteLoHook(0x5A20F4, reflectSpell);
            _PI->WriteLoHook(0x5A4D74, reflectSpell);
            _PI->WriteLoHook(0x5A13E5, massReflection);
            _PI->WriteLoHook(0x5A2156, massReflection);

            // Frost Ring, Fireball, Inferno, Meteor Shower
            _PI->WriteHiHook(0x5A4C80, SPLICE_, EXTENDED_, THISCALL_, AoEDamageReflection);
           
            // Проигрываем звук Magic Mirror при отражении одиночного заклинания
            _PI->WriteLoHook(0x5A059A, playMagicMirrorSound);
        }
    }

    return TRUE;
}

Могут быть вылеты. Желательно их репортить.

* * *
Да, есть плавающий вылет для AoE, буду дебажить :smile4: Что-то с оригинальным массив флагов, похоже. Если будет вылет, то здесь, скорее всего: NewMagicMirror.dll+0x1033.

* * *
Жук оказался в самом неожиданном месте. Очень похоже на результат оптимизации кода компилятором. Он решил использовать более не нужный аргумент в качестве локальной переменной :!: Так что, будьте осторожны, используя аргументы функции и думая, что они не могут измениться. Например, в моём случае компилятор посчитал старший байт номера заклинания удобным местом для хранения какого-то флага (ещё один повод прописывать аргументы как const). Переписал код. Нужно заодно проверить, нет ли оптимизации аргументов в других случаях.
Вернуться к началу

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

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

Сообщение Rolex » 21 мар 2021, 21:24

Такой вопрос, а как правильно проверить, что текущий отряд есть стреляющий. По кол-ву оставшихся выстрелов не вариант, так как выстрелы могут закончится. Может есть функция?
Вернуться к началу

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 » 21 мар 2021, 21:28

Можете посмотреть его флаги. У стреляющего должен быть установлен shooter (можно в HoMM3_ids.h глянуть, как правильно).

Вот - BCF_CAN_SHOOT.

* * *
Так, забыл вернуть функцию magicMirrorAI(). Что ещё остаётся?

a) Перенаправление дамажащих прощадных заклинаний.
b) Отражение для Armageddon, Destroy Undead, Death Ripple.

Всё, кажется?

Для AI нужно ещё проверку на синглкаст сделать нормальную.
Последний раз редактировалось AlexSpl 21 мар 2021, 21:43, всего редактировалось 2 раз(а).
Вернуться к началу

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

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

Сообщение Rolex » 21 мар 2021, 21:42

AlexSpl писал(а):

Вот - BCF_CAN_SHOOT.

А как правильно будет проверить на этот флаг, если есть указатель на структтуру _BattleStack_* stack?

AlexSpl писал(а):

a) Перенаправление дамащащих проладных заклинаний.

?

AlexSpl писал(а):

b) Отражение/перенаправление для Armageddon, Destroy Undead, Death Ripple.

Ну не знаю только, что мы сможем сделать с Армагеддоном. Либо вообще не трогать, либо сделать так, как с Destroy Undead и Death Ripple.

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

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 » 21 мар 2021, 21:48

Цитата:
А как правильно будет проверить на этот флаг, если есть указатель на структтуру _BattleStack_* stack?

Так же, как и с другими флагами: if ( stack->creature.flags & BCF_CAN_SHOOT ).

Цитата:
Ну не знаю только, что мы сможем сделать с Армагеддоном. Либо вообще не трогать, либо сделать так, как с Destroy Undead и Death Ripple.

Да, сделать дополнительный резист в никуда.

* * *
 Версия с magicMirrorAI()
Код: Выделить всё
#define _CRT_SECURE_NO_WARNINGS
#include "stdafx.h"
#include "..\..\HotA\homm3.h"

Patcher* _P;
PatcherInstance* _PI;

bool showMagicMirrorAnim, showMagicResistAnim, showSpellAnim;
bool needMagicMirrorAnim[2][20], needMagicResistAnim[2][20], needSpellAnim[2][20];
int stackIndex;

#define AnimateStack (((bool(*)[20])&o_BattleMgr->Field<bool>(0x547C)))

bool magicMirrorLucky(_BattleStack_* stack, int spell)
{
    int effSchoolLevel = CALL_3(int, __thiscall, 0x4E52F0, o_BattleMgr->hero[stack->side], SPL_MAGIC_MIRROR, o_BattleMgr->spec_terr_type);
   
    return !(o_Spell[spell].flags & SPF_FRIENDLY_HAS_MASS) && (stack->side != o_BattleMgr->current_side || spell == SPL_BERSERK ) &&
        stack->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(1, 100) <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel];
}

_BattleStack_* getEnemyStackToRedirectTo(_BattleStack_* stack, int spell)
{
    // В целях демонстрации работы просто перенаправим заклинание на первый доступный вражеский отряд
    int sideFoe = 1 - stack->side;
    _BattleStack_* stackFoe = stackIndex < o_BattleMgr->stacks_count[sideFoe] &&
        !o_BattleMgr->stack[sideFoe][stackIndex].is_killed ? &o_BattleMgr->stack[sideFoe][stackIndex] : 0;

    ++stackIndex;
    return stackFoe;
}

int __stdcall newTarget(LoHook* h, HookContext* c)
{
    // Если нужно изменить оригинальный выбор цели, пропишите указатель на отряд в eaх
    // (!) Обязательно, чтобы работало отражение синглкаста AI (!)
    // В этом примере заклинание перенаправляется на вражеский отряд с индексом 0, даже если он погиб,
    // так что здесь нужно написать свой алгоритм выбора цели
    c->eax = (int)&o_BattleMgr->stack[o_BattleMgr->current_side][0];
    return EXEC_DEFAULT;
}

int __stdcall magicMirrorAI(LoHook* h, HookContext* c)
{
    _BattleStack_* stack = (_BattleStack_*)c->edi;
   
    if ( stack ) {
        _Hero_* hero = o_BattleMgr->hero[1 - o_BattleMgr->current_side];
        int spell = *(int*)(c->ebp + 8);
        int effSchoolLevel = CALL_3(int, __thiscall, 0x4E52F0, hero, SPL_MAGIC_MIRROR, o_BattleMgr->spec_terr_type);
       
        // Если герой - AI, на отряде висит Magic Mirror и оно сработало, а также оригинальные проверки на Teleport и Sacrifice
        if ( !CALL_2(bool, __thiscall, 0x4CE600, o_GameMgr, o_BattleMgr->owner_id[o_BattleMgr->current_side]) &&
            stack->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(1, 100) <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel] &&
            spell != SPL_TELEPORT && spell != SPL_SACRIFICE )
        {
            // Проигрываем одиночную анимацию
            CALL_5(void, __thiscall, 0x4963C0, o_BattleMgr, o_Spell[SPL_MAGIC_MIRROR].animation_ix, c->edi, 100, 0);

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

    return EXEC_DEFAULT;
}

int __stdcall skipOrigSound_dataInit(LoHook* h, HookContext* c)
{
    int spell = *(int*)(c->ebp + 8);
    char switchCase = ((char*)0x5A2B48)[spell - 10];

    // Если не синглкаст
    if ( !c->edi )
    {
        showMagicMirrorAnim = false;
        showMagicResistAnim = false;
        showSpellAnim = false;
        memset(needMagicMirrorAnim, false, 40);
        memset(needMagicResistAnim, false, 40);
        memset(needSpellAnim, false, 40);
        stackIndex = 0;

        if ( switchCase == 17 || switchCase == 24 || o_BattleMgr->ShouldNotRenderBattle() )
        {
            c->return_address = 0x5A0646;
            return NO_EXEC_DEFAULT;
        }
    }
       
    return EXEC_DEFAULT;
}

int __stdcall reflectSpell(LoHook* h, HookContext* c)
{
    _Hero_* hero;
    _BattleStack_* stack;
    int spell, spellPower, schoolLevel;
    int casterSide = o_BattleMgr->current_side;

    switch ( h->GetAddress() ) {
        case 0x5A6A50:
            stack = (_BattleStack_*)c->esi;
            spell = *(int*)(c->ebp + 0xC);
            spellPower = *(int*)(c->ebp + 0x14);
            schoolLevel = *(int*)(c->ebp + 0x10);
            hero = *(_Hero_**)(c->ebp + 8);
            c->return_address = 0x5A6A83;
            break;
        case 0x5A20F4:
            stack = *(_BattleStack_**)(c->ebp + 0x14);
            spell = *(int*)(c->ebp + 8);
            spellPower = *(int*)(c->ebp + 0x1C);
            schoolLevel = c->esi;
            hero = *(_Hero_**)(c->ebp - 0x14);
            c->return_address = 0x5A210E;
            break;
        case 0x5A4D74:
            stack = (_BattleStack_*)c->edi;
            spell = c->esi;
            if ( stack ) needSpellAnim[stack->side][stack->index_on_side] = true;
            c->return_address = 0x5A4D89;
            break;
    }
   
    if ( stack ) {
        // Если сработало Magic Mirror, перенаправляем заклинание на вражеский отряд
        if ( magicMirrorLucky(stack, spell) )
        {
            showMagicMirrorAnim = true;
            needMagicMirrorAnim[stack->side][stack->index_on_side] = true;
           
            // Если это каст Frost Ring, Fireball, Inferno или Meteor Shower,
            // не наносим урон и пока никуда не перенаправляем
            if ( h->GetAddress() == 0x5A4D74 ) {
                needSpellAnim[stack->side][stack->index_on_side] = false;
                return NO_EXEC_DEFAULT;
            }

            _BattleStack_* stackFoe = getEnemyStackToRedirectTo(stack, spell);
            if ( stackFoe )
            {
                // Определяем, сработает ли резист против отражённого заклинания
                int resistPercentage = 100 * CALL_7(float, __thiscall, 0x5A83A0, o_BattleMgr, spell,
                    casterSide, stackFoe, 1, 1, *(int*)(c->ebp + 0x1C));

                if ( Randint(1, 100) <= resistPercentage )
                {
                    CALL_5(void, __thiscall, 0x444610, stackFoe, spell, spellPower, schoolLevel, hero);
                    showSpellAnim = true;
                    needSpellAnim[stackFoe->side][stackFoe->index_on_side] = true;
                }
                else if ( resistPercentage )
                {
                    showMagicResistAnim = true;
                    needMagicResistAnim[stackFoe->side][stackFoe->index_on_side] = true;
                }
            }
        }
        else {
            if ( Randint(1, 100) <= c->eax )
            {
                // Если это каст Frost Ring, Fireball, Inferno или Meteor Shower,
                // выполняем стандартный алгоритм
                if ( h->GetAddress() == 0x5A4D74 ) {
                    c->return_address = 0x5A4DA8;
                    return NO_EXEC_DEFAULT;
                }
               
                // Если не сработал резист, применяем эффект заклинания
                CALL_5(void, __thiscall, 0x444610, stack, spell, spellPower, schoolLevel, hero);
                showSpellAnim = true;
                needSpellAnim[stack->side][stack->index_on_side] = true;
            }
            // Если сработал резист, отмечаем отряд как требующий анимации резиста
            else if ( c->eax )
            {
                showMagicResistAnim = true;
                needMagicResistAnim[stack->side][stack->index_on_side] = true;

                // Если это каст Frost Ring, Fireball, Inferno или Meteor Shower,
                // не наносим урон
                if ( h->GetAddress() == 0x5A4D74 ) {
                    needSpellAnim[stack->side][stack->index_on_side] = false;
                    return NO_EXEC_DEFAULT;
                }
            }
        }
    }
   
    return NO_EXEC_DEFAULT;
}

int __stdcall massReflection(LoHook* h, HookContext* c)
{
    // Проигрываем массовую анимацию и звук Magic Mirror
    if ( showMagicMirrorAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, o_Spell[SPL_MAGIC_MIRROR].wav_name, -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicMirrorAnim, o_Spell[SPL_MAGIC_MIRROR].animation_ix, 0);
    }

    // Проигрываем массовую анимацию и звук резиста
    if ( showMagicResistAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, "MagicRes.wav", -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicResistAnim, 78, 0);
    }

    // Проигрываем массовую анимацию и звук кастуемого заклинания
    if ( showSpellAnim )
    {
        _Spell_* spell = (_Spell_*)*(int*)(c->ebp - 0x10);
        CALL_3(void, __fastcall, 0x59A890, spell->wav_name, -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needSpellAnim, spell->animation_ix, 0);
    }
   
    c->return_address = h->GetAddress() == 0x5A2156 ? 0x5A2168 : 0x5A13FC;
    return NO_EXEC_DEFAULT;
}

void __stdcall AoEDamageReflection(HiHook* h, _BattleMgr_* battleMgr, int a1, int spell, int a3, int a4)
{
    // Пока не реализовали собственное проигрывание анимации нанесения урона,
    // копируем отряды, нуждающиеся в ней, в оригинальный массив с флагами анимации
    memcpy(AnimateStack, needSpellAnim, 40);

    CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, a1, spell, a3, a4);
   
    // Проигрываем массовую анимацию и звук Magic Mirror
    if ( showMagicMirrorAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, o_Spell[SPL_MAGIC_MIRROR].wav_name, -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicMirrorAnim, o_Spell[SPL_MAGIC_MIRROR].animation_ix, 0);
    }

    // Проигрываем массовую анимацию и звук резиста
    if ( showMagicResistAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, "MagicRes.wav", -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicResistAnim, 78, 0);
    }
}

int __stdcall playMagicMirrorSound(LoHook* h, HookContext* c)
{
    CALL_3(void, __fastcall, 0x59A890, o_Spell[SPL_MAGIC_MIRROR].wav_name, -1, 3);
   
    return EXEC_DEFAULT;
}

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((char*)"HD.Plugin.NewMagicMirrorTest");

            // Пропускаем оригинальный звук заклинания, чтобы воспроизвезти его позже,
            // и инициализируем данные
            _PI->WriteLoHook(0x5A0634, skipOrigSound_dataInit);

            // Синглкаст
            _PI->WriteLoHook(0x5A05CC, newTarget);
            _PI->WriteLoHook(0x5A058B, magicMirrorAI);
           
            // Масскаст
            _PI->WriteLoHook(0x5A6A50, reflectSpell);
            _PI->WriteLoHook(0x5A20F4, reflectSpell);
            _PI->WriteLoHook(0x5A4D74, reflectSpell);
            _PI->WriteLoHook(0x5A13E5, massReflection);
            _PI->WriteLoHook(0x5A2156, massReflection);

            // Frost Ring, Fireball, Inferno, Meteor Shower
            _PI->WriteHiHook(0x5A4C80, SPLICE_, EXTENDED_, THISCALL_, AoEDamageReflection);
           
            // Проигрываем звук Magic Mirror при отражении одиночного заклинания
            _PI->WriteLoHook(0x5A059A, playMagicMirrorSound);
        }
    }

    return TRUE;
}

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

Кстати, нужно ещё тестить отражение заклинаний нейтралов (отрядов без героя).
Последний раз редактировалось AlexSpl 22 мар 2021, 00:10, всего редактировалось 1 раз.
Вернуться к началу

offlineArmageddets  
Новичок
Новичок
 
Сообщения: 21
Зарегистрирован: 31 окт 2018, 15:05
Пол: Не указан
Поблагодарили: 2 раз.

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

Сообщение Armageddets » 22 мар 2021, 00:08

Только что закончили играть 2 игры с палаткой - в обеих в финалке крашится игра на ходе палатки. Если плагин палатки убрать - крашей нет. Да - все игры идут с фреш модом версии 6.3 (https://dropmefiles.com/WCq0y). Краш лог скину сегодня- завтра (как соперник скинет). Видео тоже появится днем после стрима, на него скину ссылку тоже. На счет показана краш лога первого автору Фреш мода - хорошо, у меня есть с ним связь.
Вернуться к началу

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 » 22 мар 2021, 00:11

Пробовали играть с новой версией плагина?

Загрузил сейв в хотсите. Полёт нормальный. Чтобы понять, где проблема (если, конечно, у обоих игроков была последняя версия плагина), нужна последовательность ходов отрядов и каста героев, которая приводит к вылету. Ещё, для чистоты эксперимента, можете провести битву только с Fresh Mod и плагином на массхил. Я посмотрел список подключённых плагинов у игрока Prince, подключено сразу два Fresh Mod (1.0 и 6.3). Не знаю, так нужно было или нет.
Вернуться к началу

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 » 22 мар 2021, 03:50

 Death Ripple, Destroy Undead, Armageddon + примитивное отражение AoE (для тестов)
Код: Выделить всё
#define _CRT_SECURE_NO_WARNINGS
#include "stdafx.h"
#include "..\..\HotA\homm3.h"

Patcher* _P;
PatcherInstance* _PI;

bool showMagicMirrorAnim, showMagicResistAnim, showSpellAnim, redirectAreaSpell;
bool needMagicMirrorAnim[2][20], needMagicResistAnim[2][20], needSpellAnim[2][20];
int stackIndex;

#define SPL_DEATH_RIPPLE 24
#define AnimateStack (((bool(*)[20])&o_BattleMgr->Field<bool>(0x547C)))

enum E {E_MASS, E_BERSERK, E_AREA, E_DEATH_RIPPLE, E_DESTROY_UNDEAD, E_ARMAGEDDON, E_SIZE};
enum A {A_REFLECTSPELL_HOOK, A_MASSREFLECTION_HOOK, A_REFLECTSPELL_SKIP, A_REFLECTSPELL_DEFAULT, A_MASSANIMATION};

const _ptr_ hAddr[][E_SIZE] = {
    {0x5A6A50, 0x5A20F4, 0x5A4D74, 0x5A1006, 0x5A1203, 0x5A4F4E}, // A_REFLECTSPELL_HOOK
    {0x5A13E5, 0x5A2156, 0x5A4C80, 0x5A1151, 0x5A12BD, 0x5A4FFF}, // A_MASSREFLECTION_HOOK
    {0x5A6A83, 0x5A210E, 0x5A4D89, 0x5A107B, 0x5A1278, 0x5A4FBB}, // A_REFLECTSPELL_SKIP
    {0x000000, 0x000000, 0x5A4DA8, 0x5A1020, 0x5A121D, 0x5A4F63}, // A_REFLECTSPELL_DEFAULT
    {0x5A13FC, 0x5A2168, 0x000000, 0x5A116F, 0x5A12E0, 0x000000}  // A_MASSANIMATION
};

_ptr_ getRA(const int spell)
{
    _ptr_ retAddr;

    switch ( spell )
    {
        case SPL_FROST_RING: case SPL_FIREBALL: case SPL_INFERNO: case SPL_METEOR_SHOWER:
            retAddr = hAddr[A_REFLECTSPELL_DEFAULT][E_AREA];
            break;
        case SPL_DEATH_RIPPLE:
            retAddr = hAddr[A_REFLECTSPELL_DEFAULT][E_DEATH_RIPPLE];
            break;
        case SPL_DESTROY_UNDEAD:
            retAddr = hAddr[A_REFLECTSPELL_DEFAULT][E_DESTROY_UNDEAD];
            break;
        case SPL_ARMAGEDDON:
            retAddr = hAddr[A_REFLECTSPELL_DEFAULT][E_ARMAGEDDON];
            break;
    }

    return retAddr;
}

bool magicMirrorLucky(const _BattleStack_* stack, const int spell)
{
    int effSchoolLevel = CALL_3(int, __thiscall, 0x4E52F0, o_BattleMgr->hero[stack->side], SPL_MAGIC_MIRROR, o_BattleMgr->spec_terr_type);
   
    return !(o_Spell[spell].flags & SPF_FRIENDLY_HAS_MASS) && (stack->side != o_BattleMgr->current_side ||
        spell == SPL_FROST_RING || spell == SPL_FIREBALL || spell == SPL_INFERNO || spell == SPL_METEOR_SHOWER ||
        spell == SPL_BERSERK || spell == SPL_DEATH_RIPPLE || spell == SPL_DESTROY_UNDEAD || spell == SPL_ARMAGEDDON) &&
        stack->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(1, 100) <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel];
}

_BattleStack_* getEnemyStackToRedirectTo(const _BattleStack_* stack, const int spell)
{
    // В целях демонстрации работы просто перенаправим заклинание на первый доступный вражеский отряд
    int sideFoe = 1 - stack->side;
    _BattleStack_* stackFoe = stackIndex < o_BattleMgr->stacks_count[sideFoe] &&
        !o_BattleMgr->stack[sideFoe][stackIndex].is_killed ? &o_BattleMgr->stack[sideFoe][stackIndex] : 0;

    ++stackIndex;
    return stackFoe;
}

int __stdcall newTarget(LoHook* h, HookContext* c)
{
    // Если нужно изменить оригинальный выбор цели, пропишите указатель на отряд в eaх
    // (!) Обязательно, чтобы работало отражение синглкаста AI (!)
    // В этом примере заклинание перенаправляется на вражеский отряд с индексом 0, даже если он погиб,
    // так что здесь нужно написать свой алгоритм выбора цели
    c->eax = (int)&o_BattleMgr->stack[o_BattleMgr->current_side][0];
    return EXEC_DEFAULT;
}

int __stdcall magicMirrorAI(LoHook* h, HookContext* c)
{
    _BattleStack_* stack = (_BattleStack_*)c->edi;
   
    if ( stack )
    {
        _Hero_* hero = o_BattleMgr->hero[1 - o_BattleMgr->current_side];
        int spell = *(int*)(c->ebp + 8);
        int effSchoolLevel = CALL_3(int, __thiscall, 0x4E52F0, hero, SPL_MAGIC_MIRROR, o_BattleMgr->spec_terr_type);
       
        // Если герой - AI, на отряде висит Magic Mirror и оно сработало, а также оригинальные проверки на Teleport и Sacrifice
        if ( !CALL_2(bool, __thiscall, 0x4CE600, o_GameMgr, o_BattleMgr->owner_id[o_BattleMgr->current_side]) &&
            stack->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(1, 100) <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel] &&
            spell != SPL_TELEPORT && spell != SPL_SACRIFICE )
        {
            // Проигрываем одиночную анимацию
            CALL_5(void, __thiscall, 0x4963C0, o_BattleMgr, o_Spell[SPL_MAGIC_MIRROR].animation_ix, c->edi, 100, 0);

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

    return EXEC_DEFAULT;
}

int __stdcall skipOrigSound_dataInit(LoHook* h, HookContext* c)
{
    int spell = *(int*)(c->ebp + 8);
    char switchCase = ((char*)0x5A2B48)[spell - 10];

    // Если не синглкаст
    if ( !c->edi )
    {
        showMagicMirrorAnim = false;
        showMagicResistAnim = false;
        showSpellAnim = false;
        redirectAreaSpell = false;
        memset(needMagicMirrorAnim, false, 40);
        memset(needMagicResistAnim, false, 40);
        memset(needSpellAnim, false, 40);
        stackIndex = 0;

        if ( spell == SPL_DEATH_RIPPLE || spell == SPL_DESTROY_UNDEAD || spell == SPL_ARMAGEDDON ||
            switchCase == 17 || switchCase == 24 || o_BattleMgr->ShouldNotRenderBattle() )
        {
            c->return_address = 0x5A0646;
            return NO_EXEC_DEFAULT;
        }
    }
       
    return EXEC_DEFAULT;
}

int __stdcall reflectSpell(LoHook* h, HookContext* c)
{
    _Hero_* hero;
    _BattleStack_* stack;
    int spell, spellPower, schoolLevel;
    int casterSide = o_BattleMgr->current_side;

    _ptr_ addr = h->GetAddress();
   
    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_MASS] )
    {
        stack = (_BattleStack_*)c->esi;
        spell = *(int*)(c->ebp + 0xC);
        spellPower = *(int*)(c->ebp + 0x14);
        schoolLevel = *(int*)(c->ebp + 0x10);
        hero = *(_Hero_**)(c->ebp + 8);
        c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_MASS];
    }

    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_BERSERK] )
    {
        stack = *(_BattleStack_**)(c->ebp + 0x14);
        spell = *(int*)(c->ebp + 8);
        spellPower = *(int*)(c->ebp + 0x1C);
        schoolLevel = c->esi;
        hero = *(_Hero_**)(c->ebp - 0x14);
        c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_BERSERK];
    }

    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_AREA] )
    {
        stack = (_BattleStack_*)c->edi;
        spell = c->esi;
        schoolLevel = *(int*)(c->ebp - 0x44);
        c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_AREA];
    }

    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_DEATH_RIPPLE] )
    {
        stack = (_BattleStack_*)c->edi;
        spell = SPL_DEATH_RIPPLE;
        c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_DEATH_RIPPLE];
    }

    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_DESTROY_UNDEAD] )
    {
        stack = (_BattleStack_*)c->edi;
        spell = SPL_DESTROY_UNDEAD;
        c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_DESTROY_UNDEAD];
    }

    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_ARMAGEDDON] )
    {
        stack = (_BattleStack_*)c->esi;
        spell = SPL_ARMAGEDDON;
        c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_ARMAGEDDON];
    }
   
    if ( stack )
    {
        bool isSpellSpecial = spell == SPL_FROST_RING || spell == SPL_FIREBALL || spell == SPL_INFERNO || spell == SPL_METEOR_SHOWER ||
             spell == SPL_DEATH_RIPPLE || spell == SPL_DESTROY_UNDEAD || spell == SPL_ARMAGEDDON;

        if ( magicMirrorLucky(stack, spell) )
        {
            showMagicMirrorAnim = true;
            needMagicMirrorAnim[stack->side][stack->index_on_side] = true;
           
            redirectAreaSpell = addr == hAddr[A_REFLECTSPELL_HOOK][E_AREA];
            if ( isSpellSpecial ) return NO_EXEC_DEFAULT;

            // Если сработало Magic Mirror, перенаправляем заклинание на вражеский отряд
            _BattleStack_* stackFoe = getEnemyStackToRedirectTo(stack, spell);
            if ( stackFoe )
            {
                // Определяем, сработает ли резист против отражённого заклинания
                int hitPercentage = (int)(100 * CALL_7(float, __thiscall, 0x5A83A0, o_BattleMgr, spell,
                    casterSide, stackFoe, 1, 1, *(int*)(c->ebp + 0x1C)));

                if ( Randint(1, 100) <= hitPercentage )
                {
                    showSpellAnim = true;
                    needSpellAnim[stackFoe->side][stackFoe->index_on_side] = true;
                    CALL_5(void, __thiscall, 0x444610, stackFoe, spell, spellPower, schoolLevel, hero);
                }
                else if ( hitPercentage )
                {
                    showMagicResistAnim = true;
                    needMagicResistAnim[stackFoe->side][stackFoe->index_on_side] = true;
                }
            }
        }
        else {
            if ( Randint(1, 100) <= c->eax )
            {
                showSpellAnim = true;
                needSpellAnim[stack->side][stack->index_on_side] = true;

                if ( isSpellSpecial )
                {
                    c->return_address = getRA(spell);
                    return NO_EXEC_DEFAULT;
                }
               
                // Если не сработал резист, применяем эффект заклинания
                CALL_5(void, __thiscall, 0x444610, stack, spell, spellPower, schoolLevel, hero);
            }
            else if ( c->eax )
            {
                showMagicResistAnim = true;
                needMagicResistAnim[stack->side][stack->index_on_side] = true;

                if ( isSpellSpecial ) return NO_EXEC_DEFAULT;
            }
        }
    }
   
    return NO_EXEC_DEFAULT;
}

int __stdcall massReflection(LoHook* h, HookContext* c)
{
    _ptr_ addr = h->GetAddress();
   
    if ( addr == hAddr[A_MASSREFLECTION_HOOK][E_MASS] )
        c->return_address = hAddr[A_MASSANIMATION][E_MASS];
   
    if ( addr == hAddr[A_MASSREFLECTION_HOOK][E_BERSERK] )
        c->return_address = hAddr[A_MASSANIMATION][E_BERSERK];
   
    if ( addr == hAddr[A_MASSREFLECTION_HOOK][E_DEATH_RIPPLE] )
        c->return_address = hAddr[A_MASSANIMATION][E_DEATH_RIPPLE];
   
    if ( addr == hAddr[A_MASSREFLECTION_HOOK][E_DESTROY_UNDEAD] ) {
        c->edi = *(int*)(c->ebp - 0x10);
        c->return_address = hAddr[A_MASSANIMATION][E_DESTROY_UNDEAD];
    }

    // Проигрываем массовую анимацию и звук Magic Mirror
    if ( showMagicMirrorAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, o_Spell[SPL_MAGIC_MIRROR].wav_name, -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicMirrorAnim, o_Spell[SPL_MAGIC_MIRROR].animation_ix, 0);
    }

    // Проигрываем массовую анимацию и звук резиста
    if ( showMagicResistAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, "MagicRes.wav", -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicResistAnim, 78, 0);
    }

    if ( addr == hAddr[A_MASSREFLECTION_HOOK][E_ARMAGEDDON] )
    {
        memcpy(AnimateStack, needSpellAnim, 40);
        return EXEC_DEFAULT;
    }

    // Проигрываем массовую анимацию и звук кастуемого заклинания
    if ( showSpellAnim )
    {
        _Spell_* spell = (_Spell_*)*(int*)(c->ebp - 0x10);
        CALL_3(void, __fastcall, 0x59A890, spell->wav_name, -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needSpellAnim, spell->animation_ix, 0);
    }
   
    return NO_EXEC_DEFAULT;
}

void __stdcall areaReflection(HiHook* h, _BattleMgr_* battleMgr, int a1, int spell, int a3, int a4)
{
    memcpy(AnimateStack, needSpellAnim, 40);
    CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, a1, spell, a3, a4);
   
    // Проигрываем массовую анимацию и звук резиста
    if ( showMagicResistAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, "MagicRes.wav", -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicResistAnim, 78, 0);
    }

    // Проигрываем массовую анимацию и звук Magic Mirror
    if ( showMagicMirrorAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, o_Spell[SPL_MAGIC_MIRROR].wav_name, -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicMirrorAnim, o_Spell[SPL_MAGIC_MIRROR].animation_ix, 0);
    }

    // Если заклинание отражено
    if ( redirectAreaSpell )
    {
        showMagicMirrorAnim = false;
        showMagicResistAnim = false;
        memset(needMagicMirrorAnim, false, 40);
        memset(needMagicResistAnim, false, 40);

        CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, o_BattleMgr->stack[o_BattleMgr->current_side][0].hex_ix, spell, a3, a4);

        // Проигрываем массовую анимацию и звук резиста
        if ( showMagicResistAnim )
        {
            CALL_3(void, __fastcall, 0x59A890, "MagicRes.wav", -1, 3);
            CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicResistAnim, 78, 0);
        }

        // Проигрываем массовую анимацию и звук Magic Mirror
        if ( showMagicMirrorAnim )
        {
            CALL_3(void, __fastcall, 0x59A890, o_Spell[SPL_MAGIC_MIRROR].wav_name, -1, 3);
            CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicMirrorAnim, o_Spell[SPL_MAGIC_MIRROR].animation_ix, 0);
        }
    }
}

int __stdcall playMagicMirrorSound(LoHook* h, HookContext* c)
{
    CALL_3(void, __fastcall, 0x59A890, o_Spell[SPL_MAGIC_MIRROR].wav_name, -1, 3);
   
    return EXEC_DEFAULT;
}

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((char*)"HD.Plugin.NewMagicMirrorTest");

            // Пропускаем оригинальный звук заклинания, чтобы воспроизвезти его позже, и инициализируем данные
            _PI->WriteLoHook(0x5A0634, skipOrigSound_dataInit);

            // Синглкаст
            _PI->WriteLoHook(0x5A05CC, newTarget);
            _PI->WriteLoHook(0x5A058B, magicMirrorAI);
           
            // Масскаст
            for (int i = 0; i < E_SIZE; ++i) {
                _PI->WriteLoHook(hAddr[A_REFLECTSPELL_HOOK][i], reflectSpell);
                if ( i == E_AREA )
                    _PI->WriteHiHook(hAddr[A_MASSREFLECTION_HOOK][i], SPLICE_, EXTENDED_, THISCALL_, areaReflection);
                else
                    _PI->WriteLoHook(hAddr[A_MASSREFLECTION_HOOK][i], massReflection);
            }

            // Проигрываем звук Magic Mirror при отражении одиночного заклинания
            _PI->WriteLoHook(0x5A059A, playMagicMirrorSound);
        }
    }

    return TRUE;
}

Попробовал собрать все адреса в один массив. Надеюсь, ничего не напутал. Во-первых, текст плагина становится короче, а во-вторых, если нужно будет поменять адрес хука, например, достаточно будет исправить его в одном месте, а не искать по всему коду.

Также поменял порядок анимации для AoE спеллов: теперь сначала идёт анимация заклинания, потом урона, затем резиста и только в конце - зеркала. Так как планируется отражать AoE заклинания, то логично проигрывать анимацию зеркала после анимации резиста, потому что сразу после анимации зеркала начинается точно такая же последовательность анимаций, но уже для отражённого спелла.

* * *
Добавил тривиальное отражение AoE: на стек противника с индексом 0. Как бы скелет плагина готов. Осталось написать грамотные алгоритмы выбора цели (в том числе, эпицентра отражённого AoE закла*), оптимизировать код и исправить возможные баги :smile11:

* Здесь Вам всего лишь нужно выбрать гекс-эпицентр отражённого заклинания и передать его вот сюда:

Код: Выделить всё
CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, o_BattleMgr->stack[o_BattleMgr->current_side][0].hex_ix, spell, a3, a4);

Поле имеет размер 17 x 11 гексов. 0-я и 16-я вертикали - для боевых машин. Нужно написать функцию, которая будет проходить по всем гексам поля, и вычислять кол-во вражеских отрядов в нужном радиусе от текущего гекса.
Вернуться к началу

Пред.След.

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

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

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

cron