Объявления

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

Любой подойдёт. Вот это - o_BattleMgr->hex[hex_id].GetCreature() - есть _BattleStack_*.
Вернуться к началу

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

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

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

За это я в курсе. Ок. Буду разбираться.
Вернуться к началу

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

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

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

В общем написал я алгоритм (функция getHexId) отражения для площадных заклов. Плюс еще функцию проверки существ на площадные заклы (checkingTheSelectedTargetForAreaSpells).

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

Что подобный алгоритм нам дает? Максимум профита. Например, один из наших отрядов, допустим, 10 Копейщиков, стоит между десятком вражеских Архангелов и десятком Титанов, которые стоят по рызные стороны от наших Копейщиков.
Враг с приличным навыком СМ кастует на другой наш отряд, который под Зеркалом, Инферно. Если других более выгодных вариантов нет, то в итоге Инферно отразится так, чтобы задеть как Архангелов, так и Титанов. Если это будет возможно сделать так, чтобы не задеть наш отряд Копейщиков, то это будет сделано. Если это будет невозможно, то Инферно накроет и наш отряд, но тот доп урон, который будет нанесен, либо Архангелам, либо Титанам, будет гораздо больше и ценее чем наши 10 Копейщиков.
Если же наш ценный отряд, например, в виде 10 Архангелов стоит между различными вражескими отрядами, то Инферно будет аккуратно выплясывать вокруг Архангелов и не задевая их уничтожать все вражеские отряды вокруг в порядке убывания их силы.

Вообще интересный алгоритм получился. И как я его только написал... :smile5:

Вообще в эту функцию getHexId можно передавать вместе со спеллом еще и кол-во отрядов, которые отразили закл и вместо максимального значения по отрядам находить в готовом массиве именно нужное кол-во и после среди них (если вариант не один), находить наболее выгодный для атаки.
Если будет невозможно развернуть закл на то кол-во вражеских отрядов, которое равно кол-ву отражений, то можно будет уменьшать на 1 отряд до тех пор пока не найдется такое, которое будет меньше нужного, но при этом максимально возможное.

Кстати, проверять указатель на 0, было недостаточно, все равно вылетало. Только после того, как дабавил еще проверку на выход за границы поля боя, все стало ок.

AlexSpl, а где у вас в функции areaReflection хранится кол-во отрядов, которые отразили направленный закл?

1) Функция проверки существ на площадные заклы checkingTheSelectedTargetForAreaSpells:

Код: Выделить всё
bool checkingTheSelectedTargetForAreaSpells(_BattleStack_* stack, int spell)
{
   bool isSubjectToBeAttacked = true;

   if (spell == SPL_BERSERK)
   {
      int monsterID[] = { 83, 27, 121, 32, 33, 116, 117, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 141, 112, 113, 114, 115, 120, 121, 123, 125, 127, 129, 130, 131, 145, 146, 147, 148, 149 };

      for (int i = 0; i < 36; ++i)
         if (stack->creature_id == monsterID[i])
         {
            isSubjectToBeAttacked = false;
            break;
         }
   }
   else
      if (spell == SPL_METEOR_SHOWER)
      {
         int monsterID[] = { 83, 27, 121, 112, 127 };

         for (int i = 0; i < 5; ++i)
            if (stack->creature_id == monsterID[i])
            {
               isSubjectToBeAttacked = false;
               break;
            }
      }
      else
         if (spell == SPL_INFERNO)
         {
            int monsterID[] = { 83, 27, 121, 130, 131, 114, 129, 52, 53 };

            for (int i = 0; i < 9; ++i)
               if (stack->creature_id == monsterID[i])
               {
                  isSubjectToBeAttacked = false;
                  break;
               }
         }
         else
            if (spell == SPL_FIREBALL)
            {
               int monsterID[] = { 83, 27, 121, 132, 82, 26, 130, 131, 114, 129, 52, 53 };

               for (int i = 0; i < 12; ++i)
                  if (stack->creature_id == monsterID[i])
                  {
                     isSubjectToBeAttacked = false;
                     break;
                  }
            }
            else
               if (spell == SPL_FROST_RING)
               {
                  int monsterID[] = { 83, 27, 121, 132, 82, 26, 115, 123 };

                  for (int i = 0; i < 8; ++i)
                     if (stack->creature_id == monsterID[i])
                     {
                        isSubjectToBeAttacked = false;
                        break;
                     }
               }

   return isSubjectToBeAttacked;
}


2) Функция отражения площадных заклов getHexId (вызывается прямо из areaReflection в вызове CALL_5):

Код: Выделить всё
int getHexId(int spell)
{
   int maxCount = 0;
   int maxValue = 0;
   int maxHex = -1;

   bool flagCurSide = false;
   bool flagEnemySide = false;

   int SumHex;

   int dev;
   _BattleStack_* hex[19];

   vector <pair<int, int>>  countCurSide;
   vector <pair<int, int>>  countEnemySide;

   countCurSide.resize(187);
   countEnemySide.resize(187);

   if (spell == SPL_FROST_RING) SumHex = 6; else
      if (spell == SPL_METEOR_SHOWER || spell == SPL_FIREBALL) SumHex = 7; else
         if (spell == SPL_INFERNO) SumHex = 19;

   for (int i = 0; i < 187; ++i)
   {
      flagCurSide = false;
      flagEnemySide = false;

      if (((i / 17) + 1) % 2 == 0) dev = 0; else dev = 1;

      int id[] = { dev - 17, dev - 18, dev + 16, dev + 17, -1, 1, 0, -2, 2,
                dev - 16, dev - 19, dev + 15, dev + 18, -33, -34, -35, 33, 34, 35 };

      for (int j = 0; j < SumHex; ++j)
            hex[j] = o_BattleMgr->hex[i + id[j]].GetCreature();

      for (int j = 0; j < SumHex; ++j)
      {
         if (i + id[j] < 0 || i + id[j] > 186) continue;

         if (hex[j] != 0)
         {
            if (hex[j]->creature_id >= 0 && hex[j]->creature_id <= 149 && checkingTheSelectedTargetForAreaSpells(hex[j], spell))
            {
               if (hex[j]->side == o_BattleMgr->current_side)
               {
                  if (hex[j]->creature.flags & BCF_2HEX_WIDE)
                  {
                     if (!flagCurSide)
                     {                        
                        countCurSide[i].first++;
                        countCurSide[i].second += o_pCreatureInfo[hex[j]->creature_id].fight_value * hex[j]->count_current;
                        flagCurSide = true;
                     }
                     else flagCurSide = false;
                  }
                  else
                  {
                     countCurSide[i].first++;
                     countCurSide[i].second += o_pCreatureInfo[hex[j]->creature_id].fight_value * hex[j]->count_current;
                  }                     
               }
               else
               {
                  if (hex[j]->creature.flags & BCF_2HEX_WIDE)
                  {
                     if (!flagEnemySide)
                     {
                        countEnemySide[i].first++;
                        countEnemySide[i].second += o_pCreatureInfo[hex[j]->creature_id].fight_value * hex[j]->count_current;
                        flagEnemySide = true;
                     }
                     else flagEnemySide = false;
                  }
                  else
                  {
                     countEnemySide[i].first++;
                     countEnemySide[i].second += o_pCreatureInfo[hex[j]->creature_id].fight_value * hex[j]->count_current;
                  }                     
               }
            }
         }
      }
   }
   
   for (int i = 0; i < 187; ++i)
      if (countCurSide[i].first - countEnemySide[i].first > maxCount)
         maxCount = countCurSide[i].first - countEnemySide[i].first;

   for (int i = 0; i < 187; ++i)
      if (countCurSide[i].first - countEnemySide[i].first == maxCount)
      {
         if (countCurSide[i].second - countEnemySide[i].second > maxValue)
         {
            maxValue = countCurSide[i].second - countEnemySide[i].second;
            maxHex = i;
         }
      }

   return maxHex;
}


