Объявления

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

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

Герои Меча и Магии III: Возрождение Эрафии, Герои Меча и Магии III Дыхание Смерти, Герои Меча и Магии III Клинок Армагеддона, Герои Меча и Магии III Хроники Героев
offlineАватара пользователя
void_17  
имя: имя
Ветеран
Ветеран
 
Сообщения: 548
Зарегистрирован: 25 апр 2021, 15:05
Откуда: Оттуда
Пол: Мужчина
Поблагодарили: 132 раз.

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

Сообщение void_17 » 29 июн 2021, 09:42

Сейчас на даче и на ноутбук скачал IDA. Проблема в том, что базу не видит(забыл с рабочего ПК перекинуть), поэтому прошу, есть у кого dbfix.plw? Интернет перерыл, не нашел
Вернуться к началу

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

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

Сообщение Rolex » 01 июл 2021, 15:56

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

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

Patcher* _P;
PatcherInstance* _PI;

static _bool_ plugin_On = 0;

bool eagleEyeMessage = false;
char Text[200];
int EagleEyeSpell;

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

   c->return_address = 0x5A02D9;

   return NO_EXEC_DEFAULT;
}

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

SecSkillDesc EagleEyeDesc =
{
   "Орлиный взор",
   "{Базовый Орлиный взор}\n\nГерой с 60%-ной вероятностью изучает направленное врагом заклинание, как только получает ход в бою.",
   "{Продвинутый Орлиный взор}\n\nГерой с 80%-ной вероятностью изучает направленное врагом заклинание, как только получает ход в бою.",
   "{Экспертный Орлиный взор}\n\nГерой с 100%-ной вероятностью изучает направленное врагом заклинание, как только получает ход в бою."
};

int __stdcall changeEagleEyeDesc(LoHook* h, HookContext* c)
{
   *(SecSkillDesc*)(*(int*)0x67DCF0 + 11 * 16) = EagleEyeDesc;
   return EXEC_DEFAULT;
}

struct PicStruc {
   int type;       // тип картинки (9 - заклинание)
   int id;         // ID картинки
};

// кастомный _List_
struct List {
   _ptr_ Creation;
   PicStruc* Data;
   PicStruc* EndData;
   _ptr_ EndMem;
};

int captionAddr;
bool dlgFirst[] = { true, true };

// Заголовок в каждом диалоге
int __stdcall saveCaption(LoHook* h, HookContext* c)
{
   captionAddr = c->ecx;
   return EXEC_DEFAULT;
}

int __stdcall captionFix(LoHook* h, HookContext* c)
{
   *(int*)(c->ebp - 0x14) = captionAddr;
   return EXEC_DEFAULT;
}

int showSpellDlg(_Hero_* hero, int spells[], int nPics)
{
   if (!nPics) return 0;

   List picList;

   // Динамический массив картинок
   // Первый и последний элемент резервируем для полей Creation и EndData/EndMem соответственно
   PicStruc* pic = new PicStruc[nPics + 2];

   for (int i = 0; i < nPics; ++i) {
      pic[i + 1].type = 9;
      pic[i + 1].id = spells[i];
   }

   picList.Creation = (_ptr_)pic + 4;
   picList.Data = pic + 1; // Адрес первого элемента в списке
   picList.EndData = picList.Data + nPics; // Адрес следующего за последним элементом в списке байта
   picList.EndMem = (_ptr_)picList.EndData;

   sprintf(o_TextBuffer, "Благодаря навыку \"Орлиный глаз\", %s изучил%s следующ%s заклинан%s:",
      hero->name, hero->sex ? "а" : "", nPics > 1 ? "ие" : "ее", nPics > 1 ? "ия" : "ие");
   CALL_5(unsigned int, __fastcall, 0x4F7D20, o_TextBuffer, &picList, -1, -1, 0);

   delete[] pic;

   return 0;
}

