Объявления

Друзья, если не получается зарегистрироваться, напишите на почту 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 » 09 апр 2021, 15:02

Так. Много текста. Попробую пробежаться чуть позже и ответить, но некоторые пункты уже точно исправлены, а про птиц я первый раз слышу, что их молния должна отражаться только на них самих. Как бы, мы разделили абилки и магию (а у птиц - магия Lightning Bolt). Но если реально нужно, чтобы отражало только на птиц, не вопрос. Сделаем.
Вернуться к началу

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

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

Сообщение Rolex » 09 апр 2021, 15:20

AlexSpl писал(а):

а про птиц я первый раз слышу, что их молния должна отражаться только на них самих. Как бы, мы разделили абилки и магию (а у птиц - магия Lightning Bolt). Но если реально нужно, чтобы отражало только на птиц, не вопрос. Сделаем.

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

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 » 09 апр 2021, 16:24

Цитата:
7) А вот абилка Проклятие (Мумий, Черных и Зловещих рыцарей) вообще не работает и не отражается. Здесь порядок должен быть такой, как с Ударом молнии. Нанесли атаку отряду под Зеркалом, потом анимация Зеркала на целевом отряде, потом анимация Проклятия уже на отряде, который атаковал, и потом уже ответка целевого отразившего закл отряда.
Если же первым атакует отряд под Зеркалом, и абилка срабатывает во время ответки. То тогда получается сделали ответку, потом проигралась анимация Зеркала (на отряде под Зеркалом, который атаковал первым), а потом анимация Проклятия уже на отряде, который давал ответку.

Прикольно, что хотя и была ошибка в коде (addrHit должен равняться 0x440482), но толку от такого перенаправления после Вашего уточнения насчёт Thunderbirds не будет (на нежить не действует Curse) :smile1: Может быть, разве с Orb of Vulnerability.
Вернуться к началу

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

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

Сообщение Rolex » 09 апр 2021, 18:46

Цитата:
Прикольно, что хотя и была ошибка в коде (addrHit должен равняться 0x440482), но толку от такого перенаправления после Вашего уточнения насчёт Thunderbirds не будет (на нежить не действует Curse) :smile1: Может быть, разве с Orb of Vulnerability.

Ах да, точно, забыл, что Проклятие не действует на нежить. Но если его убрать, то тогда мы даже со Сферой уязвимости не увидим его в работе. Так что пускай будет (только исправьте addrHit ---> 0x440482).
Главное сейчас все остальные пункты по списку внимательно и тщательно проработать и ... все, плагин готов. :smile1:
Вернуться к началу

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

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

Сообщение Armageddets » 09 апр 2021, 21:38

Доброго времени суток. Нашлась еще одна проблема с палаткой, которая массово лечит - вылетает краш. Предыдущие проблемы решились обновлением плагина.

Очередность действий можно глянуть тут:
https://www.twitch.tv/videos/981538534

Краш логи, сохранения и сборник плагинов тут:
https://dropmefiles.com/xondN
Вернуться к началу

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 » 10 апр 2021, 10:35

Вчера понял где. Крэш-лог помог. Постараюсь исправить. Думаю, в массив нужно отправлять не индексы, а стеки целиком. Потому что теперь findTargetsToHeal() отрабатывает хорошо, а уже в applyHealing() - чтение потерянного здоровья несуществующего отряда. Я пока не понимаю, как это вообще возможно (может быть, какие-то особенности боя онлайн), но буду разбираться.

Доработанный код.

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

Patcher* _P;
PatcherInstance* _PI;

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

struct HealingInfo
{
    _BattleStack_* stack;
    int removedDamage;
} healingInfo[21 + 1];

int __stdcall afterInit(LoHook* h, HookContext* c)
{
    o_pCreatureInfo[CID_FIRST_AID_TENT].hit_points = 300;
    o_pCreatureInfo[CID_FIRST_AID_TENT].defence = 10;
    o_pCreatureInfo[CID_FIRST_AID_TENT].cost.gold = 1500;
   
    return EXEC_DEFAULT;
}

int __stdcall findTargetsToHeal(LoHook* h, HookContext* c)
{
    _Hero_* hero = o_BattleMgr->hero[o_BattleMgr->current_side];

    if ( hero && hero->second_skill[HSS_FIRST_AID] )
    {
        int n = 0;
        bool needHealing = false;
       
        for (int i = 0; i < o_BattleMgr->stacks_count[o_BattleMgr->current_side]; ++i)
        {
            _BattleStack_* stack = &o_BattleMgr->stack[o_BattleMgr->current_side][i];
       
            if ( stack && !stack->is_killed )
            {
                bool isSubjectToBeHealed = false;
               
                if ( stack->lost_hp ) {
                    switch ( hero->second_skill[HSS_FIRST_AID] ) {
                        case 1: isSubjectToBeHealed = !(stack->creature.flags & 0x200040); break;
                        case 2: isSubjectToBeHealed = !(stack->creature.flags & 0x200000) && stack->creature_id != CID_FIRST_AID_TENT; break;
                        case 3: isSubjectToBeHealed = !(stack->creature.flags & 0x200000); break;
                    }
                }

                if ( isSubjectToBeHealed )
                {
                    needHealing = true;
                    healingInfo[n++].stack = stack;

                    // Даём алгоритму игры валидную цель для хила
                    *(int*)(c->ebp - 8) = i;
                }
            }
        }
   
        healingInfo[n].stack = (_BattleStack_*)ID_NONE;

        c->return_address = needHealing ? 0x4738C3 : 0x4738B3;
        return NO_EXEC_DEFAULT;
    }

    return EXEC_DEFAULT;
}

