Объявления

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

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

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

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

Сообщение AlexSpl » 13 июн 2021, 12:36

Если хайхук использует вызов оригинальной функции (а это почти всегда так), то нужно не забыть подставить вместо hook->GetDefaultFunc() её реальный адрес. Или можно использовать вариант, предложенный RoseKavalier, только в библиотеке патчера и homm3.h нет функции/макроса FuncAt() (не смотрел реализацию). Хук - это джамп (jmp), поэтому, скорее всего, там будет E9 XX XX XX XX, где XX XX XX XX - это относительный адрес функции (относительно начала следующей за jmp инструкции).

FuncAt(hook_address) = hook_address + 5 + *(int*)(hook_address + 1). Патчер не использует jmp short и jmp far.
Вернуться к началу

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

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

Сообщение void_17 » 14 июн 2021, 11:04

Кто знает, что делает функция DlgItem_SendCmd2Item (0x5FED80) ? Не смог разобрать ни её, ни аргументы
Вернуться к началу

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 » 14 июн 2021, 11:35

Функции для работы с диалогами лучше смотреть в H3API:

Код: Выделить всё
_H3API_ VOID H3DlgItem::SendCommand(INT32 command, INT32 parameter)
{
    THISCALL_3(VOID, 0x5FED80, this, command, parameter);
}

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

Вот пример использования (плагин newDisguise), но здесь используется другая функция, которая отправляет сообщение сразу всем элементам:

Код: Выделить всё
// Устанавливаем ползунок в ближайшее к точке клика положение
_DlgMsg_ m = {512, 3, 40, 0, 0, 0, 0, 0};
CALL_2(void, __thiscall, 0x5FF3A0, dlg, &m);

Связь между command/parameter и сообщением (msg):

Код: Выделить всё
_H3API_ VOID H3BaseDlg::SendCommandToAllItems(INT32 command, INT32 itemID, INT32 parameter)
{
   H3Msg msg;
   msg.SetCommand(0x200, command, itemID, 0, 0, 0, parameter, 0);
   THISCALL_2(VOID, 0x5FF3A0, this, &msg);
}

Если планируете использовать диалоги, то лучше сразу подключать H3API :smile1:
Вернуться к началу

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

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

Сообщение Rolex » 22 июн 2021, 15:20

AlexSpl, я вот взял ваш с Ben80 код NewEagleEye и изменил шанс изучения на 50%/75%/100%. Но при этом я хочу сделать так, чтобы изучались не все подряд заклы, которые есть в книге вражеского героя, а только те, которые он использовал в текущем раунде боя. То есть вражеский герой использовал в текущем раунде заклинание Х, и в этом же раунде наш герой изучает его (если, конечно, его еще нет в книге нашего героя), как только получает ход в битве (если в текущем раунде последним ходит вражексий отряд и именно в этот момент он кастует, то тогда изучаем, получается, уже в следующем раунде). Вопрос в том, что нам нужно исправить в этом коде, чтобы все заработало так, как я описал.

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

        Patcher* _P;
        PatcherInstance* _PI;

        static _bool_ plugin_On = 0;

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

        int getEagleEyeSpells(_Hero_* hero, _Hero_* heroDonor, int spells[])
        {
            int n = 0;
            unsigned int eagleEyeProb = (unsigned int)(CALL_1(float, __thiscall, 0x4E4690, hero) * 100.0);

            for (_Spell_* iSpell = o_Spell + SPL_QUICKSAND; iSpell <= o_Spell + SPL_AIR_ELEMENTAL; ++iSpell)
            {
                int i = iSpell - o_Spell;
                if ( heroDonor->spell_level[i] && !hero->spell[i] )
                {
                    if ( iSpell->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++] = i;
                            hero->spell[i] = 1;
                            hero->spell_level[i] = 1;
                        }
                    }
                }
            }

            return n;
        }

        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;

                        // Учим заклинания всегда.
                        int n = 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, n);
                                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.50f, 0.75f, 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");

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

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

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

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

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

Сообщение void_17 » 23 июн 2021, 06:30

Rolex писал(а):

AlexSpl, я вот взял ваш с Ben80 код NewEagleEye и изменил шанс изучения на 50%/75%/100%. Но при этом я хочу сделать так, чтобы изучались не все подряд заклы, которые есть в книге вражеского героя, а только те, которые он использовал в текущем раунде боя. То есть вражеский герой использовал в текущем раунде заклинание Х, и в этом же раунде наш герой изучает его (если, конечно, его еще нет в книге нашего героя), как только получает ход в битве (если в текущем раунде последним ходит вражексий отряд и именно в этот момент он кастует, то тогда изучаем, получается, уже в следующем раунде). Вопрос в том, что нам нужно исправить в этом коде, чтобы все заработало так, как я описал.


