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


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

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

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

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

Сообщение Rolex » 16 мар 2021, 13:06

AlexSpl писал(а):

А как в HotA? Резист тоже не анимируется? Если ещё не сделали, подкиньте команде идейку. Было бы реально здорово видеть анимацию резиста частью массовой анимации. Как бы команда на меньшее и не согласится :smile2:

Не работает это в HotA. Для начала нужно сделать, чтобы это у нас работало. Я вот пытаюсь разобраться с вашими функциями, но далеко не все мне понятно. Поможете довести наше Зеркало до ума по тем 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 » 16 мар 2021, 13:41

Технически всё работает: плагин определяет, когда отряды должны отражать магию, и куда её отражать. Вам просто нужно прописать Вашу логику. Хотите, чтобы отражало на отряды по убыванию их силы, пропишите в функции getEnemyStackToRedirectTo() соответствующие строки. Я бы не массивом организовал хранение отрядов, упорядоченных по силе, а стеком, чтобы можно было делать примерно так:

Код: Выделить всё
_BattleStack_* getEnemyStackToRedirectTo(_BattleStack_* stack)
{
    // mySortedBattleStacks объявляете глобально:
    // stack<_BattleStack_*> mySortedBattleStacks;
    return mySortedBattleStacks.pop();
}

Можно и с помощью массива. Как угодно.

В функцию magicMirrorLucky() добавьте/измените условие срабатывания. Например, вот это:

Код: Выделить всё
stack->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(1, 100) <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel]

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

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 » 16 мар 2021, 17:20

С анимацией резиста и добавленной проверкой на дружественный спелл:

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

bool showMagicMirrorAnim, showMagicResistAnim;
bool needMagicMirrorAnim[2][20], needMagicResistAnim[2][20];

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

_BattleStack_* getEnemyStackToRedirectTo(_BattleStack_* stack)
{
    // В целях демонстрации работы просто перенаправим заклинание на вражеский отряд
    // с таким же индексом, как у отряда, отразившего заклинание,
    // поэтому тестируйте с полными армиями во избежание сюрпризов
    return &o_BattleMgr->stack[1 - stack->side][stack->index_on_side]; 
}

int __stdcall ResetMagicMirrorFlags(LoHook* h, HookContext* c)
{
    showMagicMirrorAnim = false;
    showMagicResistAnim = false;
    memset(needMagicMirrorAnim, false, 40);
    memset(needMagicResistAnim, false, 40);
   
    return EXEC_DEFAULT;
}

int __stdcall ReflectSpell(LoHook* h, HookContext* c)
{
    _BattleStack_* stack = (_BattleStack_*)c->esi;
    _BattleStack_* stackFoe;

    int spell = *(int*)(c->ebp + 0xC);
    int spellPower = *(int*)(c->ebp + 0x14);
    int schoolLevel = *(int*)(c->ebp + 0x10);
    int casterSide = *(int*)(c->ebp + 0x18);
    _Hero_* hero = *(_Hero_**)(c->ebp + 8);
   
    // Если сработало Magic Mirror, перенаправляем заклинание на вражеский отряд
    if ( magicMirrorLucky(o_BattleMgr->hero[1 - o_BattleMgr->current_side], stack, spell) )
    {
        showMagicMirrorAnim = true;
        needMagicMirrorAnim[stack->side][stack->index_on_side] = true;
        stackFoe = getEnemyStackToRedirectTo(stack);
        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);
                AnimateStack[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 )
        {
            // Если не сработал резист, применяем эффект заклинания
            CALL_5(void, __thiscall, 0x444610, stack, spell, spellPower, schoolLevel, hero);
            AnimateStack[stack->side][stack->index_on_side] = true;
        }
        // Если сработал резист, отмечаем отряд как требующий анимации резиста
        else if ( c->eax )
        {
            showMagicResistAnim = true;
            needMagicResistAnim[stack->side][stack->index_on_side] = true;
        }
    }
   
    c->return_address = 0x5A6A83;
    return NO_EXEC_DEFAULT;
}