Итоговый код с двумя новыми функциями (плюс мелкое исправление в checkingTheSelectedTarget, шло обращение к несуществующим элементам массива):

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

using namespace std;

Patcher* _P;
PatcherInstance* _PI;

bool showMagicMirrorAnim, showMagicResistAnim, showSpellAnim, redirectAreaSpell;
bool needMagicMirrorAnim[2][20], needMagicResistAnim[2][20], needSpellAnim[2][20];
vector <pair<int, int>> combatPowerOfStacks;
vector <pair<int, int>> combatPowerOfStacksIsSpell;
int stackIndex;

#define SPL_DEATH_RIPPLE 24
#define CID_AZURE_DRAGON 132

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

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

_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 checkingTheSelectedTargetForAreaSpells(_BattleStack_* stack, int spell)
{
   bool isSubjectToBeAttacked = true;

   if (spell == SPL_BERSERK)
   {
      int monsterID[] = { 83, 27, 121, 32, 33, 116, 117, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 141, 112, 113, 114, 115, 120, 121, 123, 125, 127, 129, 130, 131, 145, 146, 147, 148, 149 };

      for (int i = 0; i < 36; ++i)
         if (stack->creature_id == monsterID[i])
         {
            isSubjectToBeAttacked = false;
            break;
         }
   }
   else
      if (spell == SPL_METEOR_SHOWER)
      {
         int monsterID[] = { 83, 27, 121, 112, 127 };

         for (int i = 0; i < 5; ++i)
            if (stack->creature_id == monsterID[i])
            {
               isSubjectToBeAttacked = false;
               break;
            }
      }
      else
         if (spell == SPL_INFERNO)
         {
            int monsterID[] = { 83, 27, 121, 130, 131, 114, 129, 52, 53 };

            for (int i = 0; i < 9; ++i)
               if (stack->creature_id == monsterID[i])
               {
                  isSubjectToBeAttacked = false;
                  break;
               }
         }
         else
            if (spell == SPL_FIREBALL)
            {
               int monsterID[] = { 83, 27, 121, 132, 82, 26, 130, 131, 114, 129, 52, 53 };

               for (int i = 0; i < 12; ++i)
                  if (stack->creature_id == monsterID[i])
                  {
                     isSubjectToBeAttacked = false;
                     break;
                  }
            }
            else
               if (spell == SPL_FROST_RING)
               {
                  int monsterID[] = { 83, 27, 121, 132, 82, 26, 115, 123 };

                  for (int i = 0; i < 8; ++i)
                     if (stack->creature_id == monsterID[i])
                     {
                        isSubjectToBeAttacked = false;
                        break;
                     }
               }

   return isSubjectToBeAttacked;
}

bool checkingTheSelectedTarget(_BattleStack_* stack, int spell)
{
   int effSchoolLevel = stack->active_spells_power[SPL_ANTI_MAGIC];
   if (effSchoolLevel == 0) effSchoolLevel = 1;

   bool isSubjectToBeAttacked = true;

   if (spell == SPL_MAGIC_ARROW || spell == SPL_MISFORTUNE)
   {
      int monsterID[] = { 52, 53, 114, 129, 130, 131 };

      for (int i = 0; i < 6; ++i)
         if (stack->creature_id == monsterID[i])
         {
            isSubjectToBeAttacked = false;
            break;
         }
   }
   else
      if (spell == SPL_ICE_BOLT)
      {
         if (stack->creature_id == 115 || stack->creature_id == 123)
            isSubjectToBeAttacked = false;
      }
      else
         if (spell == SPL_FORGETFULNESS)
         {
            int monsterID[] = { 41, 64, 65, 123, 127 };

            for (int i = 0; i < 5; ++i)
               if (stack->creature_id == monsterID[i])
               {
                  isSubjectToBeAttacked = false;
                  break;
               }
         }
         else
            if (spell == SPL_CURSE)
            {
               int monsterID[] = { 52, 53, 114, 129, 130, 131, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 141 };

               for (int i = 0; i < 21; ++i)
                  if (stack->creature_id == monsterID[i])
                  {
                     isSubjectToBeAttacked = false;
                     break;
                  }
            }
            else
               if (spell == SPL_BLIND || spell == SPL_SORROW)
               {
                  int monsterID[] = { 32, 33, 116, 117, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 141, 112, 113,
                     114, 115, 120, 121, 123, 125, 127, 129, 130, 131, 145, 146, 147, 148, 149 };

                  for (int i = 0; i < 36; ++i)
                     if (stack->creature_id == monsterID[i])
                     {
                        isSubjectToBeAttacked = false;
                        break;
                     }
                  if (spell == SPL_BLIND && (stack->creature_id == 70 || stack->creature_id == 71)) isSubjectToBeAttacked = false;
                  if (spell == SPL_SORROW && (stack->creature_id == 40 || stack->creature_id == 41)) isSubjectToBeAttacked = false;
               }
               else
                  if (spell == SPL_SLOW || spell == SPL_IMPLOSION)
                  {
                     int monsterID[] = { 145, 146, 147, 148, 149 };

                     for (int i = 0; i < 5; ++i)
                        if (stack->creature_id == monsterID[i])
                        {
                           isSubjectToBeAttacked = false;
                           break;
                        }
                  }
                  else
                     if (spell == SPL_LIGHTNING_BOLT || spell == SPL_CHAIN_LIGHTNING || spell == SPL_TITANS_LIGHTNING_BOLT)
                     {
                        if (stack->creature_id == 113 || stack->creature_id == 125)
                           isSubjectToBeAttacked = false;
                     }


   return !(stack->creature_id == CID_BLACK_DRAGON || stack->creature_id == CID_MAGIC_ELEMENTAL || stack->creature_id == CID_GOLD_DRAGON && o_Spell[spell].level <= 4 ||
      (stack->creature_id == CID_GREEN_DRAGON || stack->creature_id == CID_RED_DRAGON || stack->creature_id == CID_AZURE_DRAGON) && o_Spell[spell].level <= 3 ||
      stack->active_spell_duration[SPL_ANTI_MAGIC] && o_Spell[spell].level <= effSchoolLevel + 2 ||
      (spell == SPL_FORGETFULNESS && (!(stack->creature.flags & BCF_CAN_SHOOT) || (stack->creature.flags & BCF_CAN_SHOOT) && stack->creature.shots == 0)) || !isSubjectToBeAttacked);
}