int __stdcall applyHealing(LoHook* h, HookContext* c)
{
    _Hero_* hero = o_BattleMgr->hero[o_BattleMgr->current_side];

    if ( hero && hero->second_skill[HSS_FIRST_AID] )
    {
        for (int i = 0; healingInfo[i].stack != (_BattleStack_*)ID_NONE; ++i)
        {
            _BattleStack_* stack = healingInfo[i].stack;
           
            if ( stack && !stack->is_killed )
            {
                if ( c->eax < stack->lost_hp ) {
                    healingInfo[i].removedDamage = c->eax;
                    stack->lost_hp -= c->eax;
                }
                else {
                    healingInfo[i].removedDamage = stack->lost_hp;
                    stack->lost_hp = 0;
                }
            }
        }

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

    return EXEC_DEFAULT;
}

int __stdcall playAnimation(LoHook* h, HookContext* c)
{
    _Hero_* hero = o_BattleMgr->hero[o_BattleMgr->current_side];

    if ( hero && hero->second_skill[HSS_FIRST_AID] )
    {
        memset(AnimateStack, false, 40);
       
        // Пишем лог и проигрываем анимацию для всех вылеченных отрядов
        for (int i = 0; healingInfo[i].stack != (_BattleStack_*)ID_NONE; ++i)
        {
            _BattleStack_* stack = healingInfo[i].stack;

            if ( stack && !stack->is_killed )
            {
                sprintf(o_TextBuffer, o_GENRLTXT_TXT->GetString(415), GetCreatureName(CID_FIRST_AID_TENT, 1),
                    GetCreatureName(stack->creature_id, stack->count_current), healingInfo[i].removedDamage);

                CALL_4(void, __thiscall, 0x4729D0, o_BattleMgr->dlg, o_TextBuffer, 1, 0);
               
                // Отмечаем отряды, для которых необходима анимация
                AnimateStack[o_BattleMgr->current_side][healingInfo[i].stack->index_on_side] = true;
            }
        }

        // Проигрываем массовую анимацию
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, AnimateStack, 79, 0);
       
        // Ждём, пока воспроизведётся звук
        CALL_3(void, __thiscall, 0x59A7C0, -1, c->eax, c->edx);
       
        c->return_address = 0x478692;
        return NO_EXEC_DEFAULT;
    }
       
    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.NewFirstAidTent");

            // Меняем параметры First Aid Tent
            _PI->WriteLoHook(0x4EE1C1, afterInit);

            // Меняем коэффициенты
            float firstAidCoefs[] = {1.0f, 3.0f, 7.0f, 11.0f};
            _PI->WriteDword(0x63EA98, (int&)firstAidCoefs[0]);
            _PI->WriteDword(0x63EA98 + 4, (int&)firstAidCoefs[1]);
            _PI->WriteDword(0x63EA98 + 8, (int&)firstAidCoefs[2]);
            _PI->WriteDword(0x63EA98 + 12, (int&)firstAidCoefs[3]);

            // Проверяем, нужен ли хил
            _PI->WriteLoHook(0x473865, findTargetsToHeal);

            // Убираем ручной контроль
            _PI->WriteCodePatch(0x4745FD, "%n", 10);

            // Делаем хил максимальным
            _PI->WriteCodePatch(0x47852C, "%n", 12);
                       
            // Хилим
            _PI->WriteLoHook(0x47852C, applyHealing);

            // Пишем лог и проигрываем анимацию
            _PI->WriteLoHook(0x478580, playAnimation);
        }
    }

    return TRUE;
}

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

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 » 10 апр 2021, 12:35

Пункты 3) - 7) готовы.

Цитата:
ПЛЮС!!! У героев с навыком Сопротивление (резистом), резист получается в приорите Зеркала. Нужно, чтобы было наоборот, так, как правило, вероятность отражения Зеркала выше чем срабатывания резиста. А то получается, что на отряд под Зеркалом накладывается закл и он может этот закл отразить на кастера и принести пользу, а выходит, что он блочится резистом и все. То есть нужно изначально проверять, есть ли на отряде Зеркало, и если есть, проверять сработает ли оно на этом отряде на который был направлен закл и только если нет (Зеркало не сработает или его нет на отряде), только тогда уже после проверки срабатывания Зеркала пускать резист. Так выгодней. Лучше отразить закл на вражеский отряд, чем просто его блокировать.

Это не так. Сначала проверяется срабатывание Magic Mirror (всегда) и только потом резист. А порядок анимации мы обсуждали. Резист показывается только тогда, когда зеркало не сработало и сработал резист или зеркало сработало, но отразило заклинание в пустоту. Если у Вас есть примеры, когда отряд под зеркалом отражает заклинание уже после наложения на него этого заклинания, делитесь.

Цитата:
8) Если все отряды каждого из героев под Зеркалом, то при отражении целевого (-ых) отрядов на отряды кастующего при одиночном касте или при масскасте отряд или отряды на которые пошло отражение всегда принимают этот закл и получают урон со 100% вероятностью.