int __stdcall MagicMirrorMassReflection(LoHook* h, HookContext* c)
{
    // Проигрываем массовую анимацию Magic Mirror
    if ( showMagicMirrorAnim )
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicMirrorAnim, o_Spell[SPL_MAGIC_MIRROR].animation_ix, 0);

    // Проигрываем массовую анимацию резиста
    if ( showMagicResistAnim )
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicResistAnim, 78, 0);

    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(0x5A13D2, ResetMagicMirrorFlags);
            _PI->WriteLoHook(0x5A6A50, ReflectSpell);
            _PI->WriteLoHook(0x5A13E5, MagicMirrorMassReflection);
        }
    }

    return TRUE;
}

Отражённые Magic Mirror заклы, естественно, не резистятся, потому что для перенаправленного закла нужна своя проверка на резист. Позже добавлю такую. UPD: Добавил резист отражённых заклинаний.

* * *
Насчёт выбора целей. Вы можете выполнять Ваш алгоритм в теле функции ResetMagicMirrorFlags(). А в getEnemyStackToRedirectTo() просто "снимать" верхний в стеке/массиве отряд. В случае массива Вам придётся завести глобальную переменную, которую нужно будет инкрементировать при каждом выборе цели. Также нужно учесть случай, когда перенаправлять некуда:

Код: Выделить всё
stackFoe = getEnemyStackToRedirectTo(stack); // пусть функция возвращает 0 (NULL), когда некуда перенаправлять
if ( stackFoe ) {
    CALL_5(void, __thiscall, 0x444610, stackFoe, spell, spellPower, schoolLevel, hero);
    AnimateStack[stackFoe->side][stackFoe->index_on_side] = true;
}
Вернуться к началу

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

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

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

Самый главный баг пока еще не исправлен. Если у меня кол-во отрядов меньше чем у врага и у врага стоит Magic Mirror на всех отрядах, то после того, как я направляю любой масспелл, отражение работает неправильно. Вот, например, у меня 2 отряда, у врага 4 отряда и на всех стоит Зеркало. Я кастую Замеделение, и на один вражеский отряд оно проходит и накладывается, а три вражеских отряда его отражают. Следовательно это заклинание должно наложится на три моих отряда. Но посколько кол-во моих отрядов меньше, чем кол-во отражений, значит оно должно наложится на все мои отряды, то есть на два отряда. А накладывается только на один. Или отражает два отряда, а накладывает опять же только на один. Или, например, 3 отряда против 7. 4 из 7 отражает Зеркалом, а накладывает вместо 3 отрядов всего на 1.

По поводу резиста при масскасте:
1) Если все отряды отражают резистом направлямый закл, то анимация резиста сопровождается звуковым сопровождением направляемого закла. А должно как бы вместе с анимацией резиста проигрыватся и звук резиста.
2) Если один или несколько отрядов не отражают закл резистом, а один или несколько наоборот отражают, то вместе с анимацией резиста проигрывается звуковое сопровождение направляемого закла, а уже после идет анимация самого закла, но уже без звука. То есть правильней было бы изначально вместе с анимацией резиста проиграть и его озвучку, а уже после вместе с анимацией закла соответственно его звук.
3) Аналогично второму, но еще с Зеркалом. То есть изначально проигрывается анимация Зеркала для отрядов, которые отразили закл, после чего идет проигрываение анимации резиста для других отрядов и после уже наложение направляемого закла на свои и вражеские отряды. Вот здесь как раз очень хорошо заметно последовательная анимация. Задержка между отражением и наложением закла увеличивается, так как между ними еще вклинивается анимация резиста. Но основая проблема в том, что вместе с анимацией Зеркала проигрывается звук напрвляемого закла, а анимация резиста и следущая за ней анимация закла проигрываються вообще без звука. То есть правильней было проиграть анимацию Зеркала и вместе с ней ее звук, дальше анимацию резиста и ее звук и только после этого анимацию и звук направляемого закла.
4) Анимация резиста срабатывает только на тех существах у которых есть определенная вероятность отразить заклинание, например, - гномы. В тоже время как на существах с полным имуннитетом к определенным заклинаниям (Титаны) или ко всем заклинаниям до определенного уровня (драконы) или вообще ко всем заклинаниям (Черные драконы) - анимация резиста не работает при масскасте, а должна. Даже на Черных драконах должна срабатывать анимация резиста при масскасте. Просто если мы попытаемся наложить какой-то одиночной закл на Черных драконов, то мы не сможем этого сделать, а потому и не сможет увидеть анимацию резиста. В то же время в оригинале Волшебное зеркало может отразить направляемый закл на Черных драконов, да случайно выбранный отряд вполне может быть и Черным драконом, и вот в таком случае анимация резиста уже срабатывает.