char __stdcall massSpellMagicMirror(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(const _BattleStack_* stack, const int spell)
{
   int effSchoolLevel = stack->active_spells_power[SPL_MAGIC_MIRROR];

   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(_BattleStack_* stack, int spell)
{
   int sideFoe = 1 - stack->side;
   _BattleStack_* stackFoe = combatPowerOfStacks[stackIndex].second < o_BattleMgr->stacks_count[sideFoe] &&
      !o_BattleMgr->stack[sideFoe][combatPowerOfStacks[stackIndex].second].is_killed ? &o_BattleMgr->stack[sideFoe][combatPowerOfStacks[stackIndex].second] : 0;
   ++stackIndex;

   return stackFoe;
}

int __stdcall newTarget(LoHook* h, HookContext* c)
{
   int pos = 0;
   int minDuration = INT_MAX;

   int count = 0;

   int spell = *(int*)(c->ebp + 8);

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

   for (unsigned i = 0; i < combatPowerOfStacks.size(); ++i)
   {
      _BattleStack_* stack = &o_BattleMgr->stack[o_BattleMgr->current_side][i];


      if (checkingTheSelectedTarget(stack, spell))
      {
         combatPowerOfStacks[count].first = o_pCreatureInfo[stack->creature_id].fight_value * stack->count_current;
         combatPowerOfStacks[count].second = i;
         count++;
      }
   }

   combatPowerOfStacks.resize(count);

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

   for (int i = 0; i < count; ++i)
   {
      _BattleStack_* stack = &o_BattleMgr->stack[o_BattleMgr->current_side][combatPowerOfStacks[i].second];

      if (stack->active_spell_duration[spell] == 0)
      {
         pos = combatPowerOfStacks[i].second;
         break;
      }
      else
         if (stack->active_spell_duration[spell] < minDuration)
         {
            minDuration = stack->active_spell_duration[spell];
            pos = combatPowerOfStacks[i].second;
         }
   }

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

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

int getHexId(int spell)
{
   int maxCount = 0;
   int maxValue = 0;
   int maxHex = -1;

   bool flagCurSide = false;
   bool flagEnemySide = false;

   int SumHex;

   int dev;
   _BattleStack_* hex[19];

   vector <pair<int, int>>  countCurSide;
   vector <pair<int, int>>  countEnemySide;

   countCurSide.resize(187);
   countEnemySide.resize(187);

   if (spell == SPL_FROST_RING) SumHex = 6; else
      if (spell == SPL_METEOR_SHOWER || spell == SPL_FIREBALL) SumHex = 7; else
         if (spell == SPL_INFERNO) SumHex = 19;

   for (int i = 0; i < 187; ++i)
   {
      flagCurSide = false;
      flagEnemySide = false;

      if (((i / 17) + 1) % 2 == 0) dev = 0; else dev = 1;

      int id[] = { dev - 17, dev - 18, dev + 16, dev + 17, -1, 1, 0, -2, 2,
                dev - 16, dev - 19, dev + 15, dev + 18, -33, -34, -35, 33, 34, 35 };

      for (int j = 0; j < SumHex; ++j)
            hex[j] = o_BattleMgr->hex[i + id[j]].GetCreature();

      for (int j = 0; j < SumHex; ++j)
      {
         if (i + id[j] < 0 || i + id[j] > 186) continue;

         if (hex[j] != 0)
         {
            if (hex[j]->creature_id >= 0 && hex[j]->creature_id <= 149 && checkingTheSelectedTargetForAreaSpells(hex[j], spell))
            {
               if (hex[j]->side == o_BattleMgr->current_side)
               {
                  if (hex[j]->creature.flags & BCF_2HEX_WIDE)
                  {
                     if (!flagCurSide)
                     {                        
                        countCurSide[i].first++;
                        countCurSide[i].second += o_pCreatureInfo[hex[j]->creature_id].fight_value * hex[j]->count_current;
                        flagCurSide = true;
                     }
                     else flagCurSide = false;
                  }
                  else
                  {
                     countCurSide[i].first++;
                     countCurSide[i].second += o_pCreatureInfo[hex[j]->creature_id].fight_value * hex[j]->count_current;
                  }                     
               }
               else
               {
                  if (hex[j]->creature.flags & BCF_2HEX_WIDE)
                  {
                     if (!flagEnemySide)
                     {
                        countEnemySide[i].first++;
                        countEnemySide[i].second += o_pCreatureInfo[hex[j]->creature_id].fight_value * hex[j]->count_current;
                        flagEnemySide = true;
                     }
                     else flagEnemySide = false;
                  }
                  else
                  {
                     countEnemySide[i].first++;
                     countEnemySide[i].second += o_pCreatureInfo[hex[j]->creature_id].fight_value * hex[j]->count_current;
                  }                     
               }
            }
         }
      }
   }
   
   for (int i = 0; i < 187; ++i)
      if (countCurSide[i].first - countEnemySide[i].first > maxCount)
         maxCount = countCurSide[i].first - countEnemySide[i].first;

   for (int i = 0; i < 187; ++i)
      if (countCurSide[i].first - countEnemySide[i].first == maxCount)
      {
         if (countCurSide[i].second - countEnemySide[i].second > maxValue)
         {
            maxValue = countCurSide[i].second - countEnemySide[i].second;
            maxHex = i;
         }
      }

   return maxHex;
}

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

   // Проигрываем массовую анимацию и звук резиста
   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);
      
      if (getHexId(spell) != -1) CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, getHexId(spell), spell, a3, a4);

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

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

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

      int count = 0;
      int countIsSpell = 0;

      for (unsigned i = 0; i < combatPowerOfStacks.size(); ++i)
      {
         _BattleStack_* stack = &o_BattleMgr->stack[o_BattleMgr->current_side][i];

         if (checkingTheSelectedTarget(stack, spell))
         {
            if (stack->active_spell_duration[spell] == 0)
            {
               combatPowerOfStacks[count].first = o_pCreatureInfo[stack->creature_id].fight_value * stack->count_current;
               combatPowerOfStacks[count].second = i;
               count++;
            }
            else
            {
               combatPowerOfStacksIsSpell[countIsSpell].first = o_pCreatureInfo[stack->creature_id].fight_value * stack->count_current;
               combatPowerOfStacksIsSpell[countIsSpell].second = i;
               countIsSpell++;
            }
         }
      }

      combatPowerOfStacks.resize(count);
      combatPowerOfStacksIsSpell.resize(countIsSpell);

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

      for (int i = 0; i < countIsSpell; ++i)
      {
         _BattleStack_* stack = &o_BattleMgr->stack[o_BattleMgr->current_side][combatPowerOfStacksIsSpell[i].second];
         combatPowerOfStacksIsSpell[i].first = stack->active_spell_duration[spell];
      }

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

      combatPowerOfStacks.resize(count + countIsSpell);

      for (int i = count; i < count + countIsSpell; ++i)
         combatPowerOfStacks[i] = combatPowerOfStacksIsSpell[count + countIsSpell - i - 1];


      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 = 0;
   _BattleStack_* 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 = (_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;


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

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

   return TRUE;
}


После тщательного тестирования имеющегося кода на данный момент обнаружил следующие баги:

1) Звук FIREBALL (Огненный шар) проигрывается уже после проигрывания самой анимации. А должен одновременно с анимацией проигрываться, как у других заклов. С другими ок.