Здесь тоже не понимаю. Есть масскаст. Обе стороны под зеркалом. Сначала проигрывается анимация зеркала для вражеских отрядов (тех, на кого направляют заклинание), потом резиста всех отрядов (он может быть: а) резистом вражеских отрядов, б) заменой анимации зеркала для вражеских отрядов в случае, если зеркало отразило в пустоту, в) резистом дружественных отрядов; г) заменой анимации зеркала для дружественных отрядов, т.к. зеркало на дружественных отрядах уже всегда отражает в пустоту). И только потом следует анимация каста (общая для вражеских отрядов и дружественных, на которых заклинание было перенаправлено). Если в каких-то случаях работает не так, нужны примеры.

Цитата:
9) Баг с имунными монстрами. Допустим, в армии героя есть Черные драконы и, допустим, Минотавры. Так вот после гибели Минотавров при отражении закла направленног героем с Черными драконами принимает этот закл резистом труп самого сильного из погибших существ (если в армии остались толькоимуннеы к этому заклу, например, как у нас Черные драконы).
Если были Черные драконы и Баллиста, то если баллисту уничтожат, то все отраженные заклы будет принимать резист уже разваленной баллисты. В данном случае нужно всегда направлять на Черного дракона, если в живых большего никого нет, а Черный будет блочить резистом.

Это было исправлено раньше этим кодом:

Код: Выделить всё
_BattleStackEx_* _BattleStackEx_::getStackToRedirect(const int spell, const bool massCast)
{
    _BattleStackEx_* stackFoe = 0;
    int sideFoe = 1 - this->side;

    if ( stackIndex < (int)stackVector.size() && spell != SPL_DESTROY_UNDEAD && spell != SPL_DEATH_RIPPLE && spell != SPL_ARMAGEDDON )
        stackFoe = (_BattleStackEx_*)&o_BattleMgr->stack[sideFoe][stackVector[stackIndex++]->index_on_side];

    if ( !massCast && !stackFoe )
    {
        int i = 0;
        // Пропускаем погибшие отряды
        while ( o_BattleMgr->stack[o_BattleMgr->current_side][i++].is_killed );
        stackFoe = (_BattleStackEx_*)&o_BattleMgr->stack[sideFoe][i];
    }
   
    return stackFoe;
}
Вернуться к началу

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

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

Сообщение Rolex » 10 апр 2021, 14:36

AlexSpl писал(а):

Rolex писал(а):

8) Если все отряды каждого из героев под Зеркалом, то при отражении целевого (-ых) отрядов на отряды кастующего при одиночном касте или при масскасте отряд или отряды на которые пошло отражение всегда принимают этот закл и получают урон со 100% вероятностью.


Здесь тоже не понимаю. Есть масскаст. Обе стороны под зеркалом. Сначала проигрывается анимация зеркала для вражеских отрядов (тех, на кого направляют заклинание), потом резиста всех отрядов (он может быть: а) резистом вражеских отрядов, б) заменой анимации зеркала для вражеских отрядов в случае, если зеркало отразило в пустоту, в) резистом дружественных отрядов; г) заменой анимации зеркала для дружественных отрядов, т.к. зеркало на дружественных отрядах уже всегда отражает в пустоту). И только потом следует анимация каста (общая для вражеских отрядов и дружественных, на которых заклинание было перенаправлено). Если в каких-то случаях работает не так, нужны примеры.

Все так. Дело не в анимациях и порядке их проигрывания.
Вот, что я писал:
Rolex писал(а):

8) Если все отряды каждого из героев под Зеркалом, то при отражении целевого (-ых) отрядов на отряды кастующего при одиночном касте или при масскасте отряд или отряды на которые пошло отражение всегда принимают этот закл и получают урон со 100% вероятностью.

В то время как при отражении площадных на отряды под Зеркалом эти заклы блочаться с вероятностью 50% на Advanced/Expert и 40% на Basic (часть принимает, часть блокирует). С той, с которой у нас заложено отражение.

Правильней было бы привести все к общему знаменателю. Либо же при отражении закла кастера на отряды под Зеркалом отраженный закл всегда будет срабатывать со 100% вероятность на отряде под Зеркалом (как сейчас у синглкаста и масскаста).

Либо же как у площадных отраженный закл на отряды под Зеркалом срабатывает ровно с той вероятность, с которой и отражается от целевого (или целевых) отрядов.

Наверное, все же нужно делать сингкаст и максскаст также как и с площадными (согласно нашей вероятности 40% и 50%).

Дело в том, что когда все отряды обеих героев под Зеркалом, то после отражения одним отрядом при снглкасте или частью при масскасте, которые также под Зеркалом, то на свои же отряды эти заклы накладываются со 100% вероятностью, то есть в любом случае. А вот с площадными все ок. При той же схеме где все отряды как одного, так и другого героя под Зеркалом, после отражение наложение на свои под Зеркалом идет с той вероятностью с которой было отражено вражескими 40-50%.

AlexSpl писал(а):

Rolex писал(а):

9) Баг с имунными монстрами. Допустим, в армии героя есть Черные драконы и, допустим, Минотавры. Так вот после гибели Минотавров при отражении закла направленног героем с Черными драконами принимает этот закл резистом труп самого сильного из погибших существ (если в армии остались только имунные к этому заклу, например, как у нас Черные драконы).
Если были Черные драконы и Баллиста, то если баллисту уничтожат, то все отраженные заклы будет принимать резист уже разваленной баллисты. В данном случае нужно всегда направлять на Черного дракона, если в живых большего никого нет, а Черный будет блочить резистом.


Это было исправлено раньше этим кодом:

Увы, но этот код не исправляет то, о чем я писал. Тестировал на последней версии вашего кода! Проверьте сами, возьмите парочку Черных драконов и Баллисту (или Палатку, без разницы). Пускай враг уничтожит Баллисту/Палатку. Потом попробуйте кастонуть героем с Черными драконами на врага под Зеркалом какой-то синглкаст ударный или Слабость начальных уровней. Отразит на разбитую Баллисту/Палатку (сработает резист), а не на Черного дракона.

Таким образом осталось исправить 8 и 9 пункты и сделать 1 и 2 пункты. И, вроде, все.

***************************************************************************
По Палатке добавьте следующий код после HealingInfo:

Код: Выделить всё
struct MonBattleDesc
{
   char* nameSingle;
   char* namePlural;
   char* desc;
} monBattleDesc[150];

struct SecSkillDesc
{
   char* Name;
   char* BasicDesc;
   char* AdvancedDesc;
   char* ExpertDesc;
};

SecSkillDesc FirstAidDesc =
{
   "Первая помощь",
   "{Первая помощь Базового уровня}\n\nВосстанавливает 100 единиц здоровья у всех отрядов в армии героя.",
   "{Первая помощь Продвинутого уровня}\n\nВосстанавливает 200 единиц здоровья у всех отрядов в армии героя, а также здоровье Тележки, Баллисты и Катапульты.",
   "{Первая помощь Экспертного уровня}\n\nВосстанавливает 300 единиц здоровья у всех отрядов и всех орудий в армии героя включая здоровье самой Палатки."
};

int __stdcall afterInit(LoHook* h, HookContext* c)
{
   o_pCreatureInfo[CID_FIRST_AID_TENT].hit_points = 300;
   o_pCreatureInfo[CID_FIRST_AID_TENT].defence = 10;
   o_pCreatureInfo[CID_FIRST_AID_TENT].cost.gold = 1500;

   monBattleDesc[CID_FIRST_AID_TENT].desc = "Лечит один из отрядов героя (50 ед. урона без навыка Первая помощь).";
   *(MonBattleDesc*)(0x6703CC + 116 * CID_FIRST_AID_TENT) = monBattleDesc[CID_FIRST_AID_TENT];

   *(SecSkillDesc*)(*(int*)0x67DCF0 + 27 * 16) = FirstAidDesc;

   return EXEC_DEFAULT;
}


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

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 » 12 апр 2021, 13:23

Цитата:
Увы, но этот код не исправляет то, о чем я писал. Тестировал на последней версии вашего кода! Проверьте сами, возьмите парочку Черных драконов и Баллисту (или Палатку, без разницы). Пускай враг уничтожит Баллисту/Палатку. Потом попробуйте кастонуть героем с Черными драконами на врага под Зеркалом какой-то синглкаст ударный или Слабость начальных уровней. Отразит на разбитую Баллисту/Палатку (сработает резист), а не на Черного дракона.

Да, перестарался я и получился перелёт. Вернул предыдущий код функции getStackToRedirect():

Код: Выделить всё
int i = 0;
// Пропускаем погибшие отряды
while ( o_BattleMgr->stack[o_BattleMgr->current_side][i].is_killed ) ++i;
stackFoe = (_BattleStackEx_*)&o_BattleMgr->stack[sideFoe][i];
return stackFoe;
Вернуться к началу

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 » 12 апр 2021, 15:25

Код на текущий момент без пунктов 1) и 2):

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

using namespace std;

Patcher* _P;
PatcherInstance* _PI;

struct _BattleStackEx_ : public _BattleStack_
{
    int getMonsterSpell();
    bool approvedTarget(const int spell);
    bool magicMirrorLucky(const int spell);
    _BattleStackEx_* getStackToRedirect(const int spell, const bool massCast = true);
    int calcMagicDamage(const int spell, const _Hero_* attHero, const _Hero_* defHero);
    float getFullVulnerability(const int spell);
       
    inline int current_health() {
        return this->count_current * this->full_hp - this->lost_hp;
    }
    inline int full_health() {
        return this->count_current * this->full_hp;
    }
    inline int fightValue() {
        return this->creature.fight_value * this->count_current;
    };
    inline double damageFightValue(const int spell, const int spellDamage) {
        return this->getFullVulnerability(spell) * min((double)spellDamage / this->full_hp,
            this->count_current - (double)this->lost_hp / this->full_hp) * this->creature.fight_value;
    }
    inline double extraFightValue(const int spell, const int spellDamage) {
        return spellDamage >= this->current_health() && spellDamage < this->full_health() ?
            this->getFullVulnerability(spell) * this->creature.fight_value : 0;
    }
    inline float getMagicVulnerability(const int spell) {
        return CALL_7(float, __thiscall, 0x5A83A0, o_BattleMgr, spell, o_BattleMgr->current_side, this, 1, 1, 0);
    };
    inline int getHitChanceResist(const int spell) {
        return (int)(100 * getMagicVulnerability(spell));
    }
    inline int getHitChanceFull(const int spell) {
        return (int)(100 * getFullVulnerability(spell));
    }
    inline bool isImmuneTo(const int spell) {
        return getMagicVulnerability(spell) == 0;
    };
    inline void playAnimation(const int id) {
        CALL_5(void, __thiscall, 0x4963C0, o_BattleMgr, id, this, 100, 0);
    }
};

bool showMagicMirrorAnim, showMagicResistAnim, showSpellAnim, redirectedAreaSpell;
bool showResistAfterAttack = false, showMagicMirrorAfterAttack = false;
bool needMagicMirrorAnim[2][20], needMagicResistAnim[2][20], needSpellAnim[2][20];
_BattleStackEx_* targetStack = 0;