AlexSpl, и нужна ваша помощь в том, чтобы доделать отражение на самые сильные вражеские отряды. У меня через массив, но можете через стек, на ваше усмотрение. Главное, чтобы работало. Я пытался это сделать, как вы и советовали в ResetMagicMirrorFlags, а в getEnemyStackToRedirectTo снимал, но что-то не так. Работает неверно, либо вообще получаю вылет.
Сделал свой массив глобальным и обявил еще глобальную переменную и инкрементировал ее при каждом обращении к функции getEnemyStackToRedirectTo. И, кстати, что тогда делать с хуком по адресу 0x5A05CC и функцией NewTarget, которая выбирает одиночную цель, когда нет масскаста. Просто основной код из нее ушел в ResetMagicMirrorFlags.

В общем я включил ваш код в свой:
Код: Выделить всё
#define _CRT_SECURE_NO_WARNINGS
#define AnimateStack (((bool(*)[20])&o_BattleMgr->Field<bool>(0x547C)))

#include "stdafx.h"
#include "HotA\homm3.h"
#include <utility>
#include <iostream>
#include <vector>
#include <algorithm>
#include <stack>

using namespace std;

Patcher* _P;
PatcherInstance* _PI;

static _bool_ plugin_On = 0;

bool showMagicMirrorAnim, showMagicResistAnim;
bool needMagicMirrorAnim[2][20], needMagicResistAnim[2][20];

int position;
vector <pair<int, int> > combatPowerOfStacks;

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

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


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 = NewMagicMirror;

   return EXEC_DEFAULT;
}

bool my_cmp(const pair<int, int>& a, const pair<int, int>& b)
{
   return a.first > b.first;
}

int __stdcall NewTarget(LoHook* h, HookContext* c)
{   
   int pos = 0;

   
   combatPowerOfStacks.resize(o_BattleMgr->stacks_count[o_BattleMgr->current_side]);
   
   for (int i = 0; i < combatPowerOfStacks.size(); ++i)
   {
      _BattleStack_* stack = &o_BattleMgr->stack[o_BattleMgr->current_side][i];      
      combatPowerOfStacks[i].first = o_pCreatureInfo[stack->creature_id].fight_value * stack->count_current;
      combatPowerOfStacks[i].second = i;   
   }
   
   sort(combatPowerOfStacks.begin(), combatPowerOfStacks.end(), my_cmp);
   
   for (int i = 0; i < combatPowerOfStacks.size(); ++i)
   {
      _BattleStack_* stack = &o_BattleMgr->stack[o_BattleMgr->current_side][combatPowerOfStacks[i].second];
         
      if (stack->creature_id == CID_BLACK_DRAGON || stack->creature_id == CID_MAGIC_ELEMENTAL || stack->active_spell_duration[SPL_ANTI_MAGIC]) continue; else
      {
         pos = combatPowerOfStacks[i].second;
         break;
      }
   }

   c->eax = (int)&o_BattleMgr->stack[o_BattleMgr->current_side][pos];

   return EXEC_DEFAULT;
}

char __stdcall IsSpellSingleTarget(HiHook *h, int spell, int ssLevel)
{
   if (spell == SPL_MAGIC_MIRROR && ssLevel == 3) return FALSE; else
      return CALL_2(char, __fastcall, h->GetDefaultFunc(), spell, ssLevel);   
}

// -----------------

bool magicMirrorLucky(_Hero_* hero, _BattleStack_* stack, int spell)
{
   int effSchoolLevel = CALL_3(int, __thiscall, 0x4E52F0, hero, SPL_MAGIC_MIRROR, o_BattleMgr->spec_terr_type);

   return !(o_Spell[spell].flags & SPF_FRIENDLY_HAS_MASS) && stack->side != o_BattleMgr->current_side &&
      stack->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(1, 100) <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel];
}

_BattleStack_* getEnemyStackToRedirectTo(_BattleStack_* stack)
{
   // В целях демонстрации работы просто перенаправим заклинание на вражеский отряд
   // с таким же индексом, как у отряда, отразившего заклинание,
   // поэтому тестируйте с полными армиями во избежание сюрпризов

    return &o_BattleMgr->stack[1 - stack->side][stack->index_on_side];
   
   //position++;
   //return &o_BattleMgr->stack[o_BattleMgr->current_side][combatPowerOfStacks[position].second];
}