void getEagleEyeSpells(_Hero_* hero, _Hero_* heroDonor, int spells[])
{
   int n = 0;
   unsigned int eagleEyeProb = (unsigned int)(CALL_1(float, __thiscall, 0x4E4690, hero) * 100.0);
   
   if (heroDonor->spell_level[EagleEyeSpell] && !hero->spell[EagleEyeSpell])
   {
      if (o_Spell[EagleEyeSpell].level <= hero->second_skill[HSS_EAGLE_EYE] + 1)
      {
         unsigned int dice;
         rand_s(&dice);
         dice = (unsigned int)((double)dice / ((double)UINT_MAX + 1) * 100.0) + 1;
         if (dice <= eagleEyeProb)
         {
            spells[n] = EagleEyeSpell;
            hero->spell[EagleEyeSpell] = 1;
            hero->spell_level[EagleEyeSpell] = 1;
         }
      }
   }
}

int __stdcall eagleEyeMain(LoHook* h, HookContext* c)
{
   _Hero_* hero[] = { o_BattleMgr->hero[ATTACKER], o_BattleMgr->hero[DEFENDER] };

   if (hero[ATTACKER] && hero[DEFENDER])
   {
      int spells[70];

      for (int i = ATTACKER; i <= DEFENDER; ++i)
      {
         if (dlgFirst[i] && o_BattleMgr->current_side == i && hero[i]->second_skill[HSS_EAGLE_EYE] &&
            hero[i]->doll_art[AS_SPELL_BOOK].id == AID_SPELL_BOOK)
         {
            dlgFirst[i] = false;

            // Учим заклинание
            getEagleEyeSpells(hero[i], hero[1 - i], spells);

            // Но диалог показываем только для игрока-человека:
            // в хотсите - диалог для обоих героев, в сетевой игре - только для своего героя.
            if (o_GameMgr->GetPlayer(hero[i]->owner_id)->IsHuman()) {
               int id = o_GameMgr->GetMeID();
               if (!o_NetworkGame || hero[i]->owner_id == id) {
                  o_ActivePlayerID = hero[i]->owner_id;
                  showSpellDlg(hero[i], spells, 1);
                  o_ActivePlayerID = id;
               }
            }
         }
      }
   }

   return EXEC_DEFAULT;
}

int __stdcall eagleEyeSetGlobalFlags(LoHook* h, HookContext* c)
{
   dlgFirst[ATTACKER] = true;
   dlgFirst[DEFENDER] = true;

   return EXEC_DEFAULT;
}

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

         // Меняем коэффициенты
         float eagleEyeCoefs[] = { 0.00f, 0.60f, 0.80f, 1.00f };

         _PI->WriteDword(0x63EA2C, (int&)eagleEyeCoefs[1]);
         _PI->WriteDword(0x63EA30, (int&)eagleEyeCoefs[2]);
         _PI->WriteDword(0x63EA34, (int&)eagleEyeCoefs[3]);

         // Убираем оригинальный эффект
         _PI->WriteHexPatch(0x469C23, "EB");
         _PI->WriteHexPatch(0x476996, "E9 DD 01 00 00");

         // получаем id направленного врагом заклинания
         _PI->WriteLoHook(0x5A02C6, getSpellEagleEye);

         // Фиксим заголовок диалога
         _PI->WriteLoHook(0x4F7D49, saveCaption);
         _PI->WriteLoHook(0x4F7D54, captionFix);

         _PI->WriteLoHook(0x462C7D, eagleEyeSetGlobalFlags);
         _PI->WriteLoHook(0x477C00, eagleEyeMain);
         _PI->WriteLoHook(0x4E6D77, changeEagleEyeDesc);
      }
   }

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

offlineАватара пользователя
void_17  
имя: имя
Ветеран
Ветеран
 
Сообщения: 548
Зарегистрирован: 25 апр 2021, 15:05
Откуда: Оттуда
Пол: Мужчина
Поблагодарили: 132 раз.

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

Сообщение void_17 » 01 июл 2021, 22:57

Rolex писал(а):

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

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

Patcher* _P;
PatcherInstance* _PI;

static _bool_ plugin_On = 0;

bool eagleEyeMessage = false;
char Text[200];
int EagleEyeSpell;

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

   c->return_address = 0x5A02D9;

   return NO_EXEC_DEFAULT;
}

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