vector<_BattleStackEx_*> stackVector;
int stackIndex;

#define SPL_DEATH_RIPPLE 24
#define CID_AZURE_DRAGON 132

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

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

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
};

struct SpellDesc
{
    char* DefaultDesc;
    char* BasicDesc;
    char* AdvancedDesc;
    char* ExpertDesc;
};

SpellDesc NewMagicMirrorRus =
{
    "{Магическое зеркало}\n\nЦелевой отряд способен отразить вражеское заклинание на самый сильный отряд противника с вероятностью в 40%.",
    "{Базовое Магическое зеркало}\n\nЦелевой отряд способен отразить вражеское заклинание на самый сильный отряд противника с вероятностью в 40%.",
    "{Продвинутое Магическое зеркало}\n\nЦелевой отряд способен отразить вражеское заклинание на самый сильный отряд противника с вероятностью в 50%.",
    "{Экспертное Магическое зеркало}\n\nВсе дружественные отряды способны отразить вражеское заклинание на все или самые сильные отряды противника с вероятностью в 50%."
};

SpellDesc NewMagicMirrorEng =
{
    "{Magic Mirror}\n\nThe target stack is able to reflect the enemy spell on the strongest enemy stack with a 40% chance.",
    "{Basic Magic Mirror}\n\nThe target stack is able to reflect the enemy spell on the strongest enemy stack with a 40% chance.",
    "{Advanced Magic Mirror}\n\nThe target stack is able to reflect the enemy spell on the strongest enemy stack with a 50% chance.",
    "{Expert Magic Mirror}\n\nAll friendly stacks are able to reflect an enemy spell on all or the most strongest enemy stacks with a 50% chance."
};

int __stdcall afterInit(LoHook* h, HookContext* c)
{
    o_Spell[SPL_MAGIC_MIRROR].effect[0] = 40;
    o_Spell[SPL_MAGIC_MIRROR].effect[1] = 40;
    o_Spell[SPL_MAGIC_MIRROR].effect[2] = 50;
    o_Spell[SPL_MAGIC_MIRROR].effect[3] = 50;

    *(SpellDesc*)o_Spell[SPL_MAGIC_MIRROR].description =
        o_CreatureInfo[0].name_single[0] != 'P' ? NewMagicMirrorRus : NewMagicMirrorEng;

    return EXEC_DEFAULT;
}

int Randint_s()
{
    unsigned dice;
    rand_s(&dice);
    return (int)((double)dice / ((double)UINT_MAX + 1) * 100.0) + 1;
}

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);
}

_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 _BattleStackEx_::approvedTarget(const int spell)
{
    return !this->is_killed && !this->isImmuneTo(spell) && !(spell == SPL_FORGETFULNESS && this->creature.shots == 0);
}

bool _BattleStackEx_::magicMirrorLucky(const int spell)
{
    int effSchoolLevel = this->active_spells_power[SPL_MAGIC_MIRROR];

    return !this->isImmuneTo(spell) && !(o_Spell[spell].flags & SPF_FRIENDLY_HAS_MASS) && (this->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) &&
        this->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint_s() <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel];
}

_BattleStackEx_* _BattleStackEx_::getStackToRedirect(const int spell, const bool massCast)
{
    _BattleStackEx_* stackFoe = 0;
    int sideFoe = 1 - this->side;

    if ( stackIndex < (int)stackVector.size() && spell != SPL_DESTROY_UNDEAD && spell != SPL_DEATH_RIPPLE && spell != SPL_ARMAGEDDON )
        stackFoe = (_BattleStackEx_*)&o_BattleMgr->stack[sideFoe][stackVector[stackIndex++]->index_on_side];

    if ( !massCast && !stackFoe )
    {
        int i = 0;
        // Пропускаем погибшие отряды
        while ( o_BattleMgr->stack[o_BattleMgr->current_side][i].is_killed ) ++i;
        stackFoe = (_BattleStackEx_*)&o_BattleMgr->stack[sideFoe][i];
    }
   
    return stackFoe;
}

float _BattleStackEx_::getFullVulnerability(const int spell)
{
    float magicMirrorChance = this->active_spell_duration[SPL_MAGIC_MIRROR] ?
        o_Spell[SPL_MAGIC_MIRROR].effect[this->active_spells_power[SPL_MAGIC_MIRROR]] * 0.01f : 0;
    return (1 - magicMirrorChance) * this->getMagicVulnerability(spell);
};

int _BattleStackEx_::calcMagicDamage(const int spell, const _Hero_* attHero, const _Hero_* defHero)
{
    int effSchoolLevel = 0;
   
    if ( attHero )
        effSchoolLevel = CALL_3(int, __thiscall, 0x4E52F0, attHero, spell, o_BattleMgr->spec_terr_type);
    // Если кастер - нейтралы на Magic Plains
    else if ( o_BattleMgr->spec_terr_type == 1 )
        effSchoolLevel = 3;

    int damage = attHero->power * o_Spell[spell].eff_power + o_Spell[spell].effect[effSchoolLevel];
    return CALL_7(int, __thiscall, 0x5A7BF0, o_BattleMgr, damage, spell, attHero, defHero, this, 0);
}

int __stdcall newTarget(LoHook* h, HookContext* c)
{
    _BattleStackEx_* stack = (_BattleStackEx_*)c->edi;
    int spell = *(int*)(c->ebp + 8);

    c->eax = (int)stack->getStackToRedirect(spell, false);

    return EXEC_DEFAULT;
}