int __stdcall ResetMagicMirrorFlags(LoHook* h, HookContext* c)
{
   showMagicMirrorAnim = false;
   showMagicResistAnim = false;
   memset(needMagicMirrorAnim, false, 40);
   memset(needMagicResistAnim, false, 40);

   // -----

   combatPowerOfStacks.resize(o_BattleMgr->stacks_count[o_BattleMgr->current_side]);

   for (int i = 0; i < combatPowerOfStacks.size(); ++i)
   {
      _BattleStack_* stack = &o_BattleMgr->stack[o_BattleMgr->current_side][i];
      combatPowerOfStacks[i].first = o_pCreatureInfo[stack->creature_id].fight_value * stack->count_current;
      combatPowerOfStacks[i].second = i;
   }

   sort(combatPowerOfStacks.begin(), combatPowerOfStacks.end(), my_cmp);

   // -----

   return EXEC_DEFAULT;
}

int __stdcall ReflectSpell(LoHook* h, HookContext* c)
{
   _BattleStack_* stack = (_BattleStack_*)c->esi;
   _BattleStack_* stackFoe;

   int spell = *(int*)(c->ebp + 0xC);
   int spellPower = *(int*)(c->ebp + 0x14);
   int schoolLevel = *(int*)(c->ebp + 0x10);
   int casterSide = *(int*)(c->ebp + 0x18);
   _Hero_* hero = *(_Hero_**)(c->ebp + 8);

   // Если сработало Magic Mirror, перенаправляем заклинание на вражеский отряд
   if (magicMirrorLucky(o_BattleMgr->hero[1 - o_BattleMgr->current_side], stack, spell))
   {
      showMagicMirrorAnim = true;
      needMagicMirrorAnim[stack->side][stack->index_on_side] = true;
      stackFoe = getEnemyStackToRedirectTo(stack);
      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);
            AnimateStack[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)
      {
         // Если не сработал резист, применяем эффект заклинания
         CALL_5(void, __thiscall, 0x444610, stack, spell, spellPower, schoolLevel, hero);
         AnimateStack[stack->side][stack->index_on_side] = true;
      }
      // Если сработал резист, отмечаем отряд как требующий анимации резиста
      else if (c->eax)
      {
         showMagicResistAnim = true;
         needMagicResistAnim[stack->side][stack->index_on_side] = true;
      }
   }

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

int __stdcall MagicMirrorMassReflection(LoHook* h, HookContext* c)
{
   // Проигрываем массовую анимацию Magic Mirror
   if (showMagicMirrorAnim)
      CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicMirrorAnim, o_Spell[SPL_MAGIC_MIRROR].animation_ix, 0);

   // Проигрываем массовую анимацию резиста
   if (showMagicResistAnim)
      CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicResistAnim, 78, 0);

   return EXEC_DEFAULT;
}

// -----------------

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
   if (DLL_PROCESS_ATTACH == ul_reason_for_call)
   {
      if (!plugin_On)
      {
         plugin_On = 1;
         _P = GetPatcher();
         _PI = _P->CreateInstance("HD.Plugin.NewMagicMirror");

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

         _PI->WriteLoHook(0x5A13D2, ResetMagicMirrorFlags);
         _PI->WriteLoHook(0x5A6A50, ReflectSpell);
         _PI->WriteLoHook(0x5A13E5, MagicMirrorMassReflection);
      }
   }

   return TRUE;
}
Вернуться к началу

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

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

Когда Вы реализуете отражение 1:1 / 1 в пустоту, он исчезнет (не зря же я коммент писал про тестирование на полных армиях).

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

Цитата:
4) Анимация резиста срабатывает только на тех существах у которых есть определенная вероятность отразить заклинание, например, - гномы. В тоже время как на существах с полным имуннитетом к определенным заклинаниям (Титаны) или ко всем заклинаниям до определенного уровня (драконы) или вообще ко всем заклинаниям (Черные драконы) - анимация резиста не работает при масскасте, а должна.

Для существ, иммунных к заклинанию, анимация резиста и не должна проигрываться, как, например, для боевых машин. Но если очень хочется, то можете удалить проверки if ( resistPercentage ) и if ( c->eax ), однако тогда Вам придётся проверять наличие/отсутствие иммунитетов самостоятельно.