SecSkillDesc EagleEyeDesc =
{
   "Орлиный взор",
   "{Базовый Орлиный взор}\n\nГерой с 60%-ной вероятностью изучает направленное врагом заклинание, как только получает ход в бою.",
   "{Продвинутый Орлиный взор}\n\nГерой с 80%-ной вероятностью изучает направленное врагом заклинание, как только получает ход в бою.",
   "{Экспертный Орлиный взор}\n\nГерой с 100%-ной вероятностью изучает направленное врагом заклинание, как только получает ход в бою."
};

int __stdcall changeEagleEyeDesc(LoHook* h, HookContext* c)
{
   *(SecSkillDesc*)(*(int*)0x67DCF0 + 11 * 16) = EagleEyeDesc;
   return EXEC_DEFAULT;
}

struct PicStruc {
   int type;       // тип картинки (9 - заклинание)
   int id;         // ID картинки
};

// кастомный _List_
struct List {
   _ptr_ Creation;
   PicStruc* Data;
   PicStruc* EndData;
   _ptr_ EndMem;
};

int captionAddr;
bool dlgFirst[] = { true, true };

// Заголовок в каждом диалоге
int __stdcall saveCaption(LoHook* h, HookContext* c)
{
   captionAddr = c->ecx;
   return EXEC_DEFAULT;
}

int __stdcall captionFix(LoHook* h, HookContext* c)
{
   *(int*)(c->ebp - 0x14) = captionAddr;
   return EXEC_DEFAULT;
}

int showSpellDlg(_Hero_* hero, int spells[], int nPics)
{
   if (!nPics) return 0;

   List picList;

   // Динамический массив картинок
   // Первый и последний элемент резервируем для полей Creation и EndData/EndMem соответственно
   PicStruc* pic = new PicStruc[nPics + 2];

   for (int i = 0; i < nPics; ++i) {
      pic[i + 1].type = 9;
      pic[i + 1].id = spells[i];
   }

   picList.Creation = (_ptr_)pic + 4;
   picList.Data = pic + 1; // Адрес первого элемента в списке
   picList.EndData = picList.Data + nPics; // Адрес следующего за последним элементом в списке байта
   picList.EndMem = (_ptr_)picList.EndData;

   sprintf(o_TextBuffer, "Благодаря навыку \"Орлиный глаз\", %s изучил%s следующ%s заклинан%s:",
      hero->name, hero->sex ? "а" : "", nPics > 1 ? "ие" : "ее", nPics > 1 ? "ия" : "ие");
   CALL_5(unsigned int, __fastcall, 0x4F7D20, o_TextBuffer, &picList, -1, -1, 0);

   delete[] pic;

   return 0;
}

void getEagleEyeSpells(_Hero_* hero, _Hero_* heroDonor, int spells[])
{
   int n = 0;
   unsigned int eagleEyeProb = (unsigned int)(CALL_1(float, __thiscall, 0x4E4690, hero) * 100.0);
   
   if (heroDonor->spell_level[EagleEyeSpell] && !hero->spell[EagleEyeSpell])
   {
      if (o_Spell[EagleEyeSpell].level <= hero->second_skill[HSS_EAGLE_EYE] + 1)
      {
         unsigned int dice;
         rand_s(&dice);
         dice = (unsigned int)((double)dice / ((double)UINT_MAX + 1) * 100.0) + 1;
         if (dice <= eagleEyeProb)
         {
            spells[n] = EagleEyeSpell;
            hero->spell[EagleEyeSpell] = 1;
            hero->spell_level[EagleEyeSpell] = 1;
         }
      }
   }
}