bool __stdcall massSpellMagicMirror(HiHook *h, int spell, int ssLevel)
{
    return spell == SPL_MAGIC_MIRROR && ssLevel == 3 ? false : CALL_2(bool, __fastcall, h->GetDefaultFunc(), spell, ssLevel);
}

// Костыль для C++98
class cmpFightValue
{
    int spell;

public:
    cmpFightValue(int id) : spell(id) { }

    bool operator () (_BattleStackEx_* a, _BattleStackEx_* b)
    {
        return a->getFullVulnerability(spell) * a->fightValue() > b->getFullVulnerability(spell) * b->fightValue();
    }
};

class cmpSpellDuration
{
    int spell;

public:
    cmpSpellDuration(int id) : spell(id) { }

    bool operator () (_BattleStackEx_* a, _BattleStackEx_* b)
    {
        return a->active_spell_duration[spell] < b->active_spell_duration[spell];
    }
};

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

        // При следующих условиях не проигрываем звук заклинания
        if ( spell == SPL_DEATH_RIPPLE || spell == SPL_DESTROY_UNDEAD ||
            switchCase == 17 || switchCase == 24 || o_BattleMgr->ShouldNotRenderBattle() )
        {
            c->return_address = 0x5A0646;
            return NO_EXEC_DEFAULT;
        }
    }

    return EXEC_DEFAULT;
}

int _BattleStackEx_::getMonsterSpell()
{
    int spell = ID_NONE;

    if ( this )
    {
        switch ( this->creature_id )
        {
        case CID_THUNDERBIRD:
            spell = SPL_LIGHTNING_BOLT;
            break;
        case CID_DRAGON_FLY:
            spell = SPL_WEAKNESS;
            break;
        case CID_UNICORN: case CID_WAR_UNICORN:
            spell = SPL_BLIND;
            break;
        case CID_BLACK_KNIGHT: case CID_DREAD_KNIGHT: case CID_MUMMY:
            spell = SPL_CURSE;
            break;
        }
    }

    return spell;
}

int __stdcall dataInit(LoHook* h, HookContext* c)
{
    int spell;
   
    if ( h->GetAddress() == 0x5A058B )
        spell = *(int*)(c->ebp + 8);
    else {
        _BattleStackEx_* stack = (_BattleStackEx_*)c->esi;
        spell = stack->getMonsterSpell();
    }

    if ( spell != ID_NONE )
    {
        showMagicMirrorAnim = false;
        showMagicResistAnim = false;
        showSpellAnim = false;
        redirectedAreaSpell = false;
        memset(needMagicMirrorAnim, false, 40);
        memset(needMagicResistAnim, false, 40);
        memset(needSpellAnim, false, 40);

        int casterSide = o_BattleMgr->current_side;
        int maxSize = o_BattleMgr->stacks_count[casterSide];
        stackVector.resize(maxSize);

        int n = 0;
        for (int i = 0; i < maxSize; ++i)
        {
            _BattleStackEx_* stack = (_BattleStackEx_*)&o_BattleMgr->stack[casterSide][i];
            if ( stack->approvedTarget(spell) ) stackVector[n++] = stack;
        }

        stackVector.resize(n);
        sort(stackVector.begin(), stackVector.end(), cmpFightValue(spell));
        stable_sort(stackVector.begin(), stackVector.end(), cmpSpellDuration(spell));

        stackIndex = 0;
    }

    return EXEC_DEFAULT;
}

int __stdcall magicMirrorAI(LoHook* h, HookContext* c)
{
    _BattleStackEx_* stack = (_BattleStackEx_*)c->edi;

    if ( stack )
    {
        _Hero_* hero = o_BattleMgr->hero[1 - o_BattleMgr->current_side];
        int spell = *(int*)(c->ebp + 8);
        int effSchoolLevel = stack->active_spells_power[SPL_MAGIC_MIRROR];

        // Если герой - 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_s() <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel] &&
             spell != SPL_TELEPORT && spell != SPL_SACRIFICE )
        {
            // Проигрываем одиночную анимацию
            stack->playAnimation(o_Spell[SPL_MAGIC_MIRROR].animation_ix);

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

void playSound(const char* fileName)
{
    CALL_1(_Sample_, __fastcall, 0x59A770, fileName);
}

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

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

int __stdcall massReflection(LoHook* h, HookContext* c)
{
    bool hitFlag = false;
    _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] )
    {
        hitFlag = true;
        c->return_address = hAddr[A_MASSANIMATION][E_DEATH_RIPPLE];
    }

    if ( addr == hAddr[A_MASSREFLECTION_HOOK][E_DESTROY_UNDEAD] )
    {
        c->edi = *(int*)(c->ebp - 0x10);
        hitFlag = true;
        c->return_address = hAddr[A_MASSANIMATION][E_DESTROY_UNDEAD];
    }

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

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

    return NO_EXEC_DEFAULT;
}

bool isValidHex(const int hex)
{
    return hex >= 0 && hex < 187;
}