Цитата:
Даже на Черных драконах должна срабатывать анимация резиста при масскасте.

Тут на вкус и цвет. Я считаю, что это лишнее, как и анимация резиста для боевых машин. Для одиночного каста анимация нужна, чтобы игрок видел, куда ушло его заклинание, т.к. при одиночном касте Волшебное зеркало всегда находит цель. А при масскасте заклинание может уходить "в пустоту". У иммунных существ, как и у боевых машин, resistPercentage = 0 (кстати, нужно переименовать: вероятность резиста равна 100 минус эта величина), поэтому на них заклинания и не срабатывают.

Цитата:
И, кстати, что тогда делать с хуком по адресу 0x5A05CC и функцией NewTarget, которая выбирает одиночную цель, когда нет масскаста. Просто основной код из нее ушел в ResetMagicMirrorFlags.

Оставьте код для случая одиночного каста, он не пересекается с масскастом.

По алгоритму. Вы заполняете и сортируете массив указателей на отряды (_BattleStack_*) в ResetMagicMirrorFlags(). Можно, кстати, сразу переименовать функцию в какой-нибудь dataInit(). Далее, устанавливаете индекс (пусть будет iStack), который будет выбирать отряд из этого массива, в 0. В getEnemyStackToRedirectTo() Вы возвращаете указатель на отряд с индексом iStack, инкрементируя индекс. Если не хотите работать с указателями, можно так:

Код: Выделить всё
return &o_BattleMgr->stack[1 - o_BattleMgr->current_side][combatPowerOfStacks[position++].second];

Должно работать. Да, и не забудьте про контроль индекса массива. Если следующий индекс выходит за границы, возвращайте 0 (соответствует случаю, когда заклинание отражается "в пустоту").

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

Код: Выделить всё
sprintf(o_TextBuffer, "Заклинание будет отражено на отряд с индексом %d", combatPowerOfStacks[position].second); // Здесь можно также выводить что это за отряд
b_MsgBox(o_TextBuffer, MBX_OK);
Вернуться к началу

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

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

Сообщение Rolex » 17 мар 2021, 12:06

AlexSpl писал(а):

Когда Вы реализуете отражение 1:1 / 1 в пустоту, он исчезнет (не зря же я коммент писал про тестирование на полных армиях).

Я не за исчезновения лишних.

Rolex писал(а):

Вот, например, у меня 2 отряда, у врага 4 отряда и на всех стоит Зеркало. Я кастую Замеделение, и на один вражеский отряд оно проходит и накладывается, а три вражеских отряда его отражают. Следовательно это заклинание должно наложится на три моих отряда. Но посколько кол-во моих отрядов меньше, чем кол-во отражений, значит оно должно наложится на все мои отряды, то есть на два отряда. А накладывается только на один. Или отражает два отряда, а накладывает опять же только на один. Или, например, 3 отряда против 7. 4 из 7 отражает Зеркалом, а накладывает вместо 3 отрядов всего на 1.

Как можно видеть в последнем примере у одного героя полная армия. И из 7 отрядов 4 отряда отражает направляемый закл, и следовательно должно наложится на 4 моих отряда, но кол-во моих отрядов равно 3, следовательно наложится должно 3 и 1 исчезнуть. А происходит наооборот, накладывается 1, а 3 исчезает. Должно же работать с любыми армиями.

AlexSpl писал(а):

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

Дело в том, что при одиночном касте на выбранный отряд, когда срабатывает резист, то вместе с анимацией резиста проигрывается и звук резиста. А при масскасте даже если на всех отрядах сработает резист, все равно проиграется звук направляемого закла. Раз у каждой анимации есть свой звук, и мы проигрываем каждую анимацию последовательно, то нам ничего не мешает последовательно проигрывать и звук, который соответствует каждой из анимаций. А то как-то не очень: проигрывается анимация Зеркала, а вместе с ней звук Замедления, после чего проирывается анимация резиста без звука, а после нее анимация самого закла, опять же без звука.
Вернуться к началу

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

Цитата:
Как можно видеть в последнем примере у одного героя полная армия. И из 7 отрядов 4 отряда отражает направляемый закл, и следовательно должно наложится на 4 моих отряда, но кол-во моих отрядов равно 3, следовательно наложится должно 3 и 1 исчезнуть. А происходит наооборот, накладывается 1, а 3 исчезает. Должно же работать с любыми армиями.