int __stdcall eagleEyeMain(LoHook* h, HookContext* c)
{
   _Hero_* hero[] = { o_BattleMgr->hero[ATTACKER], o_BattleMgr->hero[DEFENDER] };

   if (hero[ATTACKER] && hero[DEFENDER])
   {
      int spells[70];

      for (int i = ATTACKER; i <= DEFENDER; ++i)
      {
         if (dlgFirst[i] && o_BattleMgr->current_side == i && hero[i]->second_skill[HSS_EAGLE_EYE] &&
            hero[i]->doll_art[AS_SPELL_BOOK].id == AID_SPELL_BOOK)
         {
            dlgFirst[i] = false;

            // Учим заклинание
            getEagleEyeSpells(hero[i], hero[1 - i], spells);

            // Но диалог показываем только для игрока-человека:
            // в хотсите - диалог для обоих героев, в сетевой игре - только для своего героя.
            if (o_GameMgr->GetPlayer(hero[i]->owner_id)->IsHuman()) {
               int id = o_GameMgr->GetMeID();
               if (!o_NetworkGame || hero[i]->owner_id == id) {
                  o_ActivePlayerID = hero[i]->owner_id;
                  showSpellDlg(hero[i], spells, 1);
                  o_ActivePlayerID = id;
               }
            }
         }
      }
   }

   return EXEC_DEFAULT;
}

int __stdcall eagleEyeSetGlobalFlags(LoHook* h, HookContext* c)
{
   dlgFirst[ATTACKER] = true;
   dlgFirst[DEFENDER] = true;

   return EXEC_DEFAULT;
}

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

         // Меняем коэффициенты
         float eagleEyeCoefs[] = { 0.00f, 0.60f, 0.80f, 1.00f };

         _PI->WriteDword(0x63EA2C, (int&)eagleEyeCoefs[1]);
         _PI->WriteDword(0x63EA30, (int&)eagleEyeCoefs[2]);
         _PI->WriteDword(0x63EA34, (int&)eagleEyeCoefs[3]);

         // Убираем оригинальный эффект
         _PI->WriteHexPatch(0x469C23, "EB");
         _PI->WriteHexPatch(0x476996, "E9 DD 01 00 00");

         // получаем id направленного врагом заклинания
         _PI->WriteLoHook(0x5A02C6, getSpellEagleEye);

         // Фиксим заголовок диалога
         _PI->WriteLoHook(0x4F7D49, saveCaption);
         _PI->WriteLoHook(0x4F7D54, captionFix);

         _PI->WriteLoHook(0x462C7D, eagleEyeSetGlobalFlags);
         _PI->WriteLoHook(0x477C00, eagleEyeMain);
         _PI->WriteLoHook(0x4E6D77, changeEagleEyeDesc);
      }
   }

   return TRUE;
}



Ну тогда вот такое решение: создайте массив. Записывайте в него заклинания(одно заклинание - одна ячейка) и потом считываете каждое заклинание циклом for.
Вернуться к началу

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

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

Сообщение Rolex » 03 июл 2021, 06:48

void_17 писал(а):

Ну тогда вот такое решение: создайте массив. Записывайте в него заклинания(одно заклинание - одна ячейка) и потом считываете каждое заклинание циклом for.

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

Код: Выделить всё
int __stdcall getSpellEagleEye(LoHook* h, HookContext* c)
{
   EagleEyeSpell = *(int*)(c->ebp + 8);

   c->return_address = 0x5A02D9;

   return NO_EXEC_DEFAULT;
}


Я нашел в чем была причина. Достаточно в eagleEyeMain убрать вот это dlgFirst[i] = false;

То есть проблема в том, что вот эту проверку не проходит:
Код: Выделить всё
if (dlgFirst[i] && o_BattleMgr->current_side == i && hero[i]->second_skill[HSS_EAGLE_EYE] &&
            hero[i]->doll_art[AS_SPELL_BOOK].id == AID_SPELL_BOOK)


И не проходит ее именно из-за dlgFirst[i]. Когда нужно true дает false и изучения не происходит.

Но если ее убрать (dlgFirst[i] = false) все как бы изучается как нужно, но начинаються другие проблемы: иногда изучается прямо в бою небоевое нулевое заклинание Вызов корабля (или просто показывается диалог об изучении, но закл в книгу не добавляется), а частенько при попытке пропустить ход идет вылет.

Мне не до конца ясно, каково назначение в коде Орлиного глаза от AlexSpl, вот этого хука глобальных флагов eagleEyeSetGlobalFlags. Его надо бы как-то исправить.

