Объявления

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

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

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

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

Сообщение Rolex » 01 апр 2021, 11:26

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

Атака своего теперь может быть только в том случае, когда это действительно выгодно для отразившего. То есть атака большего кол-ва отрядов вместе со своим будет более выгодной чем атака меньшего кол-ва, но без своего.

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

Код: Выделить всё
   
for (int i = 0; i < 187; ++i)
   if (countCurSide[i].first > maxCount)
      maxCount = countCurSide[i].first;

   while (maxHex == -1)
   {
      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;
            }
         }
      if (maxHex == -1 && maxCount > 1) --maxCount; else break;
   }
   
   return maxHex;


Также добавил хук для проверки языка и структуру с английским описанием. Чтобы игроки, которые предпочитают играть в оригинале с англ переводом видели англ описание Зеркала, а те, кто с русским - русское. В русской версии Героев и в англ с подключенным плагином руссификации будет русское описание, а в английской версии и в русской с подключенным плагином англификации - английское.
Если Вы знаете более правильное определние языка игры, тогда делитесь.

Код: Выделить всё
bool isRusLng;
...
int __stdcall isRusLanguage(LoHook* h, HookContext* c)
{
   isRusLng = o_CreatureInfo[0].name_single[0] != 'P';
   return EXEC_DEFAULT;
}
...
// проверяем язык после загрузки всех txt-файлов игры
_PI->WriteLoHook(0x4EDFFD, isRusLanguage);


 Последняя версия Вашего кода из поста выше с двумя дополнениями из этого поста:
Код: Выделить всё
#define _CRT_SECURE_NO_WARNINGS
#include "stdafx.h"
#include "..\..\HotA\homm3.h"
#include <vector>
#include <algorithm>

using namespace std;

Patcher* _P;
PatcherInstance* _PI;

struct _BattleStackEx_ : public _BattleStack_
{
   bool isImmuneTo(const int spell);
   bool approvedTarget(const int spell);
   bool magicMirrorLucky(const int spell);
   _BattleStackEx_* getEnemyStackToRedirectTo(const int spell);
};

bool isRusLng;
bool showMagicMirrorAnim, showMagicResistAnim, showSpellAnim, redirectAreaSpell;
bool needMagicMirrorAnim[2][20], needMagicResistAnim[2][20], needSpellAnim[2][20];
vector<_BattleStackEx_*> stackVector;
int stackIndex;

#define SPL_DEATH_RIPPLE 24
#define CID_AZURE_DRAGON 132

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

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

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

int __stdcall isRusLanguage(LoHook* h, HookContext* c)
{
   isRusLng = o_CreatureInfo[0].name_single[0] != 'P';
   return EXEC_DEFAULT;
}

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

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

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

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

   if (isRusLng) *(SpellDesc*)o_Spell[SPL_MAGIC_MIRROR].description = NewMagicMirrorRus; else
              *(SpellDesc*)o_Spell[SPL_MAGIC_MIRROR].description = NewMagicMirrorEng;

   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 _BattleStackEx_::isImmuneTo(const int spell)
{
   return CALL_7(float, __thiscall, 0x5A83A0, o_BattleMgr, spell, o_BattleMgr->current_side, this, 1, 1, 0) == 0;
}

bool _BattleStackEx_::approvedTarget(const int spell)
{
   return !this->is_killed && !this->isImmuneTo(spell) || spell == SPL_FORGETFULNESS && this->creature.shots == 0;
}

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

   return !this->isImmuneTo(spell) && !(o_Spell[spell].flags & SPF_FRIENDLY_HAS_MASS) && (this->side != o_BattleMgr->current_side ||
      spell == SPL_FROST_RING || spell == SPL_FIREBALL || spell == SPL_INFERNO || spell == SPL_METEOR_SHOWER ||
      spell == SPL_BERSERK || spell == SPL_DEATH_RIPPLE || spell == SPL_DESTROY_UNDEAD || spell == SPL_ARMAGEDDON) &&
      this->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(1, 100) <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel];
}

_BattleStackEx_* _BattleStackEx_::getEnemyStackToRedirectTo(const int spell)
{
   int sideFoe = 1 - this->side;
   _BattleStackEx_* stackFoe = stackIndex < (int)stackVector.size() ? (_BattleStackEx_*)&o_BattleMgr->stack[sideFoe][stackVector[stackIndex]->index_on_side] : 0;

   ++stackIndex;
   return stackFoe;
}

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 cmpFightValue(_BattleStackEx_* a, _BattleStackEx_* b)
{
   return a->creature.fight_value * a->count_current > b->creature.fight_value * b->count_current;
}

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

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

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

int __stdcall newTarget(LoHook* h, HookContext* c)
{
   c->eax = stackIndex < (int)stackVector.size() ?
      (int)&o_BattleMgr->stack[o_BattleMgr->current_side][stackVector[stackIndex]->index_on_side] :
      (int)&o_BattleMgr->stack[o_BattleMgr->current_side][0];

   return EXEC_DEFAULT;
}

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

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

   return EXEC_DEFAULT;
}