А вы смотрели индексы? Что, по-Вашему, происходит, когда игра пытается наложить заклинание на несуществующий стек? Если написано для тестов с полной армией, значит, для тестов с полной армией :smile2: Хотите, просто инкрементируйте индекс, но не забывайте его сбрасывать (устанавливать в 0) в ResetMagicMirrorFlags(), и проблема исчезнет.

Цитата:
Раз у каждой анимации есть свой звук, и мы проигрываем каждую анимацию последовательно, то нам ничего не мешает последовательно проигрывать и звук, который соответствует каждой из анимаций.

Логично в случае последовательной анимации. Посмотрю, где начинается воспроизведение звука при масскасте.
Вернуться к началу

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

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

Сообщение Rolex » 17 мар 2021, 12:32

AlexSpl писал(а):

А вы смотрели индексы? Что, по-Вашему, происходит, когда игра пытается наложить заклинание на несуществующий стек? Если написано для тестов с полной армией, значит, для тестов с полной армией :smile2: Хотите, просто инкрементируйте индекс, но не забывайте его сбрасывать (устанавливать в 0) в ResetMagicMirrorFlags(), и проблема исчезнет.

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

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 » 17 мар 2021, 12:37

Звук нашёл. Попробую сделать подходящее сопровождения всех трёх эффектов.
Вернуться к началу

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 » 17 мар 2021, 13:06

Звук сейчас потестить не могу, но попробуйте так:

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

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

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

_BattleStack_* getEnemyStackToRedirectTo(_BattleStack_* stack)
{
    // В целях демонстрации работы просто перенаправим заклинание на первый доступный вражеский отряд
    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 dataInit(LoHook* h, HookContext* c)
{
    showMagicMirrorAnim = false;
    showMagicResistAnim = false;
    memset(needMagicMirrorAnim, false, 40);
    memset(needMagicResistAnim, false, 40);
    stackIndex = 0;
   
    return EXEC_DEFAULT;
}

int __stdcall reflectSpell(LoHook* h, HookContext* c)
{
    _BattleStack_* stack = (_BattleStack_*)c->esi;
   
    int spell = *(int*)(c->ebp + 0xC);
    int spellPower = *(int*)(c->ebp + 0x14);
    int schoolLevel = *(int*)(c->ebp + 0x10);
    int casterSide = *(int*)(c->ebp + 0x18);
    _Hero_* hero = *(_Hero_**)(c->ebp + 8);
   
    // Если сработало Magic Mirror, перенаправляем заклинание на вражеский отряд
    if ( magicMirrorLucky(o_BattleMgr->hero[1 - o_BattleMgr->current_side], stack, spell) )
    {
        showMagicMirrorAnim = true;
        needMagicMirrorAnim[stack->side][stack->index_on_side] = true;
        _BattleStack_* stackFoe = getEnemyStackToRedirectTo(stack);
        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);
                AnimateStack[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 )
        {
            // Если не сработал резист, применяем эффект заклинания
            CALL_5(void, __thiscall, 0x444610, stack, spell, spellPower, schoolLevel, hero);
            AnimateStack[stack->side][stack->index_on_side] = true;
        }
        // Если сработал резист, отмечаем отряд как требующий анимации резиста
        else if ( c->eax )
        {
            showMagicResistAnim = true;
            needMagicResistAnim[stack->side][stack->index_on_side] = true;
        }
    }
   
    c->return_address = 0x5A6A83;
    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);
    }

    // Проигрываем звук кастуемого заклинания
    _Spell_* spell = (_Spell_*)*(int*)(c->ebp - 0x10);
    CALL_3(void, __fastcall, 0x59A890, spell->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(0x5A13D2, dataInit);
            _PI->WriteLoHook(0x5A6A50, reflectSpell);
            _PI->WriteLoHook(0x5A13E5, massReflection);

            // Пропускаем оригинальный звук
            _PI->WriteByte(0x5A0634, 0xEB);
        }
    }

    return TRUE;
}

UPD: Услышать не могу, но могу отыскать баги. Исправлено :smile1: Заодно исправил алгоритм выбора цели для отражённого заклинания на более "предсказуемый".
Вернуться к началу

Пред.След.

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

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

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

cron