Код: Выделить всё
bool dlgFirst[] = { true, true };
...
int __stdcall eagleEyeSetGlobalFlags(LoHook* h, HookContext* c)
{
   dlgFirst[ATTACKER] = true;
   dlgFirst[DEFENDER] = true;

   return EXEC_DEFAULT;
}
...
_PI->WriteLoHook(0x462C7D, eagleEyeSetGlobalFlags);


Плюс хотелось бы сделать, чтобы с помощью Орлиного глаза можно было изучать и заклы 5-го уровня. В оригинале только до 4-го.
Замена этого
Код: Выделить всё
if (o_Spell[EagleEyeSpell].level <= hero->second_skill[HSS_EAGLE_EYE] + 1)

на это:
Код: Выделить всё
if (o_Spell[EagleEyeSpell].level <= hero->second_skill[HSS_EAGLE_EYE] + 2)

не дала результатов. Даже если убрать эту проверку изучение заклов 5-го уровня не происходит. Видимо нужно ставить еще какой-то доп хук.
Вернуться к началу

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

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

Сообщение AlexSpl » 29 июл 2021, 10:24

По многочисленным просьбам радиослушателей :smile1:

Плагин позволяет герою с навыком Eagle Eye с некоторой вероятностью* выучить заклинание, направляемое противником этого героя (заклинание учится в момент каста). Информация о выученных заклинаниях отображается при получении хода героем. Плагин не позволяет изучать заклинания монстров (нужны дополнительные хуки на каст монстров). Для того, чтобы герой мог учить заклинания 5-го уровня, уберите условие o_Spell[spell].level <= hero->second_skill[HSS_EAGLE_EYE] + 1.

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

Patcher* _P;
PatcherInstance* _PI;

struct PicStruc {
    int type;       // тип картинки (9 - заклинание)
    int id;         // ID картинки
};

// кастомный _List_
struct List {
    _ptr_ Creation;
    PicStruc* Data;
    PicStruc* EndData;
    _ptr_ EndMem;
};

int captionAddr;
int n = 0;
int spells[70];
_Hero_* lastHero = 0;

int showSpellDlg(_Hero_* hero, int spells[], int nPics)
{
    if (!nPics) return 0;

    List picList;

    // Динамический массив картинок
    // Первый и последний элемент резервируем для полей Creation и EndData/EndMem соответственно
    PicStruc* pic = new PicStruc[nPics + 2];

    for (int i = 0; i < nPics; ++i) {
        pic[i + 1].type = 9;
        pic[i + 1].id = spells[i];
    }

    picList.Creation = (_ptr_)pic + 4;
    picList.Data = pic + 1; // Адрес первого элемента в списке
    picList.EndData = picList.Data + nPics; // Адрес следующего за последним элементом в списке байта
    picList.EndMem = (_ptr_)picList.EndData;

    sprintf(o_TextBuffer, "Благодаря навыку {Орлиный глаз}, {%s} выучил%s следующ%s заклинан%s:",
        hero->name, hero->sex ? "а" : "", nPics > 1 ? "ие" : "ее", nPics > 1 ? "ия" : "ие");
    CALL_5(unsigned int, __fastcall, 0x4F7D20, o_TextBuffer, &picList, -1, -1, 0);

    delete[] pic;

    return 0;
}

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

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

        if (spell != ID_NONE && !hero->spell[spell] && hero->doll_art[AS_SPELL_BOOK].id == AID_SPELL_BOOK &&
            o_Spell[spell].level <= hero->second_skill[HSS_EAGLE_EYE] + 1)
        {
            unsigned int dice;
            rand_s(&dice);
            dice = (unsigned int)((double)dice / ((double)UINT_MAX + 1) * 100.0) + 1;
            unsigned int eagleEyeProb = (unsigned int)(CALL_1(float, __thiscall, 0x4E4690, hero) * 100.0);
            //if (dice <= eagleEyeProb)
            //{
                spells[n++] = spell;
                hero->spell[spell] = true;
                hero->spell_level[spell] = true;
            //}
        }
    }

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

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

    // Диалог показываем только для игрока-человека:
    // в хотсите - диалог для обоих героев, в сетевой игре - только для своего героя
    if (hero && hero != lastHero)
    {
        int id = o_GameMgr->GetMeID();
        if (!o_NetworkGame || hero->owner_id == id)
        {
            o_ActivePlayerID = hero->owner_id;
            showSpellDlg(hero, spells, n);
            o_ActivePlayerID = id;
        }

        n = 0;
        lastHero = hero;
    }
   
    return EXEC_DEFAULT;
}