int __stdcall dataInit(LoHook* h, HookContext* c)
{
   int spell = *(int*)(c->ebp + 8);

   showMagicMirrorAnim = false;
   showMagicResistAnim = false;
   showSpellAnim = false;
   redirectAreaSpell = false;
   memset(needMagicMirrorAnim, false, 40);
   memset(needMagicResistAnim, false, 40);
   memset(needSpellAnim, false, 40);

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

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

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

   stackIndex = 0;

   return EXEC_DEFAULT;
}

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

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

      // Если герой - AI, на отряде висит Magic Mirror и оно сработало, а также оригинальные проверки на Teleport и Sacrifice
      if (!CALL_2(bool, __thiscall, 0x4CE600, o_GameMgr, o_BattleMgr->owner_id[o_BattleMgr->current_side]) &&
         stack->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(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;

   int SumHex;

   int dev;
   _BattleStackEx_* hex[19];

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

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

   vector <pair<int, _BattleStackEx_*>> wideHexCur;
   vector <pair<int, _BattleStackEx_*>> 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] = (_BattleStackEx_*)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 && !hex[j]->isImmuneTo(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)
      {
         _BattleStackEx_* stack = (_BattleStackEx_*)o_BattleMgr->hex[dist].GetCreature();
         if (dist % 17 != 0 && (dist + 1) % 17 != 0 && stack != 0 && stack->creature_id >= 0 && stack->creature_id <= 149 && !stack->isImmuneTo(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)
               {
                  ++dist;
                  wideHexCur[dist].first = cur;
                  wideHexCur[dist].second = stack;
               }
            }
            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)
               {
                  ++dist;
                  wideHexEnemy[dist].first = enemy;
                  wideHexEnemy[dist].second = stack;
               }
            }
         }
         ++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;

   while (maxHex == -1)
   {
      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;
            }
         }
      if (maxHex == -1 && maxCount > 1) --maxCount; else break;
   }

   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_3(void, __fastcall, 0x59A890, o_Spell[spell].wav_name, -1, 3);

      if (getHexId(spell) != -1)
      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 reflectSpell(LoHook* h, HookContext* c)
{
   _Hero_* hero = 0;
   _BattleStackEx_* stack = 0;
   int spell, spellPower, schoolLevel;
   int casterSide = o_BattleMgr->current_side;

   _ptr_ addr = h->GetAddress();

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

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

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

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

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

   if (addr == hAddr[A_REFLECTSPELL_HOOK][E_ARMAGEDDON])
   {
      stack = (_BattleStackEx_*)c->esi;
      spell = SPL_ARMAGEDDON;
      c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_ARMAGEDDON];
   }

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

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

      if (stack->magicMirrorLucky(spell))
      {
         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, перенаправляем заклинание на вражеский отряд
         _BattleStackEx_* stackFoe = stack->getEnemyStackToRedirectTo(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");

         // проверяем язык после загрузки всех txt-файлов игры
         _PI->WriteLoHook(0x4EDFFD, isRusLanguage);

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

         // Инициализируем данные
         _PI->WriteLoHook(0x5A058B, dataInit);

         // Синглкаст
         _PI->WriteLoHook(0x5A05CC, newTarget);
         _PI->WriteLoHook(0x5A058B, magicMirrorAI);

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

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

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

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

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

Пока Вы писали новый алгоритм поиска цели, я пытался улучшить Ваш старый. В нём я ищу гекс с максимальной разницой между Fight Value вражеских отрядов и Fight Value наших, попадающих в зону поражения. Функция является примером обхода поля и поиска отрядов, включая двугексовые, в зоне действия заклинания. Можете добавлять в неё свои условия. Также поправил условие в approvedTarget() и сделал перенаправление синглкаста на первый живой вражеский отряд в newTarget(), если вектор пуст (в оригинале синглкаст тоже отражается на отряд, даже иммунный).

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

using namespace std;

Patcher* _P;
PatcherInstance* _PI;

struct _BattleStackEx_ : public _BattleStack_
{
    bool isImmuneTo(const int spell);
    bool approvedTarget(const int spell);
    bool magicMirrorLucky(const int spell);
    int fightValue();
    _BattleStackEx_* getEnemyStackToRedirectTo(const int spell);
};

bool showMagicMirrorAnim, showMagicResistAnim, showSpellAnim, redirectAreaSpell;
bool needMagicMirrorAnim[2][20], needMagicResistAnim[2][20], needSpellAnim[2][20];
vector<_BattleStackEx_*> stackVector;
int stackIndex;

#define SPL_DEATH_RIPPLE 24
#define CID_AZURE_DRAGON 132

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

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

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

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

SpellDesc 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 _BattleStackEx_::isImmuneTo(const int spell)
{
    return CALL_7(float, __thiscall, 0x5A83A0, o_BattleMgr, spell, o_BattleMgr->current_side, this, 1, 1, 0) == 0;
}

bool _BattleStackEx_::approvedTarget(const int spell)
{
    return !this->is_killed && !this->isImmuneTo(spell) && !(spell == SPL_FORGETFULNESS && this->creature.shots == 0);
}

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

    return !this->isImmuneTo(spell) && !(o_Spell[spell].flags & SPF_FRIENDLY_HAS_MASS) && (this->side != o_BattleMgr->current_side ||
        spell == SPL_FROST_RING || spell == SPL_FIREBALL || spell == SPL_INFERNO || spell == SPL_METEOR_SHOWER ||
        spell == SPL_BERSERK || spell == SPL_DEATH_RIPPLE || spell == SPL_DESTROY_UNDEAD || spell == SPL_ARMAGEDDON) &&
        this->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(1, 100) <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel];
}

int _BattleStackEx_::fightValue()
{
    return this->creature.fight_value * this->count_current;
}

_BattleStackEx_* _BattleStackEx_::getEnemyStackToRedirectTo(const int spell)
{
    int sideFoe = 1 - this->side;
    _BattleStackEx_* stackFoe = stackIndex < (int)stackVector.size() ?
        (_BattleStackEx_*)&o_BattleMgr->stack[sideFoe][stackVector[stackIndex]->index_on_side] : 0;
   
    ++stackIndex;
    return stackFoe;
}

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 cmpFightValue(_BattleStackEx_* a, _BattleStackEx_* b)
{
    return a->creature.fight_value * a->count_current > b->creature.fight_value * b->count_current;
}

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

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

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

int __stdcall newTarget(LoHook* h, HookContext* c)
{
    if ( stackIndex < (int)stackVector.size() )
        c->eax = (int)&o_BattleMgr->stack[o_BattleMgr->current_side][stackVector[stackIndex]->index_on_side];
    else {
        int i = 0;
        while ( !o_BattleMgr->stack[o_BattleMgr->current_side][i].is_killed ) ++i;
        c->eax = (int)&o_BattleMgr->stack[o_BattleMgr->current_side][i];
    }
   
    return EXEC_DEFAULT;
}

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

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

    return EXEC_DEFAULT;
}