2) Берсерк отражается не эффективно, все из-за отсутствия проверки на иммунитет существ к площадным заклам. Я написал функцию checkingTheSelectedTargetForAreaSpells на площадные заклы, подобно функции для обычных заклов, которая проверяет существ на иммунитет ко всем площадным заклам.
Добавьте вызов этой функции в своей реализации Берсерка перед тем, как перенаправлять его на вражеские отряды, чтобы исключить из списка целей всех существ на которых этот закл попросту не наложится. Таким образом мы не будем терять отражения, а будем перенапрявлять их на других существ на которых он наложится (в порядке убывания силы оставшихся отрядов).

3) Дамажащие площадные заклы при касте на своих не отражаються на вражеские отряды, а отражаються обратно на свои.

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

4) Для Армагеддона нужно анимацию резиста и Зеркала сделать уже после анимации самого Армагеддона. Аналогично боевым площадным заклам.

5) Заклинания нейтралов (отрядов без героя) не отражает на самих же нейтралов. Надо бы сделать, чтобы отражало.

AlexSpl писал(а):

Ещё потестируйте Death Ripple. Наверняка заметили, что код для Destroy Undead отличается. Там очень важно сохранить edi.


У Вас получилось его сохранить или могут быть проблемы?

6) Проблемы заметил следующие:

Death Ripple - что-то не то со звуком. Звуковое сопровождение от другого закла. В оригинале у него звук другой.

Общая СЕРЬЕЗНАЯ проблема Death Ripple и Destroy Undead:
Вот здесь вообще забавная штука происходит. Короче, если одним из этих заклов отряд был полностью уничтожен, то картинка живого существа не заменятся на картинку мертвого. Просто исчезает количество и вместо трупака остается висеть якобы живое существо, но без количества. Прямо таки настоящая нежить. Клетка, которою занимал отряд становится свободной (на нее можно стать).

AlexSpl писал(а):

Для AI нужно ещё проверку на синглкаст сделать нормальную.

А это делали?

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

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

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

Сообщение AlexSpl » 28 мар 2021, 23:51

Пройдусь на этой неделе ещё раз по всем хукам. Что-то уже представляю, как исправить, где-то нужно разбираться (например, с Death Ripple и Destroy Undead ситуация странная).

Цитата:
Для AI нужно ещё проверку на синглкаст сделать нормальную.

Это сделал для героев AI. Там фишка была в том, что для синглкаста AI пропускается код отражения Magic Mirror. Нейтралов отдельно смотреть нужно, конечно.

Цитата:
3) Дамажащие площадные заклы при касте на своих не отражаються на вражеские отряды, а отражаються обратно на свои.

Тут тоже смотреть нужно. В область действия могут попасть как свои, так и чужие отряды под Magic Mirror. Не отражать же два раза? Не знаю, как лучше сделать. Может, от дружественных всё-таки не отражать?
Вернуться к началу

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

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

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

Цитата:
В область действия могут попасть как свои, так и чужие отряды под Magic Mirror. Не отражать же два раза? Не знаю, как лучше сделать. Может, от дружественных всё-таки не отражать?

Только тогда возникает вопрос, а что тогда делать с Берсерком? Ведь он то отражается от своих на вражеские отряды. Убирать у него это? Думаю все же, что от дружественных лучше тогда и не отражать. Просто игнор без отражения с проигрыванием анимации Зеркала.