// Заголовок в каждом диалоге
int __stdcall saveCaption(LoHook* h, HookContext* c)
{
    captionAddr = c->ecx;
    return EXEC_DEFAULT;
}

int __stdcall captionFix(LoHook* h, HookContext* c)
{
    *(int*)(c->ebp - 0x14) = captionAddr;
    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.NewEagleEye");

            // Меняем коэффициенты
            float eagleEyeCoefs[] = { 0.00f, 0.30f, 0.35f, 0.40f };

            _PI->WriteDword(0x63EA2C, (int&)eagleEyeCoefs[1]);
            _PI->WriteDword(0x63EA30, (int&)eagleEyeCoefs[2]);
            _PI->WriteDword(0x63EA34, (int&)eagleEyeCoefs[3]);

            // Убираем оригинальный эффект
            _PI->WriteHexPatch(0x469C23, (char*)"EB");
            _PI->WriteHexPatch(0x476996, (char*)"E9 DD 01 00 00");

            // Пробуем учить заклинания
            _PI->WriteLoHook(0x5A0262, learnSpell);
           
            // Фиксим заголовок диалога
            _PI->WriteLoHook(0x4F7D49, saveCaption);
            _PI->WriteLoHook(0x4F7D54, captionFix);

            // Показываем выученные заклинания
            _PI->WriteLoHook(0x477C00, showSpells);
        }
    }

    return TRUE;
}

* Чтобы вернуть вероятность, раскомментируйте строки в хуке learnSpell() (комменты нужны для стабильных тестов).
Вернуться к началу

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

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

Сообщение Rolex » 29 июл 2021, 12:30

Огромное спасибо.

AlexSpl писал(а):

Плагин не позволяет изучать заклинания монстров (нужны дополнительные хуки на каст монстров).

А это и ненужно. Проверил в оригинале заклинания-абилки существ (вроде, удара молнии Птиц грома) не изучаються.

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

AlexSpl писал(а):

Для того, чтобы герой мог учить заклинания 5-го уровня, уберите условие o_Spell[spell].level <= hero->second_skill[HSS_EAGLE_EYE] + 1.

Все хорошо, все изучается. Только вот изучается даже тогда, когда недостаточно Мудрости. То есть если у героя Продвинутая Мудрость он может максимум изучить закл 4-го уровня, а в данном случае текущее развития Мудрости игнорируется и изучаються даже заклы 5-го уровня. Ну это я, вроде, пофиксил, заменив условие на такое:
Код: Выделить всё
o_Spell[spell].level <= hero->second_skill[HSS_WISDOM] + 2


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

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

 тыц
Изображение

Изображение

Изображение


Думаю, может в таких случаях сделать как в оригинале. То есть если кастующий герой направил закл и сразу же сдался, то выводить сообщение с изученным заклом сразу же после боя.
Последний раз редактировалось Rolex 29 июл 2021, 16:30, всего редактировалось 2 раз(а).
Вернуться к началу

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

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

Сообщение AlexSpl » 29 июл 2021, 13:09

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

Нужно добавить хук showSpells() (ещё один) вместо оригинального кода вывода изученных заклинаний в конце боя, т.е. вместо вот этого:

Код: Выделить всё
// Убираем оригинальный эффект
...
_PI->WriteHexPatch(0x476996, (char*)"E9 DD 01 00 00");

и хук на начало боя, который бы обнулял n.

* * *
Попробуйте поставить хук на 0x4AC5EA с одной строчкой: n = 0; А здесь - 0x476996 - хук showSpells() с return_address = 0x476B78; (второй HexPatch нужно, конечно, удалить).