Посмотрите на код, написанный Ben80: здесь сообщение о заклинании добавляется сразу после использования, но в книжку не добавляет заклинание.
Код: Выделить всё
bool eagleEyeMessage = false;
char Text[200];
...
int __stdcall addEagleEyeMessage(LoHook* h, HookContext* c)
{
   if(eagleEyeMessage)
      CALL_4(void, __thiscall, 0x4729D0, *(void**)((int)o_BattleMgr + 0x132FC), Text, 1, 0);

   eagleEyeMessage = false;

   return EXEC_DEFAULT;
}

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

   if(hero->spell[*(int*)(c->ebp + 8)] == 0)
   {
      hero->spell[*(int*)(c->ebp + 8)] = 1;
      hero->spell_level[*(int*)(c->ebp + 8)] = 1;

      sprintf(Text, *(const char **)(*(int*)(*(int*)(0x6A5DC4) + 32) + 888),
         hero->name, o_Spell[*(int*)(c->ebp + 8)].name);
      eagleEyeMessage = true;

   }

   c->return_address = 0x5A02D9;

   return NO_EXEC_DEFAULT;
}
...
_PI->WriteLoHook(0x5A02C6, getSpellEagleEye);
_PI->WriteLoHook(0x5A247B, addEagleEyeMessage);


В функции getSpellEagleEye получают это заклинание.
Вернуться к началу

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

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

Сообщение Rolex » 24 июн 2021, 07:38

void_17 писал(а):

здесь сообщение о заклинании добавляется сразу после использования

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

void_17 писал(а):

но в книжку не добавляет заклинание.

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

Судя по code style плагина, который я взял из второго сообщения из темы с готовыми плагинами, AlexSpl полностью переписал весь код Ben80.

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

Нужно как-то изменить уже имеющиеся eagleEyeMain и getEagleEyeSpells.

В том коде от AlexSpl, что сейчас есть, getEagleEyeSpells принимает указатели на структуру нашего и вражеского героя и массив спеллов и, как я понял, возвращает номер картинки выбранного закла.
Код: Выделить всё
// Учим заклинания всегда.
int n = getEagleEyeSpells(hero[i], hero[1 - i], spells);
...
int getEagleEyeSpells(_Hero_* hero, _Hero_* heroDonor, int spells[])


В нашем же случае нужно вернуть номер картинки направленного вражеским героем закла.
Вернуться к началу

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

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

Сообщение Rolex » 25 июн 2021, 10:36

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

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

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

Сообщение void_17 » 25 июн 2021, 17:19

Я вам кидал код. Там получают нужное вам заклинание. Вы просто создайте глобальную переменную, допустим, EagleEyeSpell и запишите в нее то заклинание и потом используйте в коде функции showSpellDlg и getSpellEagleEye.
Вернуться к началу

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

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

Сообщение Rolex » 25 июн 2021, 18:26

Пробовал подобным образом, почему-то не работает, пытаюсь разобраться в чем дело.

В хуке по получению закла, getSpellEagleEye сохраняем, например, наш закл в

Код: Выделить всё
EagleEyeSpell = *(int*)(c->ebp + 8);


Соотвественно в eagleEyeMain вот это уже не вызываем
Код: Выделить всё
// int n = getEagleEyeSpells(hero[i], hero[1 - i], spells);


И в showSpellDlg передаем этот сохраненный указатель на закл и номер картинки этого закла.

Код: Выделить всё
showSpellDlg(hero[i], &EagleEyeSpell, EagleEyeSpell);
Вернуться к началу

offlineprimitive  
имя: Тим
 
Сообщения: 8
Зарегистрирован: 17 апр 2021, 15:42
Пол: Мужчина
Поблагодарили: 1 раз.

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

Сообщение primitive » 26 июн 2021, 13:41

На редкость познавательный и полезный здесь форум. Прямо хочется сказать спасибо всем за ту кучу плагинов которые я отсюда скачал. В частности, регулярно пользуюсь вот этим https://handbookhmm.ru/forum/viewtopic.php?f=56&t=518&p=21443&hilit=LimitMaxHeroes.ini#p21443 ограничением на покупку количества героев от AlexSpl (которому отдельное спасибо за всё хорошее):
AlexSpl писал(а):

Файл LimitMaxHeroes.ini положить рядом с плагином:

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

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

Пред.След.

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

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

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

cron