Я подобным образом сделал когда в армии кастующего остались существа с иммунитетом от направляемого закла. Можно конечно было сделать на них перенаправление, но смысла в этом нет, так как на них это не подействует, а отряды отразившего, которые могут находится рядом с нашим иммуным монстром могут получить урон. В ином же случае закл будет проигрываться на нулевом гексе (или на -1), что тоже не очень хорошо, особенно в случае с Инферно, так как в этой области также могут находится отряды отразившего. Поэтому пришлось сделать так:

Код: Выделить всё
if (getHexId(spell) != -1) CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, getHexId(spell), spell, a3, a4);
Вернуться к началу

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 » 29 мар 2021, 14:30

Решил взяться за оптимизацию кода и обратил внимание на то, что функции checkingTheSelectedTargetForAreaSpells() и сheckingTheSelectedTarget() проверяют, попадают ли существа в отряде в список монстров, иммунных к заклинанию. Но логичнее это делать с помощью игровой функции, которая чекает разные нюансы. Например, арты (сразу коварная связка Orb of Vulnerability + Black Dragons вспоминается), специализацию героев, террейн:

Код: Выделить всё
float creatureMagicResistance = CALL_7(float, __thiscall, 0x5A83A0, o_BattleMgr, spell, casterSide, stack, 1, 1, 0);

Функция возвращает число от 0 до 1. Чем меньше, тем лучше (мы уже использовали эту функцию в нашем плагине). 0 означает иммунитет или полную неуязвимость к урону от заклинания, 1 - полную беззащитность. Названия аргументов сами за себя говорят, кроме последних. Первая 1 для того, чтобы учесть сопротивление магии всех отрядов (а не только вражеских; если поставить 0, не будет, например, учитываться резист гномов к касту заклинаний собственного героя), вторую 1 лучше оставить по умолчанию (если я правильно понимаю, этот флаг определяет, для какой стороны считать резисты, 1 - это для casterSide), последний 0 лучше тоже не трогать: аргумент имеет отношение к заклинаниям Resurrection и Animate Dead).
Вернуться к началу

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 » 30 мар 2021, 10:55

Общее исправление порядка анимации. Теперь первой идёт анимация заклинания, потом резиста, потом Magic Mirror. Логично показывать анимацию Magic Mirror в самом конце, так как именно после неё идёт перенаправление заклинания на вражеский отряд или в пустоту. А какую анимацию показывать первой - анимацию заклинания или резиста - в условиях отсутствия одновременной анимации, считаю, вопрос вкуса (всегда можно поменять: там просто блоки кода местами переставить).

Цитата:
3) Дамажащие площадные заклы при касте на своих не отражаються на вражеские отряды, а отражаються обратно на свои.

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

Цитата:
4) Для Армагеддона нужно анимацию резиста и Зеркала сделать уже после анимации самого Армагеддона. Аналогично боевым площадным заклам.

Решил, что пусть так будет для всех заклинаний.

Цитата:
Death Ripple - что-то не то со звуком. Звуковое сопровождение от другого закла. В оригинале у него звук другой.

Пишет, что проигрывает заклинание Death Ripple и звук Deathrip.wav :smile22:

Цитата:
Общая СЕРЬЕЗНАЯ проблема Death Ripple и Destroy Undead

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

Цитата:
2) Берсерк отражается не эффективно, все из-за отсутствия проверки на иммунитет существ к площадным заклам.

Я добавил функцию isImmune() для определения иммунитета отрядов и переписал две Ваши, которые делали то же самое. Её можно использовать и для Берсерка в функции getEnemyStackToRedirectTo().

Заклинания нейтралов пока не смотрел. Для Чародеев, проверил, отражает.

UPD: Добавил в функцию magicMirrorLucky() проверку на иммунитет. Смысл такой: например, на Золотых драконов можно повесить Magic Mirror, но не Slow, поэтому отряд не должен отражать также и массовую версию Slow. Более яркий пример: кастанули Destroy Undead, копейщики не должны его отражать.

* * *
Rolex, а почему у Вас площадные заклинания на свои отряды отражаются? Функция поиска гекса - это Ваш участок кода.

* * *
Всё-таки вернул для массовых залинаний прежний порядок анимаций. Для площадных ударных сделал рандомное отражение пока. Где-то ошибка в Вашем коде, либо я напортачил при переносе.

 Актуальный код
Код: Выделить всё
#define _CRT_SECURE_NO_WARNINGS
#include "stdafx.h"
#include "..\..\HotA\homm3.h"
#include <utility>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;

Patcher* _P;
PatcherInstance* _PI;

bool showMagicMirrorAnim, showMagicResistAnim, showSpellAnim, redirectAreaSpell;
bool needMagicMirrorAnim[2][20], needMagicResistAnim[2][20], needSpellAnim[2][20];
vector <pair<int, int>> combatPowerOfStacks;
vector <pair<int, int>> combatPowerOfStacksIsSpell;
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 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;
}

_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 isImmune(const _BattleStack_* stack, const int spell)
{
    return CALL_7(float, __thiscall, 0x5A83A0, o_BattleMgr, spell, o_BattleMgr->current_side, stack, 1, 1, 0) == 0;
}

bool approvedTarget(_BattleStack_* stack, int spell)
{
    return !(isImmune(stack, spell) || spell == SPL_FORGETFULNESS && stack->creature.shots == 0);
}

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