int __stdcall dataInit(LoHook* h, HookContext* c)
{
    int spell = *(int*)(c->ebp + 8);

    showMagicMirrorAnim = false;
    showMagicResistAnim = false;
    showSpellAnim = false;
    redirectAreaSpell = false;
    memset(needMagicMirrorAnim, false, 40);
    memset(needMagicResistAnim, false, 40);
    memset(needSpellAnim, false, 40);

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

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

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

    stackIndex = 0;

    return EXEC_DEFAULT;
}

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

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

        // Если герой - AI, на отряде висит Magic Mirror и оно сработало, а также оригинальные проверки на Teleport и Sacrifice
        if ( !CALL_2(bool, __thiscall, 0x4CE600, o_GameMgr, o_BattleMgr->owner_id[o_BattleMgr->current_side]) &&
             stack->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(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;
}

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

int getSpellTargetHex(const int spell)
{
    const int xSize = 17;
               
    int maxFightValueDelta = 0;
    int bestTargetHex = -1;
    for (int hex = 1; ; ++hex)
    {
        if ( hex % xSize == 16 ) hex += 2;
        if ( hex > 185 ) break;
     
        int y = hex / xSize;
        int d = y % 2;

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

        if ( spell == SPL_INFERNO ) {
            area = areaR2;
            areaSize = sizeof(areaR2) / sizeof(int);
        }
       
        int countUs = 0, countThem = 0, fightValueUs = 0, fightValueThem = 0;
        for (int i = 0; i < areaSize; ++i)
        {
            int iHex = hex + area[i];
            if ( isValidHex(iHex) )
            {
                _BattleStackEx_* stack = (_BattleStackEx_*)o_BattleMgr->hex[iHex].GetCreature();
                if ( stack )
                {
                    if ( !stack->is_killed && !stack->isImmuneTo(spell) )
                    {
                        if ( stack->side == o_BattleMgr->current_side ) {
                            ++countThem;
                            fightValueThem += stack->fightValue();
                        }
                        else {
                            ++countUs;
                            fightValueUs += stack->fightValue();
                        }
                    }
                                   
                    if ( i + 1 < areaSize )
                    {
                        int iHexNext = hex + area[i + 1];
                        if ( isValidHex(iHexNext) && (_BattleStackEx_*)o_BattleMgr->hex[iHexNext].GetCreature() == stack ) ++i;
                    }
                }
            }
        }

        if ( countUs + countThem > 0 && fightValueThem - fightValueUs > maxFightValueDelta )
        {
            maxFightValueDelta = fightValueThem - fightValueUs;
            bestTargetHex = hex;
        }
    }

    return bestTargetHex;
}

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

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

        CALL_3(void, __fastcall, 0x59A890, o_Spell[spell].wav_name, -1, 3);
        CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, getSpellTargetHex(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 reflectSpell(LoHook* h, HookContext* c)
{
    _Hero_* hero = 0;
    _BattleStackEx_* stack = 0;
    int spell, spellPower, schoolLevel;
    int casterSide = o_BattleMgr->current_side;

    _ptr_ addr = h->GetAddress();

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

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

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

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

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

    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_ARMAGEDDON] )
    {
        stack = (_BattleStackEx_*)c->esi;
        spell = SPL_ARMAGEDDON;
        c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_ARMAGEDDON];
    }

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

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

        if ( stack->magicMirrorLucky(spell) )
        {
            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, перенаправляем заклинание на вражеский отряд
            _BattleStackEx_* stackFoe = stack->getEnemyStackToRedirectTo(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, skipSpellSound);
           
            // Инициализируем данные
            _PI->WriteLoHook(0x5A058B, dataInit);

            // Синглкаст
            _PI->WriteLoHook(0x5A05CC, newTarget);
            _PI->WriteLoHook(0x5A058B, magicMirrorAI);

            // Масскаст
            for (int i = 0; i < E_SIZE; ++i)
            {
                _PI->WriteLoHook(hAddr[A_REFLECTSPELL_HOOK][i], reflectSpell);
                if ( i == E_AREA )
                    _PI->WriteHiHook(hAddr[A_MASSREFLECTION_HOOK][i], SPLICE_, EXTENDED_, THISCALL_, areaReflection);
                else
                    _PI->WriteLoHook(hAddr[A_MASSREFLECTION_HOOK][i], massReflection);
            }
           
            // Проигрываем звук Magic Mirror при отражении одиночного заклинания
            _PI->WriteLoHook(0x5A059A, playMagicMirrorSound);

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

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

    return TRUE;
}

Для "бублика" Frost Ring, однако, дырку нужно поправить :smile1:

* * *
Не пришлось. Алгоритм умнее оказался и работает для любых типов областей.

Код: Выделить всё
  [A] [B]
[C] [X] [D]
  [E] [F]


Пусть на СX расположен двугексовый отряд. Проверяем следующий гекс области (это будет гекс D, а не X!). Разумеется, на гексе D не может быть того же отряда, что и на C, поэтому в штатном режиме :smile1: переходим к гексу D.

* * *
Добавил также подсчёт отрядов в области действия заклинания. Нужен для того, чтобы при равенстве нулю разницы Fight Value, заклинание уходило в никуда, как при отрицательной разности.
Последний раз редактировалось AlexSpl 01 апр 2021, 13:39, всего редактировалось 3 раз(а).
Вернуться к началу

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

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

Сообщение AlexSpl » 01 апр 2021, 12:56

Отрисовка в оригинале тоже так глючила или это HD мод?

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

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

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

Сообщение Rolex » 01 апр 2021, 13:44

AlexSpl писал(а):

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

Новый (вторая версия), а вернее переделанный старый я опубликовал 30 марта (позавчера). Первую же версию getHexId я публиковал 26 марта. А сегодня только while добавил с одним условием, а код остался тот же последний от 30 марта.

AlexSpl писал(а):

Отрисовка в оригинале тоже так глючила или это HD мод?

По ходу мод. Заметил еще один баг мода, когда кастишь Инферно внизу, то след от закла остается на время на панели, пока не обновится очередь.
Вернуться к началу

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

Тоже подумал, что связано с очередью.

Цитата:
А сегодня только while добавил с одним условием, а код остался тот же последний от 30 марта.

Есть ли принципиальные отличия? Чем плох алгоритм, максимизирующий разность Fight Value?