Кстати, если кому интересно, запись E9 DD 01 00 00 означает джамп (near) на 0x1DD = +477 байт (вперёд, раз с плюсом) относительно адреса команды, следующей за данной. В плагинах советую использовать _PI->WriteJmp(откуда, куда); вместо этого.
Вернуться к началу

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

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

Сообщение Rolex » 29 июл 2021, 15:58

Что-то не работает. Либо я что-то не так делаю... :smile5:

Добавил хук zeroSpell:

Код: Выделить всё
int __stdcall zeroSpell(LoHook* h, HookContext* c)
{
   n = 0;
   return EXEC_DEFAULT;
}
...
_PI->WriteLoHook(0x4AC5EA, zeroSpell);


Далее добавил еще один хук showSpells и в самом коде хука добавил c->return_address = 0x476B78 и возвращаю NO_EXEC_DEFAULT.

Код: Выделить всё
int __stdcall showSpells(LoHook* h, HookContext* c)
{
   _Hero_* hero = o_BattleMgr->hero[o_BattleMgr->current_side];

   // Диалог показываем только для игрока-человека:
   // в хотсите - диалог для обоих героев, в сетевой игре - только для своего героя
   if (hero && hero != lastHero)
   {
      int id = o_GameMgr->GetMeID();
      if (!o_NetworkGame || hero->owner_id == id)
      {
         o_ActivePlayerID = hero->owner_id;
         showSpellDlg(hero, spells, n);
         o_ActivePlayerID = id;
      }

      n = 0;
      lastHero = hero;
   }

   c->return_address = 0x476B78;
   return NO_EXEC_DEFAULT;
}
...
_PI->WriteLoHook(0x476996, showSpells);


_PI->WriteHexPatch(0x476996, (char*)"E9 DD 01 00 00") - убрал.

На старте боя получаю вылет. Если же исходный код хука showSpells не трогать, а создать дополнительный хук showSpellsTwo, например, который будет дублировать первый за исключением c->return_address = 0x476B78 и NO_EXEC_DEFAULT, то вылета нет, но ничего не меняется. Направленый закл сбежавшим героем не изучается после боя, а при повторной загрузке n не обнуляется почему-то, снова выводится несколько заклов.
Вернуться к началу

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

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

Сообщение AlexSpl » 30 июл 2021, 06:39

Пробуйте так:

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

Patcher* _P;
PatcherInstance* _PI;

struct PicStruc {
    int type;       // тип картинки (9 - заклинание)
    int id;         // ID картинки
};

// кастомный _List_
struct List {
    _ptr_ Creation;
    PicStruc* Data;
    PicStruc* EndData;
    _ptr_ EndMem;
};

int captionAddr;
int n = 0;
int spells[70];
_Hero_* lastHero = 0;

int showSpellDlg(_Hero_* hero, int spells[], int nPics)
{
    if (!nPics) return 0;

    List picList;

    // Динамический массив картинок
    // Первый и последний элемент резервируем для полей Creation и EndData/EndMem соответственно
    PicStruc* pic = new PicStruc[nPics + 2];

    for (int i = 0; i < nPics; ++i) {
        pic[i + 1].type = 9;
        pic[i + 1].id = spells[i];
    }

    picList.Creation = (_ptr_)pic + 4;
    picList.Data = pic + 1; // Адрес первого элемента в списке
    picList.EndData = picList.Data + nPics; // Адрес следующего за последним элементом в списке байта
    picList.EndMem = (_ptr_)picList.EndData;

    sprintf(o_TextBuffer, "Благодаря навыку {Орлиный глаз}, {%s} выучил%s следующ%s заклинан%s:",
        hero->name, hero->sex ? "а" : "", nPics > 1 ? "ие" : "ее", nPics > 1 ? "ия" : "ие");
    CALL_5(unsigned int, __fastcall, 0x4F7D20, o_TextBuffer, &picList, -1, -1, 0);

    delete[] pic;

    return 0;
}

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

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

        if (spell != ID_NONE && !hero->spell[spell] && hero->doll_art[AS_SPELL_BOOK].id == AID_SPELL_BOOK &&
            o_Spell[spell].level <= hero->second_skill[HSS_EAGLE_EYE] + 1)
        {
            unsigned int dice;
            rand_s(&dice);
            dice = (unsigned int)((double)dice / ((double)UINT_MAX + 1) * 100.0) + 1;
            unsigned int eagleEyeProb = (unsigned int)(CALL_1(float, __thiscall, 0x4E4690, hero) * 100.0);
            //if (dice <= eagleEyeProb)
            //{
                spells[n++] = spell;
                hero->spell[spell] = true;
                hero->spell_level[spell] = true;
            //}
        }
    }

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