bool magicMirrorLucky(const _BattleStack_* stack, const int spell)
{
    int effSchoolLevel = stack->active_spells_power[SPL_MAGIC_MIRROR];

    return !isImmune(stack, spell) && !(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(_BattleStack_* stack, int spell)
{
    int sideFoe = 1 - stack->side;
    _BattleStack_* stackFoe = combatPowerOfStacks[stackIndex].second < o_BattleMgr->stacks_count[sideFoe] &&
        !o_BattleMgr->stack[sideFoe][combatPowerOfStacks[stackIndex].second].is_killed ?
        &o_BattleMgr->stack[sideFoe][combatPowerOfStacks[stackIndex].second] : 0;
   
    ++stackIndex;
    return stackFoe;
}

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;
    int count = 0;
    int minDuration = INT_MAX;

    int spell = *(int*)(c->ebp + 8);

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

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

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

    for (int i = 0; i < count; ++i)
    {
        _BattleStack_* stack = &o_BattleMgr->stack[o_BattleMgr->current_side][combatPowerOfStacks[i].second];

        if ( stack->active_spell_duration[spell] == 0 )
        {
            pos = combatPowerOfStacks[i].second;
            break;
        }
        else if ( stack->active_spell_duration[spell] < minDuration )
        {
            minDuration = stack->active_spell_duration[spell];
            pos = combatPowerOfStacks[i].second;
        }
    }

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

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

void playResistAndMagicMirror()
{
    // Проигрываем массовую анимацию и звук резиста
    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 massReflection(LoHook* h, HookContext* c)
{
    bool hitFlag = false;
    _ptr_ addr = h->GetAddress();

    if ( addr == hAddr[A_MASSREFLECTION_HOOK][E_ARMAGEDDON] )
    {
        memcpy(AnimateStack, needSpellAnim, 40);
        return EXEC_DEFAULT;
    }
   
    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];
    }

    playResistAndMagicMirror();

    // Проигрываем массовую анимацию и звук кастуемого заклинания
    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, hitFlag);
    }

    return NO_EXEC_DEFAULT;
}

/*int getHexId(const int spell)
{
    int randomStack = Randint(0, o_BattleMgr->stacks_count[o_BattleMgr->current_side] - 1);

    return o_BattleMgr->stack[o_BattleMgr->current_side][randomStack].hex_ix;
}*/

int getHexId(int spell)
{
   int maxCount = 0;
   int maxValue = 0;
   int maxHex = -1;

   bool flagCurSide = false;
   bool flagEnemySide = false;

   int SumHex;

   int dev;
   _BattleStack_* hex[19];

   vector <pair<int, int>>  countCurSide;
   vector <pair<int, int>>  countEnemySide;

   countCurSide.resize(187);
   countEnemySide.resize(187);

   if (spell == SPL_FROST_RING) SumHex = 6; else
      if (spell == SPL_METEOR_SHOWER || spell == SPL_FIREBALL) SumHex = 7; else
         if (spell == SPL_INFERNO) SumHex = 19;

   for (int i = 0; i < 187; ++i)
   {
      flagCurSide = false;
      flagEnemySide = false;

      if (((i / 17) + 1) % 2 == 0) dev = 0; else dev = 1;

      int id[] = { dev - 17, dev - 18, dev + 16, dev + 17, -1, 1, 0, -2, 2,
                dev - 16, dev - 19, dev + 15, dev + 18, -33, -34, -35, 33, 34, 35 };

      for (int j = 0; j < SumHex; ++j)
            hex[j] = o_BattleMgr->hex[i + id[j]].GetCreature();

      for (int j = 0; j < SumHex; ++j)
      {
         if (i + id[j] < 0 || i + id[j] > 186) continue;

         if (hex[j] != 0)
         {
            if (hex[j]->creature_id >= 0 && hex[j]->creature_id <= 149 && !isImmune(hex[j], spell))
            {
               if (hex[j]->side == o_BattleMgr->current_side)
               {
                  if (hex[j]->creature.flags & BCF_2HEX_WIDE)
                  {
                     if (!flagCurSide)
                     {                       
                        countCurSide[i].first++;
                        countCurSide[i].second += o_pCreatureInfo[hex[j]->creature_id].fight_value * hex[j]->count_current;
                        flagCurSide = true;
                     }
                     else flagCurSide = false;
                  }
                  else
                  {
                     countCurSide[i].first++;
                     countCurSide[i].second += o_pCreatureInfo[hex[j]->creature_id].fight_value * hex[j]->count_current;
                  }                     
               }
               else
               {
                  if (hex[j]->creature.flags & BCF_2HEX_WIDE)
                  {
                     if (!flagEnemySide)
                     {
                        countEnemySide[i].first++;
                        countEnemySide[i].second += o_pCreatureInfo[hex[j]->creature_id].fight_value * hex[j]->count_current;
                        flagEnemySide = true;
                     }
                     else flagEnemySide = false;
                  }
                  else
                  {
                     countEnemySide[i].first++;
                     countEnemySide[i].second += o_pCreatureInfo[hex[j]->creature_id].fight_value * hex[j]->count_current;
                  }                     
               }
            }
         }
      }
   }
   
   for (int i = 0; i < 187; ++i)
      if (countCurSide[i].first - countEnemySide[i].first > maxCount)
         maxCount = countCurSide[i].first - countEnemySide[i].first;

   for (int i = 0; i < 187; ++i)
      if (countCurSide[i].first - countEnemySide[i].first == maxCount)
      {
         if (countCurSide[i].second - countEnemySide[i].second > maxValue)
         {
            maxValue = countCurSide[i].second - countEnemySide[i].second;
            maxHex = i;
         }
      }

   return maxHex;
}

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);
    playResistAndMagicMirror();

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

        CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, getHexId(spell), spell, a3, a4);
        playResistAndMagicMirror();
    }
}

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

int __stdcall 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);

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

        int count = 0;
        int countIsSpell = 0;

        for (unsigned i = 0; i < combatPowerOfStacks.size(); ++i)
        {
            _BattleStack_* stack = &o_BattleMgr->stack[o_BattleMgr->current_side][i];

            if ( approvedTarget(stack, spell) )
            {
                if ( stack->active_spell_duration[spell] == 0 )
                {
                    combatPowerOfStacks[count].first = o_pCreatureInfo[stack->creature_id].fight_value * stack->count_current;
                    combatPowerOfStacks[count].second = i;
                    ++count;
                }
                else
                {
                    combatPowerOfStacksIsSpell[countIsSpell].first = o_pCreatureInfo[stack->creature_id].fight_value * stack->count_current;
                    combatPowerOfStacksIsSpell[countIsSpell].second = i;
                    ++countIsSpell;
                }
            }
        }
   
        combatPowerOfStacks.resize(count);
        combatPowerOfStacksIsSpell.resize(countIsSpell);

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

        for (int i = 0; i < countIsSpell; ++i)
        {
            _BattleStack_* stack = &o_BattleMgr->stack[o_BattleMgr->current_side][combatPowerOfStacksIsSpell[i].second];
            combatPowerOfStacksIsSpell[i].first = stack->active_spell_duration[spell];
        }

        sort(combatPowerOfStacksIsSpell.begin(), combatPowerOfStacksIsSpell.end(), my_cmp);
        combatPowerOfStacks.resize(count + countIsSpell);

        for (int i = count; i < count + countIsSpell; ++i)
            combatPowerOfStacks[i] = combatPowerOfStacksIsSpell[count + countIsSpell - i - 1];

        stackIndex = 0;

        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 __stdcall reflectSpell(LoHook* h, HookContext* c)
{
    _Hero_* hero = 0;
    _BattleStack_* 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 = (_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];
    }

    // Получаем оригинальный шанс срабатывания заклинания
    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 ( magicMirrorLucky(stack, spell) )
        {
            showMagicMirrorAnim = true;
            needMagicMirrorAnim[stack->side][stack->index_on_side] = true;

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

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

                if ( Randint(1, 100) <= 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(1, 100) <= 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;

                if ( isSpellAoE || isSpellSpec ) return NO_EXEC_DEFAULT;
            }
        }
    }
   
    return NO_EXEC_DEFAULT;
}