* * *
Делать или нет отражение абилок существ? Есть такая магия или нет, а они всё-таки абилки. В Героях 1, например, есть абилка Ослепление и магия Ослепление, хотя выглядят идентично. Вот для существ-кастеров всё однозначно. Для них отражение и без дополнительных усилий работает. Можно, конечно, ради интереса отражать Ослепление Единорогов... Но, в принципе, плагин уже готов.
Вернуться к началу

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

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

Сообщение Rolex » 01 апр 2021, 13:57

Rolex писал(а):

Такс, проверил. В общем выставил оригинальному Зеркалу 100% вероятность отражения и проверил отражает ли оно абилки-заклы существ. Итого ничего не отражает ни абилки с обычными заклами, которые есть в книге, ни уж тем более уникальные абилки существ, которых нет в книге заклинаний.

А вот с Антимагией интересней. Антимагия блочит все абилки-заклы существ, которые есть в книге, причем даже Сказочный дракон не может атаковать заклом существо с Антимагией, только обычной атакой. И в то же время Антимагия пропускает уникальные заклы-абилки, которые присущи только лишь отдельным существам (Окаменение, Паралич, Болезнь, Старение, Яд).

Что касается резиста, то он работает только против заклов Чародея. Ибо у них уникальное направление каждые 3 раунда, которое не есть частью атаки. У всех остальных существ абилка-закл - это часть атаки и в таком случае резист не работает.

Таким образом прихожу к выводу, что надо делать как с Антимагией, уникальные заклы-абилки мы не отражаем (Окаменение, Паралич, Болезнь, Старение, Яд), а те что есть в книге (Слепота, Проклятие, Слабость) - отражаем. Единороги/Боевые единороги - Слепота, Черные рыцари/Рыцари Смерти/Мумии - Проклятие, Ядовитые змии - Слабость. А Снятие заклинаний Змиев и Ядовитых змиев получается отражать ненужно, оно если накладывается, то должно снимать Зеркало с отряда, который был атакован Змием. Диспел снимает все, а потому его не отражаем.

Заклы Чародея получается итак сейчас отражаються, так как он кастует массово как на свои, так и на вражеские отряды и это идет как направление закла, а не атака. И получается еще нужно отражать и Удар молнии Громовой птицы и Огненный щит Султана ифритов (то есть те заклы, которые есть в книге заклинаний). Хотя нет, щит Ифритов ненужно, его Антимагия не блочит, а вот Удар молнии Громовых птиц нужно, он блочится Антимагией. :smile5: И ... если следовать логике Антимагии, то по идее мы должны отражать и атаку Сказочного дракона заклом. :smile1:

UPD: Заклы Сказочного дракона, также как и Чародея получается Зеркало итак сейчас отражает. С ними ничего делать ненужно. То есть по сути нужно сделать отражение 4 заклов (Слепота, Проклятие, Слабость и Удар молнии).

Думаю, нужно делать все аналогично Антимагии.

AlexSpl писал(а):

Есть ли принципиальные отличия? Чем плох алгоритм, максимизирующий разность Fight Value?

Так он и остался. Я только его допилил. Прочтите первый пост на этой странице.
Вернуться к началу

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

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

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

AlexSpl писал(а):

Чем плох алгоритм, максимизирующий разность Fight Value?

На самом деле в ряде случаев плох. Я потестил вашу реализация отражения площадных. Отрабатывает, вроде, хорошо, также как и моя. Но в итоге Вы максимизируете только лишь разницу Fight Value, не учитывая при этом кол-во отрядов. Я тоже изначально думал так сделать. Но потом передумал, так как на самом деле это далеко не всегда хорошо. А потому в моем случае приоритет изначально отдается кол-ву, а потом уже разнице Fight Value в случае нескольких вариантов с одинаковым количеством. Но и этот вариант не есть оптимальным.

Например, есть 1 отряд на 5 Лазурных драконов и 6 отрядов по 5 Архангелов в каждом. Мы можем атаковать либо отряд Лазурных, либо 6 отрядов Архов. Так вот ваш алгоритм выберет один отряд Лазурных, так как его Fight Value больше 30 Архов. В то время как мой алгоритм выберет 6 отрядов Архов (по 5 в каждом).
Например, при уроне от Инферно в 500 ед. мы заберем только лишь половину здоровья Лазурного и целых 12 Архов. А при уроне в 1000 ед. 1 Лазурного и 24 Арха. Очевидно, что 24 Арха сильнее 1 Лазурного. Даже если это будет не 6, а 3 отряда Архангелов, это все равно будет выгодней, потому как даже 12 Архов сильнее 1 Лазурного.

Или такой еще пример. Есть 12 Архов одним отрядом и еще 6 отрядов по 2 Арха в каждом. При этом Инферно мы можем развернуть либо на один отряд из 12 Архов, либо на все 6 отрядов по 2 в каждом.
Их Fight Value будет одинаков. Как Вы думаете, какой вариант в этом случае выберет ваш алгоритм? Правильно, он выберет первый попавшийся ему вариант. И если это будет отряд из 12 Архов, то он и будет целью. Так вот при уроне от Инферно, например, в 500 ед. мы уничтожим лишь 2 Архов, в то время как при атаке 6 отрядов могло бы лечь целых 12 Архов.
А вывод из всего этого таков, что сравнение по Fight Value отрядов будет уместным только в случае сравнения одинакового кол-ва отрядов для атаки.

Мой же вариант с выбором максимального кол-ва отрядов для атаки также далек от оптимального. Например, есть цель из двух отрядов Архангелы и Титаны и 3 отряда Копейщики, Лучники (примерно в том же кол-ве, что Архов с Титанами) и Тележка боеприпасов. Так вот мой алгоритм выберет в данном случае Тележку с Копейщика и Лучниками, так как кол-во отрядов больше.

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