int getSpellTargetHex(const int spell)
{
    const int xSize = 17;
               
    vector <pair<double, double>> ones;
    ones.resize(186);

    double maxDamageFightValueDelta = 0.0;
    int bestTargetHex = -1;

    _Hero_* attHero = o_BattleMgr->hero[o_BattleMgr->current_side];
    _Hero_* defHero = o_BattleMgr->hero[1 - o_BattleMgr->current_side];

    for (int hex = 1; ; ++hex)
    {
        if ( hex % xSize == 16 ) hex += 2;
        if ( hex > 185 ) break;
     
        int y = hex / xSize;
        int d = y % 2;

        int areaR1[] = {-17 - d, -16 - d, -1, 0, 1, 17 - d, 18 - d};
        int areaFR[] = {-17 - d, -16 - d, -1, 1, 17 - d, 18 - d};
        int areaR2[] = {-35, -34, -33, -18 - d, -17 - d, -16 - d, -15 - d, -2, -1,
                0, 1, 2, 16 - d, 17 - d, 18 - d, 19 - d, 33, 34, 35};
       
        int* area = areaR1;
        int areaSize = sizeof(areaR1) / sizeof(int);
       
        if ( spell == SPL_FROST_RING ) {
            area = areaFR;
            areaSize = sizeof(areaFR) / sizeof(int);
        }

        if ( spell == SPL_INFERNO ) {
            area = areaR2;
            areaSize = sizeof(areaR2) / sizeof(int);
        }
       
        int countUs = 0, countThem = 0;
        double extraFightValueUs = 0, extraFightValueThem = 0;
        double damageFightValueUs = 0, damageFightValueThem = 0;
        for (int i = 0; i < areaSize; ++i)
        {
            int iHex = hex + area[i];
            if ( isValidHex(iHex) )
            {
                _BattleStackEx_* stack = (_BattleStackEx_*)o_BattleMgr->hex[iHex].GetCreature();
                if ( stack )
                {
                    if ( !stack->is_killed && !stack->isImmuneTo(spell) )
                    {
                        // Считаем магический урон, получаемый стеком stack
                        int spellDamage = stack->calcMagicDamage(spell, attHero, defHero);
                       
                        if ( stack->side == o_BattleMgr->current_side ) {
                            ++countThem;
                            damageFightValueThem += stack->damageFightValue(spell, spellDamage);
                            extraFightValueThem += stack->extraFightValue(spell, spellDamage);
                        }
                        else {
                            ++countUs;
                            damageFightValueUs += stack->damageFightValue(spell, spellDamage);
                            extraFightValueUs += stack->extraFightValue(spell, spellDamage);
                        }
                    }
                                   
                    if ( i + 1 < areaSize )
                    {
                        int iHexNext = hex + area[i + 1];
                        if ( isValidHex(iHexNext) && (_BattleStackEx_*)o_BattleMgr->hex[iHexNext].GetCreature() == stack ) ++i;
                    }
                }
            }
        }

        ones[hex].first = damageFightValueThem - damageFightValueUs;
        ones[hex].second = extraFightValueThem - extraFightValueUs;

        if ( countThem + countUs > 0 && damageFightValueThem - damageFightValueUs > maxDamageFightValueDelta )
        {
            maxDamageFightValueDelta = damageFightValueThem - damageFightValueUs;
            bestTargetHex = hex;
        }
    }

    for (int i = 1; i < 186; ++i)
        if ( maxDamageFightValueDelta > 0.0 && ones[i].first == maxDamageFightValueDelta )
        {
            if ( ones[i].second > ones[bestTargetHex].second )
                bestTargetHex = i;
        }

    return bestTargetHex;
}

void __stdcall areaReflection(HiHook* h, _BattleMgr_* battleMgr, int hex, int spell, int a3, int a4)
{
    memcpy(AnimateStack, needSpellAnim, 40);
    CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, hex, spell, a3, a4);
    playMagicResist();
    playMagicMirror();

    // Если заклинание отражено
    if ( redirectedAreaSpell )
    {
        int bestHex = getSpellTargetHex(spell);
        if ( bestHex != ID_NONE )
        {
            showMagicMirrorAnim = false;
            showMagicResistAnim = false;
            memset(needMagicMirrorAnim, false, 40);
            memset(needMagicResistAnim, false, 40);

            playSound(o_Spell[spell].wav_name);
            CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, bestHex, spell, a3, a4);
            playMagicResist();
            playMagicMirror();
        }
    }
}