int __stdcall afterArmageddon(LoHook* h, HookContext* c)
{
    playResistAndMagicMirror();
   
    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, 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);

            // Проигрываем анимацию резиста и Magic Mirror для Armageddon
            _PI->WriteLoHook(0x5A5560, afterArmageddon);
        }
    }

    return TRUE;
}

* * *
Кажись, починил :smile4: Что-то пошло не так при переносе. Есть ещё вопрос по Frost Ring. Дырку от бублика учитываете? Ага, вижу, порядком обхода гексов :smile1:

Для тех, кто не хочет собирать, но хочет присоединиться к тестированию:

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

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

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

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

AlexSpl писал(а):

Rolex, а почему у Вас площадные заклинания на свои отряды отражаются? Функция поиска гекса - это Ваш участок кода.

Такое могло быть и на это может быть 3 причины:
1) У Вас была безвыходная ситуация и ответить так, чтобы не задеть свой отряд было невозможно.
2) Ответ с атакой своего слабого отряда был выгоден в той ситуации, чтобы задеть дополнительно вражеский более сильный.
3) Вы столкнулись с багом двухклеточных сеществ над фиксом которого я работал последнее время.

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

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

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

Универсальное решение подобной задачи оказалось непростым и куда сложнее чем я ожидал. Сравнить позиции двух двухклеточных существ не прошло бы. Дело в том, что мы не знаем какая-часть существа у нас. И если допустим 2 двухклеточных одинаковых существа с одинаковых кол-вом стоят рядом друг с другом, то, например, тот же Метеоритный дождь своими двумя верхними гексами может цеплять вторую часть первого двухклеточго существа и первую часть второго. А мы принимает это за одно двухклеточное существо. Проверять соседние гексы не вариант, так как за уже имеющимися могут стоять в плотную такие же.

Вот, например, в этих ситуациях первая версия getHexId выбирала не оптимальный гекс для атаки (оптимальный выделен).
 screenshots1
Изображение
Изображение


А вот безвыходная ситуация, где отряд сбоку будет задет Инферно в любом случае.
 screenshots2
Изображение
Изображение


Решение придумал следующее:
Для горизонтали на которой находится проверямый гекс эпицентр, а также для двух горизонталей выше и для двух ниже я генерирую массив типа vector <pair<int, _BattleStack_*>>, где в first у нас хранится число, которое для каждого монстра на проверяемой горизонтале будет свое. Проверяються только двухклеточные существа, соответственно у каждого существа будет свой уникальный ID, но у каждого из них их будет два, под каждую клетку. Даже если существа имеют одинаковый CID, но это разные существа, которые находятся в разных позициях, то у каждого будет свой идентификатор.

В second же мы складываем указатели на каждого монстра, чтобы потом без проблем получить CID, count и fight_value, так как мы храним и сравниваем еще и суммарную боевую мощь отрядов, которые попадают в область атаки.

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

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

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

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

Так вот первая версия getHexId отдавала предпочтение второму варинту (после уже сравнивалась суммарная сила отрядов аналогичных вариантов). А вот в этой версии, если боевая мощь Титаны + Архангели - Копейщики с учетом их кол-ва в каждом отряде будет больше суммарной мощи отрядов Кентавров и Гномов, то будет выбран первый вариант с атакой нашего отряда, и только в ином случае второй вариант.

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

Исправленная и протестированная функция getHexId:

Код: Выделить всё
int getHexId(int spell)
{
   int maxCount = 0;
   int maxValue = 0;
   int maxHex = -1;

   int SumHex;

   int dev;
   _BattleStack_* hex[19];

   vector <pair<int, int>> countCurSide;
   vector <pair<int, int>> countEnemySide;

   countCurSide.resize(187);
   countEnemySide.resize(187);

   vector <pair<int, _BattleStack_*>> wideHexCur;
   vector <pair<int, _BattleStack_*>> wideHexEnemy;

   wideHexCur.resize(187);
   wideHexEnemy.resize(187);

   int koefCur[187] = { 0 };
   int koefEnemy[187] = { 0 };

   int test[187] = { 0 };

   if  ( spell == SPL_FROST_RING ) SumHex = 6; else
      if ( spell == SPL_METEOR_SHOWER || spell == SPL_FIREBALL ) SumHex = 7; else
         if ( spell == SPL_INFERNO ) SumHex = 19;

   for (int i = 0; i < 187; ++i)
   {
      if (i % 17 == 0 || (i + 1) % 17 == 0) continue;

      memset(koefCur, 0, sizeof(koefCur));
      memset(koefEnemy, 0, sizeof(koefEnemy));

      wideHexCur.clear();
      wideHexEnemy.clear();

      wideHexCur.resize(187);
      wideHexEnemy.resize(187);

      if ( ((i / 17) + 1) % 2 == 0 ) dev = 0; else dev = 1;

      int id[] = { dev - 17, dev - 18, dev + 16, dev + 17, -1, 1, 0, -2, 2,
         dev - 16, dev - 19, dev + 15, dev + 18, -33, -34, -35, 33, 34, 35 };

      for (int j = 0; j < SumHex; ++j)
         hex[j] = o_BattleMgr->hex[i + id[j]].GetCreature();

      for (int j = 0; j < SumHex; ++j)
      {
         if ( i + id[j] < 0 || i + id[j] > 186 || (i + id[j]) % 17 == 0 || (i + id[j] + 1) % 17 == 0 ) continue;

         if ( hex[j] != 0 )
         {
            if ( hex[j]->creature_id >= 0 && hex[j]->creature_id <= 149 && !isImmune(hex[j], spell) )
            {
               if ( hex[j]->side == o_BattleMgr->current_side )
               {
                  if ( !(hex[j]->creature.flags & BCF_2HEX_WIDE) )
                  {
                     ++countCurSide[i].first;
                     countCurSide[i].second += o_pCreatureInfo[hex[j]->creature_id].fight_value * hex[j]->count_current;
                  }
               }
               else
               {
                  if ( !(hex[j]->creature.flags & BCF_2HEX_WIDE) )
                  {
                     ++countEnemySide[i].first;
                     countEnemySide[i].second += o_pCreatureInfo[hex[j]->creature_id].fight_value * hex[j]->count_current;
                  }
               }
            }
         }
      }

      int min = (i / 17) * 17 - 34;
      if (min < 0) min = 0;
      int max = (i / 17 + 1) * 17 + 33;
      if (max > 186) max = 186;

      int dist = min;

      int cur = 0;
      int enemy = 0;

      while ( dist <= max )
      {
         _BattleStack_* stack = o_BattleMgr->hex[dist].GetCreature();
         if ( dist % 17 != 0 && (dist + 1) % 17 != 0 && stack != 0 && stack->creature_id >= 0 && stack->creature_id <= 149 && !isImmune(stack, spell) && (stack->creature.flags & BCF_2HEX_WIDE) )
         {
            if ( stack->side == o_BattleMgr->current_side )
            {
               ++cur;
               wideHexCur[dist].first = cur;
               wideHexCur[dist].second = stack;

               if ( stack->creature_id != CID_CATAPULT && stack->creature_id != CID_BALLISTA && stack->creature_id != CID_FIRST_AID_TENT )
               {
                  wideHexCur[dist + 1].first = cur;
                  wideHexCur[dist + 1].second = stack;
                  ++dist;
               }
            }
            else
            {
               ++enemy;
               wideHexEnemy[dist].first = enemy;
               wideHexEnemy[dist].second = stack;

               if ( stack->creature_id != CID_CATAPULT && stack->creature_id != CID_BALLISTA && stack->creature_id != CID_FIRST_AID_TENT )
               {
                  wideHexEnemy[dist + 1].first = enemy;
                  wideHexEnemy[dist + 1].second = stack;
                  ++dist;
               }
            }
         }
         ++dist;
      }

      int countCurHexWide = 0;
      int sumCurFightValueHexWide = 0;

      for (int j = 0; j < SumHex; ++j)
      {
         if ( i + id[j] >= 0 && i + id[j] <= 186 && (i + id[j]) % 17 != 0 && (i + id[j] + 1) % 17 != 0 && wideHexCur[i + id[j]].first != 0 )
         {
            if ( koefCur[wideHexCur[i + id[j]].first] == 0 )
            {
               koefCur[wideHexCur[i + id[j]].first] = 1;
               ++countCurHexWide;
               sumCurFightValueHexWide += o_pCreatureInfo[wideHexCur[i + id[j]].second->creature_id].fight_value * wideHexCur[i + id[j]].second->count_current;
            }
         }
      }

      int countEnemyHexWide = 0;
      int sumEnemyFightValueHexWide = 0;

      for (int j = 0; j < SumHex; ++j)
      {
         if ( i + id[j] >= 0 && i + id[j] <= 186 && (i + id[j]) % 17 != 0 && (i + id[j] + 1) % 17 != 0 && wideHexEnemy[i + id[j]].first != 0 )
         {
            if ( koefEnemy[wideHexEnemy[i + id[j]].first] == 0 )
            {
               koefEnemy[wideHexEnemy[i + id[j]].first] = 1;
               ++countEnemyHexWide;
               sumEnemyFightValueHexWide += o_pCreatureInfo[wideHexEnemy[i + id[j]].second->creature_id].fight_value * wideHexEnemy[i + id[j]].second->count_current;
            }
         }
      }

      countCurSide[i].first += countCurHexWide;
      countCurSide[i].second += sumCurFightValueHexWide;

      countEnemySide[i].first += countEnemyHexWide;
      countEnemySide[i].second += sumEnemyFightValueHexWide;
   }

   for (int i = 0; i < 187; ++i)
      if (countCurSide[i].first > maxCount)
         maxCount = countCurSide[i].first;

   for (int i = 0; i < 187; ++i)
      if ( countCurSide[i].first == maxCount )
      {
         if ( countCurSide[i].second - countEnemySide[i].second > maxValue )
         {
            maxValue = countCurSide[i].second - countEnemySide[i].second;
            maxHex = i;
         }
      }

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

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

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

Код: Выделить всё
_BattleStack_* stack = o_BattleMgr->hex[i].GetCreature();
if ( o_BattleMgr->hex[i + stack->side == 0 ? 1 : -1].GetCreature() == stack ) ++i;

Здесь i - индекс гекса в массиве гексов, входящих в область заклинания.

Т.е. в цикле мы инкрементируем i, а если заметили, что на гексе справа тот же стек, дополнительно икрементируем i. Тут даже [i + stack->side == 0 ? 1 : -1] не нужно (мы же слева направо идём). Просто [i + 1].
Последний раз редактировалось AlexSpl 30 мар 2021, 18:32, всего редактировалось 1 раз.
Вернуться к началу

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

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

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

AlexSpl писал(а):

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

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

AlexSpl писал(а):

1) Звук FIREBALL (Огненный шар) проигрывается уже после проигрывания самой анимации. А должен одновременно с анимацией проигрываться, как у других заклов.

А это правили?

AlexSpl писал(а):

Пишет, что проигрывает заклинание Death Ripple и звук Deathrip.wav

Отключите все плагины и проверьте звук в оригинале. Там другой более резкий звук. Может их два. Или это баг оригинала. Но я как-то к тому привык. Этот вообще незаметен. Я проверю что за звук проигрывается и отпишусь.

Rolex писал(а):

5) Заклинания нейтралов (отрядов без героя) не отражает на самих же нейтралов. Надо бы сделать, чтобы отражало.

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

Пред.След.

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

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

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