void showSpells()
{
    _Hero_* hero = o_BattleMgr->hero[o_BattleMgr->current_side];

    // Диалог показываем только для игрока-человека:
    // в хотсите - диалог для обоих героев, в сетевой игре - только для своего героя
    if (hero && hero != lastHero)
    {
        int id = o_GameMgr->GetMeID();
        if (!o_NetworkGame || hero->owner_id == id)
        {
            o_ActivePlayerID = hero->owner_id;
            showSpellDlg(hero, spells, n);
            o_ActivePlayerID = id;
        }

        n = 0;
        lastHero = hero;
    }
}

int __stdcall notifyPlayer(LoHook* h, HookContext* c)
{
    showSpells();

    return EXEC_DEFAULT;
}

int __stdcall notifyPlayerAfterBattle(LoHook* h, HookContext* c)
{
    showSpellDlg(o_BattleMgr->hero[1 - o_BattleMgr->current_side], spells, n);

    return EXEC_DEFAULT;
}

// Заголовок в каждом диалоге
int __stdcall saveCaption(LoHook* h, HookContext* c)
{
    captionAddr = c->ecx;
    return EXEC_DEFAULT;
}

int __stdcall captionFix(LoHook* h, HookContext* c)
{
    *(int*)(c->ebp - 0x14) = captionAddr;
    return EXEC_DEFAULT;
}

int __stdcall zeroSpells(LoHook* h, HookContext* c)
{
    n = 0;
    lastHero = 0;

    return EXEC_DEFAULT;
}

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

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

            // Меняем коэффициенты
            float eagleEyeCoefs[] = { 0.00f, 0.30f, 0.35f, 0.40f };

            _PI->WriteDword(0x63EA2C, (int&)eagleEyeCoefs[1]);
            _PI->WriteDword(0x63EA30, (int&)eagleEyeCoefs[2]);
            _PI->WriteDword(0x63EA34, (int&)eagleEyeCoefs[3]);

            // Убираем оригинальный эффект
            _PI->WriteHexPatch(0x469C23, (char*)"EB");
            _PI->WriteHexPatch(0x476996, (char*)"E9 DD 01 00 00");

            // Пробуем учить заклинания
            _PI->WriteLoHook(0x5A0262, learnSpell);
           
            // Фиксим заголовок диалога
            _PI->WriteLoHook(0x4F7D49, saveCaption);
            _PI->WriteLoHook(0x4F7D54, captionFix);

            // Показываем выученные заклинания
            _PI->WriteLoHook(0x477C00, notifyPlayer);
            _PI->WriteLoHook(0x477214, notifyPlayerAfterBattle);

            _PI->WriteLoHook(0x4AC5EA, zeroSpells);
        }
    }

    return TRUE;
}

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

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

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

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

Сообщение Rolex » 30 июл 2021, 07:04

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

 тыц
Изображение

Изображение

Изображение


При тестировании в хотсите одним героем кастую закл и сразу же ним убегаю, показывается сообщение с выученным заклом. Загружаю заново этот же сейв, кастую либо тот же, либо другой закл и в этот раз дожидаюсь перехода хода к другому герою и сообщения об изученном закле во время боя. В итоге выводится два закла, тот который который я направлял в предыдущем бою с предыдущей загрузки и этот, который был направлен только что в текущем бою. По факту изучается только тот, который был направлен в текущем бою. Тот закл, который с предыдущего боя, просто отображается в сообщении об изученных заклах.
Последний раз редактировалось Rolex 30 июл 2021, 07:08, всего редактировалось 2 раз(а).
Вернуться к началу

Пред.След.

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

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

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

cron