int __stdcall playMagicMirrorSound(LoHook* h, HookContext* c)
{
    playSound(o_Spell[SPL_MAGIC_MIRROR].wav_name);
   
    return EXEC_DEFAULT;
}

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

    _ptr_ addr = h->GetAddress();

    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_MASS] )
    {
       stack = (_BattleStackEx_*)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 = *(_BattleStackEx_**)(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 = (_BattleStackEx_*)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 = (_BattleStackEx_*)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 = (_BattleStackEx_*)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 = (_BattleStackEx_*)c->esi;
        spell = SPL_ARMAGEDDON;
        c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_ARMAGEDDON];
    }

    // Получаем оригинальный шанс срабатывания заклинания
    int hitChance = c->eax;

    if ( stack )
    {
        bool isSpellAoE = spell == SPL_FROST_RING || spell == SPL_FIREBALL || spell == SPL_INFERNO || spell == SPL_METEOR_SHOWER;
        bool isSpellSpec = spell == SPL_DEATH_RIPPLE || spell == SPL_DESTROY_UNDEAD || spell == SPL_ARMAGEDDON;

        if ( stack->magicMirrorLucky(spell) )
        {
            _BattleStackEx_* stackFoe = stack->getStackToRedirect(spell);

            if ( stackFoe && !redirectedAreaSpell ) {
                showMagicMirrorAnim = true;
                needMagicMirrorAnim[stack->side][stack->index_on_side] = true;
            }
            else {
                showMagicResistAnim = true;
                needMagicResistAnim[stack->side][stack->index_on_side] = true;
            }

            // Не перенаправляем площадные заклинания, отражённые от дружественных отрядов
            if ( isSpellAoE && stack->side != casterSide ) redirectedAreaSpell = true;
           
            if ( isSpellAoE || isSpellSpec ) return NO_EXEC_DEFAULT;

            // Если сработало Magic Mirror, перенаправляем заклинание на вражеский отряд
            if ( stackFoe )
            {
                // Определяем, сработает ли резист против отражённого заклинания
                int hitChanceFoe = stackFoe->getHitChanceFull(spell);

                if ( Randint_s() <= hitChanceFoe )
                {
                    showSpellAnim = true;
                    needSpellAnim[stackFoe->side][stackFoe->index_on_side] = true;
                    CALL_5(void, __thiscall, 0x444610, stackFoe, spell, spellPower, schoolLevel, hero);
                }
                else if ( hitChanceFoe )
                {
                    showMagicResistAnim = true;
                    needMagicResistAnim[stackFoe->side][stackFoe->index_on_side] = true;
                }
            }
        }
        else
        {
            if ( Randint_s() <= hitChance )
            {
                showSpellAnim = true;
                needSpellAnim[stack->side][stack->index_on_side] = true;

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

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

    _ptr_ addrHit, addrSkip;
    switch ( h->GetAddress() )
    {
    case 0x440EE8:
        spell = SPL_LIGHTNING_BOLT;
        addrHit = 0x440F03;
        addrSkip = 0x4412AB;
        break;
    case 0x440372:
        spell = SPL_BLIND;
        addrHit = 0x44038D;
        addrSkip = 0x4402AE;
        break;
    case 0x440467:
        spell = SPL_CURSE;
        addrHit = 0x440482;
        addrSkip = 0x4402AE;
        break;
    }

    if ( stack->magicMirrorLucky(spell) )
    {
        c->return_address = addrSkip;
       
        // Если сработало Magic Mirror, отражаем обратно
        // _BattleStackEx_* stackFoe = (_BattleStackEx_*)c->esi;
        _BattleStackEx_* stackFoe = stack->getStackToRedirect(spell, false);
        if ( stackFoe )
        {
            int hitChanceFoe = stackFoe->getHitChanceResist(spell);
           
            if ( Randint_s() <= hitChanceFoe )
            {
                if ( spell == SPL_LIGHTNING_BOLT )
                {
                    playSound(o_Spell[SPL_MAGIC_MIRROR].wav_name);
                    stack->playAnimation(o_Spell[SPL_MAGIC_MIRROR].animation_ix);
                }
                else
                {
                    targetStack = stack;
                    showMagicMirrorAfterAttack = true;
                }

                c->edi = (int)stackFoe;
                c->return_address = addrHit;
            }
            else if ( hitChanceFoe )
            {
                if ( spell == SPL_LIGHTNING_BOLT )
                {
                    playSound("MagicRes.wav");
                    stackFoe->playAnimation(78);
                }
                else
                {
                    targetStack = stackFoe;
                    showResistAfterAttack = true;
                }
            }
        }
                       
        return NO_EXEC_DEFAULT;
    }
       
    return EXEC_DEFAULT;
}

int __stdcall playAnimForMonster(LoHook* h, HookContext* c)
{
    if ( showResistAfterAttack )
    {
        playSound("MagicRes.wav");
        targetStack->playAnimation(78);
        showResistAfterAttack = false;
    }

    if ( showMagicMirrorAfterAttack )
    {
        playSound(o_Spell[SPL_MAGIC_MIRROR].wav_name);
        targetStack->playAnimation(o_Spell[SPL_MAGIC_MIRROR].animation_ix);
        showMagicMirrorAfterAttack = false;
    }

    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.NewMagicMirrorBeta");

            // Пропускаем оригинальный звук заклинания, чтобы воспроизвезти его позже
            _PI->WriteLoHook(0x5A0634, skipSpellSound);
           
            // Инициализируем данные
            _PI->WriteLoHook(0x441893, dataInit);
            _PI->WriteLoHook(0x441991, dataInit);
            _PI->WriteLoHook(0x5A058B, 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);

            _PI->WriteLoHook(0x4EE1C1, afterInit);
            _PI->WriteHiHook(0x59E360, SPLICE_, EXTENDED_, FASTCALL_, massSpellMagicMirror);

            _PI->WriteLoHook(0x440EE8, mirrorMonsterSpell); // Thunderstrike
            _PI->WriteLoHook(0x440372, mirrorMonsterSpell); // Blind
            _PI->WriteLoHook(0x440467, mirrorMonsterSpell); // Curse
            _PI->WriteLoHook(0x468C99, playAnimForMonster);
        }
    }

    return TRUE;
}

Для абилок сделал отражение, как и для всей остальной магии, чтобы можно было потестить отражение Curse. Если всё-таки нужно, чтобы отражало только на существо с абилкой, закомментируйте третью строчку и раскомментируйте вторую:

Код: Выделить всё
// Если сработало Magic Mirror, отражаем обратно
// _BattleStackEx_* stackFoe = (_BattleStackEx_*)c->esi;
_BattleStackEx_* stackFoe = stack->getStackToRedirect(spell, false);

Но тогда реально отражение будет работать для Thunderbirds и единорогов, т.к. Curse не действует на нежить.

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

Пред.След.

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

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

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

cron