Мы можем выбрать вариант с максимальной разницей Fight Value, только в том случае, если он превосходит по Fight Value в процентном отношении вариант с большим кол-вом отрядов настолько, насколько уступает в процентном отношении в кол-ве отрядов.
Например, есть 3 отряда для атаки и 2 отряда. Выбрать 2 отряда мы может только тогда, когда его Fight Value сторого больше чем Fight Value 3 отрядов в 1,5 раза. Если же превосходство Fight Value 2 отрядов над 3-мя не более 50%, тогда приоритет отдаем 3 отрядам.
Думаю, идея понятна. И этот алгоритм будет весьма неплохо справлятся в ряде сложных ситуаций и будет однозначно лучше двух вышеописанных.

Но и у него есть недостатки. Дело в том, что на высокое Fight Value именно отряда влияет не только сила существ в отряде, а и их кол-во. Например, есть стек на 50 Архов и 2 стека по 10 Архов. В данном случае будет выбран идин стек на 50 Архов, так как 50 > 2 * (10 + 10).

А потому нам нужен алгоритм, который бы исходил именно от урона в кол-ве существ который будет нанесен каждому из этих отрядов. В случае сравнения разных по кол-ву отрядов и суммарному Fight Value вариантов предлагаю следующий алгоритм:

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

То есть сумма

Код: Выделить всё
min( (double)(урон_от_закла / полное_здоровье_одного_существа_в_отряде), кол-во_существ_в_отряде ) * Fight_Value_одного_существа_в_отряде

сумма этих значений по всем отрядам в проверямом варианте. После выбираем вариант с максимальной суммой.

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

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

Хотя, ... я не уверен, что нам нужно будет сравнимать только два варианта. Да, по ходу нам все же придется собирать в массив и все варианты, которые могут находится между первым и вторым, а в итоге при пересчете стать оптимальными. То есть такой вариант может уступать первому по кол-ву, но превосходить его по Fight Value, а второму уступать по Fight Value, но превосходить его по кол-ву.
Например, 4 отряда имеют Fight Value - 20, 3 отряда - 30, 2 отряда - 40. Пересчитав все варианты по вышеописанной формуле самым оптимальным вариантом вполне может оказаться и вариант 3 - 30. При равенстве, нам всегда выгодно выбирать вариант с самым большим кол-вом отрядов.

Если же у нас возможны только варианты с одним кол-вом (например, только 1 отряд), то здесь ничего пересчитывать и ненужно, все остается так, как есть, выбираем отряд с найбольшей разницей Fight Value.

Вот формулы вычиления урона каждого из наших 4 площадных заклов.

Кольцо холода и Огненный шар:
Нулевой/Базовый:
СМ х 10 + 15
Продвинутый:
СМ х 10 + 30
Экспертный:
СМ х 10 + 60

Инферно:
Нулевой/Базовый:
СМ х 10 + 20
Продвинутый:
СМ х 10 + 40
Экспертный:
СМ х 10 + 80

Метеоритный дождь:
Нулевой/Базовый:
СМ х 25 + 25
Продвинутый:
СМ х 25 + 50
Экспертный:
СМ х 25 + 100

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

--------------------------------------------------------------------------------------
И еще один момент. Мне не нравится как работает rand(). У него относительно короткий период. Нам нужно заменить его на ГСПЧ с использованием Вихря Мерсенна. Это высококачественный ГСПЧ, у которого довольно длинный период, который равен числу Мерсенна. Он есть в стандартной библиотеке C++ <random>.

Вот хорошая статья на тему генерации случайных чисел:
https://ravesli.com/urok-71-generatsiya ... nd-i-rand/

В общем в дополнение к нейтралам, нам нужно будет еще и эти два пункта проработать и только тогда плагин для меня будет готов.

 Ваша последняя версия кода с вашей getSpellTargetHex, новым хуком для проверки языка и структурой с английским описанием
Код: Выделить всё
#define _CRT_SECURE_NO_WARNINGS
#include "stdafx.h"
#include "..\..\HotA\homm3.h"
#include <vector>
#include <algorithm>

using namespace std;

Patcher* _P;
PatcherInstance* _PI;

struct _BattleStackEx_ : public _BattleStack_
{
   bool isImmuneTo(const int spell);
   bool approvedTarget(const int spell);
   bool magicMirrorLucky(const int spell);
   int fightValue();
   _BattleStackEx_* getEnemyStackToRedirectTo(const int spell);
};

bool isRusLng;
bool showMagicMirrorAnim, showMagicResistAnim, showSpellAnim, redirectAreaSpell;
bool needMagicMirrorAnim[2][20], needMagicResistAnim[2][20], needSpellAnim[2][20];
vector<_BattleStackEx_*> stackVector;
int stackIndex;

#define SPL_DEATH_RIPPLE 24
#define CID_AZURE_DRAGON 132

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

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

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

int __stdcall isRusLanguage(LoHook* h, HookContext* c)
{
   isRusLng = o_CreatureInfo[0].name_single[0] != 'P';
   return EXEC_DEFAULT;
}

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

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

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

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

   if (isRusLng) *(SpellDesc*)o_Spell[SPL_MAGIC_MIRROR].description = NewMagicMirrorRus; else
      *(SpellDesc*)o_Spell[SPL_MAGIC_MIRROR].description = NewMagicMirrorEng;

   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 _BattleStackEx_::isImmuneTo(const int spell)
{
   return CALL_7(float, __thiscall, 0x5A83A0, o_BattleMgr, spell, o_BattleMgr->current_side, this, 1, 1, 0) == 0;
}

bool _BattleStackEx_::approvedTarget(const int spell)
{
   return !this->is_killed && !this->isImmuneTo(spell) && !(spell == SPL_FORGETFULNESS && this->creature.shots == 0);
}

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

   return !this->isImmuneTo(spell) && !(o_Spell[spell].flags & SPF_FRIENDLY_HAS_MASS) && (this->side != o_BattleMgr->current_side ||
      spell == SPL_FROST_RING || spell == SPL_FIREBALL || spell == SPL_INFERNO || spell == SPL_METEOR_SHOWER ||
      spell == SPL_BERSERK || spell == SPL_DEATH_RIPPLE || spell == SPL_DESTROY_UNDEAD || spell == SPL_ARMAGEDDON) &&
      this->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(1, 100) <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel];
}

int _BattleStackEx_::fightValue()
{
   return this->creature.fight_value * this->count_current;
}

_BattleStackEx_* _BattleStackEx_::getEnemyStackToRedirectTo(const int spell)
{
   int sideFoe = 1 - this->side;
   _BattleStackEx_* stackFoe = stackIndex < (int)stackVector.size() ?
      (_BattleStackEx_*)&o_BattleMgr->stack[sideFoe][stackVector[stackIndex]->index_on_side] : 0;

   ++stackIndex;
   return stackFoe;
}

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 cmpFightValue(_BattleStackEx_* a, _BattleStackEx_* b)
{
   return a->creature.fight_value * a->count_current > b->creature.fight_value * b->count_current;
}

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

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

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

int __stdcall newTarget(LoHook* h, HookContext* c)
{
   if (stackIndex < (int)stackVector.size())
      c->eax = (int)&o_BattleMgr->stack[o_BattleMgr->current_side][stackVector[stackIndex]->index_on_side];
   else {
      int i = 0;
      while (!o_BattleMgr->stack[o_BattleMgr->current_side][i].is_killed) ++i;
      c->eax = (int)&o_BattleMgr->stack[o_BattleMgr->current_side][i];
   }

   return EXEC_DEFAULT;
}

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

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

   return EXEC_DEFAULT;
}

int __stdcall dataInit(LoHook* h, HookContext* c)
{
   int spell = *(int*)(c->ebp + 8);

   showMagicMirrorAnim = false;
   showMagicResistAnim = false;
   showSpellAnim = false;
   redirectAreaSpell = false;
   memset(needMagicMirrorAnim, false, 40);
   memset(needMagicResistAnim, false, 40);
   memset(needSpellAnim, false, 40);

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

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

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

   stackIndex = 0;

   return EXEC_DEFAULT;
}

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

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

      // Если герой - AI, на отряде висит Magic Mirror и оно сработало, а также оригинальные проверки на Teleport и Sacrifice
      if (!CALL_2(bool, __thiscall, 0x4CE600, o_GameMgr, o_BattleMgr->owner_id[o_BattleMgr->current_side]) &&
         stack->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(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;
}

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

int getSpellTargetHex(const int spell)
{
   const int xSize = 17;

   int maxFightValueDelta = 0;
   int bestTargetHex = -1;
   for (int hex = 1; ; ++hex)
   {
      if (hex % xSize == 16) hex += 2;
      if (hex > 185) break;

      int y = hex / xSize;
      int d = y % 2;

      int areaR1[] = { -17 - d, -16 - d, -1, 0, 1, 17 - d, 18 - d };
      int areaFR[] = { -17 - d, -16 - d, -1, 1, 17 - d, 18 - d };
      int areaR2[] = { -35, -34, -33, -18 - d, -17 - d, -16 - d, -15 - d, -2, -1,
         0, 1, 2, 16 - d, 17 - d, 18 - d, 19 - d, 33, 34, 35 };

      int* area = areaR1;
      int areaSize = sizeof(areaR1) / sizeof(int);

      if (spell == SPL_FROST_RING) {
         area = areaFR;
         areaSize = sizeof(areaFR) / sizeof(int);
      }

      if (spell == SPL_INFERNO) {
         area = areaR2;
         areaSize = sizeof(areaR2) / sizeof(int);
      }

      int countUs = 0, countThem = 0, fightValueUs = 0, fightValueThem = 0;
      for (int i = 0; i < areaSize; ++i)
      {
         int iHex = hex + area[i];
         if (isValidHex(iHex))
         {
            _BattleStackEx_* stack = (_BattleStackEx_*)o_BattleMgr->hex[iHex].GetCreature();
            if (stack)
            {
               if (!stack->is_killed && !stack->isImmuneTo(spell))
               {
                  if (stack->side == o_BattleMgr->current_side) {
                     ++countThem;
                     fightValueThem += stack->fightValue();
                  }
                  else {
                     ++countUs;
                     fightValueUs += stack->fightValue();
                  }
               }

               if (i + 1 < areaSize)
               {
                  int iHexNext = hex + area[i + 1];
                  if (isValidHex(iHexNext) && (_BattleStackEx_*)o_BattleMgr->hex[iHexNext].GetCreature() == stack) ++i;
               }
            }
         }
      }

      if (countUs + countThem > 0 && fightValueThem - fightValueUs > maxFightValueDelta)
      {
         maxFightValueDelta = fightValueThem - fightValueUs;
         bestTargetHex = hex;
      }
   }

   return bestTargetHex;
}

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

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

      CALL_3(void, __fastcall, 0x59A890, o_Spell[spell].wav_name, -1, 3);
      CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, getSpellTargetHex(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 reflectSpell(LoHook* h, HookContext* c)
{
   _Hero_* hero = 0;
   _BattleStackEx_* stack = 0;
   int spell, spellPower, schoolLevel;
   int casterSide = o_BattleMgr->current_side;

   _ptr_ addr = h->GetAddress();

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

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

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

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

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

   if (addr == hAddr[A_REFLECTSPELL_HOOK][E_ARMAGEDDON])
   {
      stack = (_BattleStackEx_*)c->esi;
      spell = SPL_ARMAGEDDON;
      c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_ARMAGEDDON];
   }

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

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

      if (stack->magicMirrorLucky(spell))
      {
         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, перенаправляем заклинание на вражеский отряд
         _BattleStackEx_* stackFoe = stack->getEnemyStackToRedirectTo(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");

         // проверяем язык после загрузки всех txt-файлов игры
         _PI->WriteLoHook(0x4EDFFD, isRusLanguage);

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

         // Инициализируем данные
         _PI->WriteLoHook(0x5A058B, dataInit);

         // Синглкаст
         _PI->WriteLoHook(0x5A05CC, newTarget);
         _PI->WriteLoHook(0x5A058B, magicMirrorAI);

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

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

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

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

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

Реализовал отражение для Thunderbirds. В принципе, идея одна и та же, можно как частный случай в reflectSpell() добавить позже. Также нашёл ошибку в reflectSpell(), связанную с резистом отражённого заклинания. Исправил:

Код: Выделить всё
// Если сработало Magic Mirror, перенаправляем заклинание на вражеский отряд
_BattleStackEx_* stackFoe = stack->getStackToRedirect(spell);
if ( stackFoe )
{
    // Определяем, сработает ли резист против отражённого заклинания
    int hitChanceFoe = (int)(100 * stackFoe->getMagicResist(spell));

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

Тут брался резист отряда, на который направляется заклинание (stack), а не того, на который перенаправляется.

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

using namespace std;

Patcher* _P;
PatcherInstance* _PI;

struct _BattleStackEx_ : public _BattleStack_
{
    bool approvedTarget(const int spell);
    bool magicMirrorLucky(const int spell);
    _BattleStackEx_* getStackToRedirect(const int spell);
    inline int fightValue() {
        return this->creature.fight_value * this->count_current;
    };
    inline float getMagicVulnerability(const int spell) {
        return CALL_7(float, __thiscall, 0x5A83A0, o_BattleMgr, spell, o_BattleMgr->current_side, this, 1, 1, 0);
    };
    inline int getHitChance(const int spell) {
        return (int)(100 * getMagicVulnerability(spell));
    }
    inline bool isImmuneTo(const int spell) {
        return getMagicVulnerability(spell) == 0;
    };
};

bool showMagicMirrorAnim, showMagicResistAnim, showSpellAnim, redirectAreaSpell;
bool needMagicMirrorAnim[2][20], needMagicResistAnim[2][20], needSpellAnim[2][20];
vector<_BattleStackEx_*> stackVector;
int stackIndex;

#define SPL_DEATH_RIPPLE 24
#define CID_AZURE_DRAGON 132

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

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

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

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

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

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

    return !this->isImmuneTo(spell) && !(o_Spell[spell].flags & SPF_FRIENDLY_HAS_MASS) && (this->side != o_BattleMgr->current_side ||
        spell == SPL_FROST_RING || spell == SPL_FIREBALL || spell == SPL_INFERNO || spell == SPL_METEOR_SHOWER ||
        spell == SPL_BERSERK || spell == SPL_DEATH_RIPPLE || spell == SPL_DESTROY_UNDEAD || spell == SPL_ARMAGEDDON) &&
        this->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(1, 100) <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel];
}

_BattleStackEx_* _BattleStackEx_::getStackToRedirect(const int spell)
{
    int sideFoe = 1 - this->side;
    _BattleStackEx_* stackFoe = stackIndex < (int)stackVector.size() ?
        (_BattleStackEx_*)&o_BattleMgr->stack[sideFoe][stackVector[stackIndex]->index_on_side] : 0;
   
    ++stackIndex;
    return stackFoe;
}

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 cmpFightValue(_BattleStackEx_* a, _BattleStackEx_* b)
{
    return a->creature.fight_value * a->count_current > b->creature.fight_value * b->count_current;
}

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

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

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

int __stdcall newTarget(LoHook* h, HookContext* c)
{
    if ( stackIndex < (int)stackVector.size() )
        c->eax = (int)&o_BattleMgr->stack[o_BattleMgr->current_side][stackVector[stackIndex]->index_on_side];
    else {
        int i = 0;
        // Пропускаем погибшие отряды
        while ( o_BattleMgr->stack[o_BattleMgr->current_side][i].is_killed ) ++i;
        c->eax = (int)&o_BattleMgr->stack[o_BattleMgr->current_side][i];
    }
   
    return EXEC_DEFAULT;
}

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

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

    return EXEC_DEFAULT;
}

int getMonsterSpell(const int creature_id)
{
    int spell;

    switch ( creature_id )
    {
    case CID_THUNDERBIRD:
        spell = SPL_LIGHTNING_BOLT;
        break;
    case CID_DRAGON_FLY:
        spell = SPL_WEAKNESS;
        break;
    }

    return spell;
}

int __stdcall dataInit(LoHook* h, HookContext* c)
{
    int spell = h->GetAddress() == 0x5A058B ? *(int*)(c->ebp + 8) :
        getMonsterSpell(((_BattleStackEx_*)c->ecx)->creature_id);

    showMagicMirrorAnim = false;
    showMagicResistAnim = false;
    showSpellAnim = false;
    redirectAreaSpell = false;
    memset(needMagicMirrorAnim, false, 40);
    memset(needMagicResistAnim, false, 40);
    memset(needSpellAnim, false, 40);

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

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

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

    stackIndex = 0;

    return EXEC_DEFAULT;
}

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

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

        // Если герой - AI, на отряде висит Magic Mirror и оно сработало, а также оригинальные проверки на Teleport и Sacrifice
        if ( !CALL_2(bool, __thiscall, 0x4CE600, o_GameMgr, o_BattleMgr->owner_id[o_BattleMgr->current_side]) &&
             stack->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(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 playMagicResist()
{
    // Проигрываем массовую анимацию и звук резиста
    if ( showMagicResistAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, "MagicRes.wav", -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicResistAnim, 78, 0);
    }
}

void playMagicMirror()
{
    // Проигрываем массовую анимацию и звук 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);
    }
}

void playResistAndMagicMirror()
{
    playMagicResist();
    playMagicMirror();
}

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

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

int getSpellTargetHex(const int spell)
{
    const int xSize = 17;
               
    int maxFightValueDelta = 0;
    int bestTargetHex = -1;
    for (int hex = 1; ; ++hex)
    {
        if ( hex % xSize == 16 ) hex += 2;
        if ( hex > 185 ) break;
     
        int y = hex / xSize;
        int d = y % 2;

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

        if ( spell == SPL_INFERNO ) {
            area = areaR2;
            areaSize = sizeof(areaR2) / sizeof(int);
        }
       
        int countUs = 0, countThem = 0, fightValueUs = 0, fightValueThem = 0;
        for (int i = 0; i < areaSize; ++i)
        {
            int iHex = hex + area[i];
            if ( isValidHex(iHex) )
            {
                _BattleStackEx_* stack = (_BattleStackEx_*)o_BattleMgr->hex[iHex].GetCreature();
                if ( stack )
                {
                    if ( !stack->is_killed && !stack->isImmuneTo(spell) )
                    {
                        if ( stack->side == o_BattleMgr->current_side ) {
                            ++countThem;
                            fightValueThem += stack->fightValue();
                        }
                        else {
                            ++countUs;
                            fightValueUs += stack->fightValue();
                        }
                    }
                                   
                    if ( i + 1 < areaSize )
                    {
                        int iHexNext = hex + area[i + 1];
                        if ( isValidHex(iHexNext) && (_BattleStackEx_*)o_BattleMgr->hex[iHexNext].GetCreature() == stack ) ++i;
                    }
                }
            }
        }

        if ( countUs + countThem > 0 && fightValueThem - fightValueUs > maxFightValueDelta )
        {
            maxFightValueDelta = fightValueThem - fightValueUs;
            bestTargetHex = hex;
        }
    }

    return bestTargetHex;
}

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

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

        CALL_3(void, __fastcall, 0x59A890, o_Spell[spell].wav_name, -1, 3);
        CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, getSpellTargetHex(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 reflectSpell(LoHook* h, HookContext* c)
{
    _Hero_* hero = 0;
    _BattleStackEx_* stack = 0;
    int spell, spellPower, schoolLevel;
    int casterSide = o_BattleMgr->current_side;

    _ptr_ addr = h->GetAddress();

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

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

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

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

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

    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_ARMAGEDDON] )
    {
        stack = (_BattleStackEx_*)c->esi;
        spell = SPL_ARMAGEDDON;
        c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_ARMAGEDDON];
    }

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

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

        if ( stack->magicMirrorLucky(spell) )
        {
            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, перенаправляем заклинание на вражеский отряд
            _BattleStackEx_* stackFoe = stack->getStackToRedirect(spell);
            if ( stackFoe )
            {
                // Определяем, сработает ли резист против отражённого заклинания
                int hitChanceFoe = stackFoe->getHitChance(spell);

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

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

    _ptr_ addrHit, addrResist = 0x4412AB;
    switch ( h->GetAddress() )
    {
    case 0x440EE8:
        spell = SPL_LIGHTNING_BOLT;
        addrHit = 0x440F03;
        break;
    case 0x441178:
        spell = SPL_WEAKNESS;
        addrHit = 0x441193;
        break;
    }

    if ( stack->magicMirrorLucky(spell) )
    {
        showMagicMirrorAnim = true;
        needMagicMirrorAnim[stack->side][stack->index_on_side] = true;
        playMagicMirror();
       
        // Если сработало Magic Mirror, перенаправляем заклинание на вражеский отряд
        _BattleStackEx_* stackFoe = stack->getStackToRedirect(spell);
        if ( stackFoe )
        {
            // Определяем, сработает ли резист против отражённого заклинания
            int hitChanceFoe = stackFoe->getHitChance(spell);

            if ( Randint(1, 100) <= hitChanceFoe )
            {
                c->edi = (int)stackFoe;
                c->return_address = addrHit;
                return NO_EXEC_DEFAULT;
            }
            else if ( hitChanceFoe )
            {
                showMagicResistAnim = true;
                needMagicResistAnim[stackFoe->side][stackFoe->index_on_side] = true;
                playMagicResist();

                c->return_address = addrResist;
                return NO_EXEC_DEFAULT;
            }
        }
    }
    else
    {
        int hitChance = stack->getHitChance(spell);

        if ( Randint(1, 100) <= hitChance )
        {
            c->return_address = addrHit;
            return NO_EXEC_DEFAULT;
        }
        else if ( hitChance )
        {
            showMagicResistAnim = true;
            needMagicResistAnim[stack->side][stack->index_on_side] = true;
            playMagicResist();

            c->return_address = addrResist;
            return NO_EXEC_DEFAULT;
        }
    }
   
    return EXEC_DEFAULT;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    static bool plugin_On = false;

    if ( DLL_PROCESS_ATTACH == ul_reason_for_call )
    {
        if ( !plugin_On )
        {
            plugin_On = true;
            _P = GetPatcher();
            _PI = _P->CreateInstance((char*)"HD.Plugin.NewMagicMirrorTest");

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

            // Thunderstrike
            _PI->WriteLoHook(0x440EE8, mirrorMonsterSpell);

            // Weakness
            _PI->WriteLoHook(0x441178, mirrorMonsterSpell);
        }
    }

    return TRUE;
}

Так вопрос возник. В оригинале есть анимация резиста при срабатывании абилок существ? Сам резист работает.
Последний раз редактировалось AlexSpl 02 апр 2021, 13:32, всего редактировалось 2 раз(а).
Вернуться к началу

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

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

Сообщение Rolex » 02 апр 2021, 12:26

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

Цитата:
AlexSpl, попробуйте доработать Вашу функцию getSpellTargetHex в частности сам алгоритм выбора целевого гекса так, как я описал выше.

Пример с Fight Value - это просто пример одной из возможных реализаций алгоритма. Можно и урон максимизировать. Не вижу проблемы в реализации. Попробуйте написать сами. Инфа о кастующем герое есть, инфа по отрядам в области действия заклинания тоже. Какая разница какую разницу :smile1: считать: Fight Value или Damage?

Цитата:
И еще один момент. Мне не нравится как работает rand(). У него относительно короткий период.

В плагине newEagleEye используется, например, rand_s(). Заменить всегда можно.

Цитата:
The rand_s function writes a pseudorandom integer in the range 0 to UINT_MAX to the input pointer. The rand_s function uses the operating system to generate cryptographically secure random numbers. It does not use the seed generated by the srand function, nor does it affect the random number sequence used by rand.


Цитата:
Нет. Ее тоже было бы неплохо прикрутить, если сам резист работает.

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

* * *
Сделал "отражение" Слабости. Но разве её можно отразить? Перед ней же идёт Dispel? :smile4: Но код пусть остаётся ради анимации резиста.
Ещё, чтобы не забыть: в коде нигде нет проверок на Гипноз, а в оригинальном коде такие проверки есть. Как поступать с загипнотизированными отрядами? Считать их союзниками?
Вернуться к началу

Пред.След.

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

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

Сейчас этот форум просматривают: GoGo.Ru [Bot] и гости: 2