Объявления

Друзья, если не получается зарегистрироваться, напишите на почту 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 » 05 окт 2017, 18:06

Вам нужны ещё два файла для работы кастомных диалогов.

 
HotA.zip
(65.29 КБ) Скачиваний: 273

Включите оба cpp файла в Ваш проект: щёлкните на Source Files в Solution Explorer слева, нажмите Shift + Alt + A и добавьте эти файлы.
Вернуться к началу

offlineBen80  
имя: Сергей
Эксперт
Эксперт
 
Сообщения: 1318
Зарегистрирован: 18 июн 2017, 06:49
Пол: Не указан
Поблагодарили: 336 раз.

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

Сообщение Ben80 » 06 окт 2017, 16:49

Почти готовый плагин NewDisguise. Прошу потестить.
И подсказать правильные параметры для _DlgStaticText_::Create :smile1:
Что еще осталось сделать ? Возвращать герою ману, если он нажал отмену. Проигрывать WAV файл после выбора
множителя, а не сразу (если нажали отмену, не проигрывать вообще).

1 баг вроде уже нашел. Скорее всего в этом месте:
Код: Выделить всё
ghostHero->army.count[i] = ghostHero->army.count[i] * multiplier;


multiplier здесь float и компилятор предупреждал, что возможна потеря данных при прeобразовании от float к int
При опции "В 5 раз меньше" результат отличался в различных испытаниях.

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

Patcher* _P;
PatcherInstance* _PI;

static _bool_ plugin_On = 0;

const int ghostHeroHookAddr[] = {0x41664C, 0x4DE583, 0x5D44A0, 0x5D451D};
_Hero_* ghostHero = new _Hero_();

// Выбранный селектор в диалоге (унифицированное значение, не равное выбранному на ползунке)
int selNumber;

// Создаём стандартный задник с рамкой цвета игрока
void setStdBackground(_Dlg_* dlg)
{
    CALL_5 (int, __thiscall, 0x48FA80, dlg, dlg->x, dlg->y, dlg->width, dlg->height);

    for (int i = dlg->field_4C; i <= dlg->field_50; ++i)
    {
        CALL_5 (int, __thiscall, 0x5FF400, dlg, 512, 13, i, o_GameMgr->GetMeID());   
    }
}

void __fastcall scrollDlgCallback_1(int click_id, _Dlg_* dlg)
{
    // Выводим в текстовое поле текущее значение, выбранное с помощью ползунка
    // click_id;
   // Уровень Магии воздуха определяет доступные для выбора значения

   if(click_id == 0)
      sprintf(o_TextBuffer, "%s", "Поменьше");
   if(click_id == 1)
      sprintf(o_TextBuffer, "%s", "Побольше");
   // селектор - преобразуем выбранный номер к унифицированной последовательности
   selNumber = click_id + 4;

    ((_DlgStaticText_*)dlg->GetItem(14))->SetText(o_TextBuffer);

   _DlgMsg_ m = {512, 3, 40, 0, 0, 0, 0, 0};
   CALL_2 (void, __thiscall, 0x5FF3A0, dlg, &m);   
    dlg->Redraw(TRUE);
}

void __fastcall scrollDlgCallback_2(int click_id, _Dlg_* dlg)
{
    // Выводим в текстовое поле текущее значение, выбранное с помощью ползунка
    // click_id;
   // Уровень Магии воздуха определяет доступные для выбора значения

   if(click_id == 0)
      sprintf(o_TextBuffer, "%s", "В 2 раза\nменьше");
   if(click_id == 1)
      sprintf(o_TextBuffer, "%s", "Поменьше");
   if(click_id == 2)
      sprintf(o_TextBuffer, "%s", "Побольше");
   if(click_id == 3)
      sprintf(o_TextBuffer, "%s", "В 2 раза\nбольше");
   // селектор - преобразуем выбранный номер к унифицированной последовательности
   selNumber = click_id + 3;

    ((_DlgStaticText_*)dlg->GetItem(14))->SetText(o_TextBuffer);

   _DlgMsg_ m = {512, 3, 40, 0, 0, 0, 0, 0};
   CALL_2 (void, __thiscall, 0x5FF3A0, dlg, &m);   
    dlg->Redraw(TRUE);
}

void __fastcall scrollDlgCallback_3(int click_id, _Dlg_* dlg)
{
    // Выводим в текстовое поле текущее значение, выбранное с помощью ползунка
    // click_id;
   // Уровень Магии воздуха определяет доступные для выбора значения

   if(click_id == 0)
      sprintf(o_TextBuffer, "%s", "В 5 раз\nменьше");
   if(click_id == 1)
      sprintf(o_TextBuffer, "%s", "В 3 раза\nменьше");
   if(click_id == 2)
      sprintf(o_TextBuffer, "%s", "В 2 раза\nменьше");
   if(click_id == 3)
      sprintf(o_TextBuffer, "%s", "Поменьше");
   if(click_id == 4)
      sprintf(o_TextBuffer, "%s", "Побольше");
   if(click_id == 5)
      sprintf(o_TextBuffer, "%s", "В 2 раза\nбольше");
   if(click_id == 6)
      sprintf(o_TextBuffer, "%s", "В 3 раза\nбольше");
   if(click_id == 7)
      sprintf(o_TextBuffer, "%s", "В 5 раз\nбольше");
   // селектор - преобразуем выбранный номер к унифицированной последовательности
   selNumber = click_id + 1;

    ((_DlgStaticText_*)dlg->GetItem(14))->SetText(o_TextBuffer);

   _DlgMsg_ m = {512, 3, 40, 0, 0, 0, 0, 0};
   CALL_2 (void, __thiscall, 0x5FF3A0, dlg, &m);   
    dlg->Redraw(TRUE);
}


void yDlgShow(int width, int height, int count, char* captionText, char* messageText, bool middle = true, int x = 0, int y = 0)
{
    // Создаём по центру экрана пустой диалог с тенью
    _Dlg_* dlg = _CustomDlg_::Create(DLG_X_CENTER, DLG_Y_CENTER, width, height, DF_SCREENSHOT | DF_SHADOW, NULL);
   
    // Добавляем задник (фон) и рамку по цвету игрока
    setStdBackground(dlg);
       
    // Добавляем заголовок диалога
    dlg->AddItem(_DlgStaticText_::Create(0, 20, dlg->width, 30,
        captionText, "bigfont.fnt", 7, 1, ALIGN_H_CENTER | ALIGN_V_TOP, 0));

    // Добавляем текст
    dlg->AddItem(_DlgStaticText_::Create(0, 50, dlg->width, 50,
        messageText, "medfont.fnt", 1, 0, ALIGN_H_CENTER | ALIGN_V_TOP, 0));

    // Добавляем полосу горизонтальной прокрутки
    //dlg->AddItem(_DlgScroll_::Create(30, dlg->height - 90, dlg->width - 30 * 2 + 1, 16, 40, count,
    //    (_ptr_)scrollDlgCallback, 0, 18, 0));
   
   
    // Добавляем текстовое поле для отображения значений, выбираемых с помощью ползунка
   if(count == 2)
   {
       dlg->AddItem(_DlgStaticText_::Create((dlg->width - 30) / 2 + 1, dlg->height - 61, 72, 40,
          "Поменьше", "medfont.fnt", 1, 14, ALIGN_H_CENTER | ALIGN_V_CENTER, 0));
      dlg->AddItem(_DlgScroll_::Create(30, dlg->height - 90, dlg->width - 30 * 2 + 1, 16, 40, count,
        (_ptr_)scrollDlgCallback_1, 0, 0, 0));
   }
   if(count == 4)
   {
      dlg->AddItem(_DlgStaticText_::Create((dlg->width - 30) / 2 + 1, dlg->height - 61, 72, 40,
         "В 2 раза\nменьше", "medfont.fnt", 1, 14, ALIGN_H_CENTER | ALIGN_V_CENTER, 0));
      dlg->AddItem(_DlgScroll_::Create(30, dlg->height - 90, dlg->width - 30 * 2 + 1, 16, 40, count,
        (_ptr_)scrollDlgCallback_2, 0, 0, 0));
   }
   if(count == 8)
   {
      dlg->AddItem(_DlgStaticText_::Create((dlg->width - 30) / 2 + 1, dlg->height - 61, 72, 40,
         "В 5 раз\nменьше", "medfont.fnt", 1, 14, ALIGN_H_CENTER | ALIGN_V_CENTER, 0));
      dlg->AddItem(_DlgScroll_::Create(30, dlg->height - 90, dlg->width - 30 * 2 + 1, 16, 40, count,
        (_ptr_)scrollDlgCallback_3, 0, 0, 0));
   }

    // Добавляем кнопку OK
    dlg->AddItem(_DlgStaticPcx8_::Create(30, dlg->height - 61, 0, "Box64x30.pcx"));
    dlg->AddItem(_DlgButton_::Create(31, dlg->height - 60, 64, 30, 30722, "iOkay.def", 0, 1, 1, 28, 2));

    // Добавляем кнопку Cancel
    dlg->AddItem(_DlgStaticPcx8_::Create(dlg->width - 31 - 64, dlg->height - 61, 0, "Box64x30.pcx"));
    dlg->AddItem(_DlgButton_::Create(dlg->width - 30 - 64, dlg->height - 60, 64, 30, 30721, "iCancel.def", 0, 1, 1, 1, 2));

    if ( !middle ) dlg->SetPos(x, y);
    dlg->Run();
    dlg->Destroy(TRUE);
}

float __stdcall getUnpackedMultiplier(int n)
{
   float selFloat[8] = {0.2, 0.333, 0.5, 0.666, 1.5, 2.0, 3.0, 5.0};
   return selFloat[n/2 - 1];
}

int __stdcall makeGhostHero(LoHook* h, HookContext* c)
{
    _Hero_* hero;
    int* heroReg;
   float multiplier = 1.0;

    h->GetAddress() == ghostHeroHookAddr[0] ? heroReg = &c->esi : heroReg = &c->eax;
    hero = *(_Hero_**)heroReg;
   
    if ( hero->disguise != -1)
    {
        // Копируем героя в "призрачного" героя
        *ghostHero = *hero;
       
      // Своих героев просматриваем в истинном виде (в оригинальной игре это не так)
        if(hero->owner_id != o_GameMgr->GetMeID())
         multiplier = getUnpackedMultiplier((int)hero->disguise);

      ghostHero->disguise = -1;
     
        for (int i = 0; i < 7; ++i)
        {
            ghostHero->army.count[i] = ghostHero->army.count[i] * multiplier;
        }
     
        *(int*)heroReg = (int)ghostHero;
    }

    return EXEC_DEFAULT;
}


int __stdcall decPackedDisguise(LoHook* h, HookContext* c)
{
    for (int i = 0; i < o_HEROES_COUNT; ++i)
    {
        if ( o_GameMgr->GetHero(i)->owner_id == o_GameMgr->GetMeID() && o_GameMgr->GetHero(i)->disguise != -1 )
        {
         if(((int)(o_GameMgr->GetHero(i)->disguise)) % 2 == 0)
            o_GameMgr->GetHero(i)->disguise = -1;
         else
            --o_GameMgr->GetHero(i)->disguise;
        }
    }

    return EXEC_DEFAULT;
}

int __stdcall setPackedDisguise(LoHook* h, HookContext* c)
{
   // Получаем количество делений для ползунка исходя из уровня Магии воздуха
   int arPos[4] = {2,2,4,8};
   int nPos = arPos[*(char*)(c->esi + 201 + 15)];
   // Если герой скастовал Disguise, показываем диалог
    //yDlgShow(240, 140, nPos, "Маскировка", "Выберите мнимую\nсилу отрядов");
   yDlgShow(360, 210, nPos, "Маскировка", "Выберите мнимую\nсилу отрядов");
   
    // Если нажата кнопка OK (30722), то формируем поле disguise у героя,
    // иначе - возвращаем герою израсходованную ману ???
    if ( o_WndMgr->result_dlg_item_id == 30722 )
    // +1 - потому что длительность - 2 хода
       c->edi = selNumber * 2 + 1;
   else
      c->edi = -1;

    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.NewDisguise");

         _PI->WriteHexPatch(0x4C7DA5, "90 90 90");
         _PI->WriteLoHook(0x4C75E0, decPackedDisguise);
         _PI->WriteLoHook(0x41C7C6, setPackedDisguise);
         for (int i = 0; i < 4; ++i) _PI->WriteLoHook(ghostHeroHookAddr[i], makeGhostHero);
        }
    }

   return TRUE;
}

Вернуться к началу

offlineBen80  
имя: Сергей
Эксперт
Эксперт
 
Сообщения: 1318
Зарегистрирован: 18 июн 2017, 06:49
Пол: Не указан
Поблагодарили: 336 раз.

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

Сообщение Ben80 » 06 окт 2017, 18:19

Расход маны отрегулировал.

Код: Выделить всё
    // Если нажата кнопка OK (30722), то формируем поле disguise у героя,
    // иначе - возвращаем герою израсходованную ману ???
    if ( o_WndMgr->result_dlg_item_id == 30722 )
       c->edi = selNumber * 2 + 1;
   else
   {
      c->edi = -1;
      selNumber = 0;
   }

    return EXEC_DEFAULT;
}

int __stdcall cancelSpellCost(LoHook* h, HookContext* c)
{
   if(selNumber == 0)
      c->eax = 0;

    return EXEC_DEFAULT;
}

...

_PI->WriteLoHook(0x41C7DD, cancelSpellCost);
Вернуться к началу

offlineBen80  
имя: Сергей
Эксперт
Эксперт
 
Сообщения: 1318
Зарегистрирован: 18 июн 2017, 06:49
Пол: Не указан
Поблагодарили: 336 раз.

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

Сообщение Ben80 » 06 окт 2017, 18:54

С WAV проигрыванием пока не получается:

Код: Выделить всё
int __stdcall setPackedDisguise(LoHook* h, HookContext* c)
...
c->eax = 0x6854A0;
c->ecx = *(char*)(0x6854A0 + 0x224);
CALL_0(__int64, __fastcall, 0x59A770);

...
_PI->WriteHexPatch(0x41C7BA, "90 90 90 90 90");


Пишет ошибку с sfx ресурсом.
Вернуться к началу

offlineBen80  
имя: Сергей
Эксперт
Эксперт
 
Сообщения: 1318
Зарегистрирован: 18 июн 2017, 06:49
Пол: Не указан
Поблагодарили: 336 раз.

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

Сообщение Ben80 » 06 окт 2017, 21:03

Завершил написание кода, багов теперь нет, параметры (геометрию) диалога подобрал, опытным путем,
WAV файл проигрывается там, где нужно.

Оказывается, если не двигать ползунок, а нажать ОК, то никакого значения не выберется.
Пришлось задать значения по умолчанию, например
Код: Выделить всё
if(count == 2)
   {
       dlg->AddItem(_DlgStaticText_::Create((dlg->width - 30) / 2 + 1, dlg->height - 61, 72, 40,
          "Поменьше", "medfont.fnt", 1, 14, ALIGN_H_CENTER | ALIGN_V_CENTER, 0));
      dlg->AddItem(_DlgScroll_::Create(30, dlg->height - 90, dlg->width - 30 * 2 + 1, 16, 40, count,
        (_ptr_)scrollDlgCallback_1, 0, 0, 0));
      selNumber = 4;
   }


Весь последний код:
Код: Выделить всё
#include "..\..\include\homm3.h"

Patcher* _P;
PatcherInstance* _PI;

static _bool_ plugin_On = 0;

const int ghostHeroHookAddr[] = {0x41664C, 0x4DE583, 0x5D44A0, 0x5D451D};
_Hero_* ghostHero = new _Hero_();

// Выбранный селектор в диалоге (унифицированное значение, не равное выбранному на ползунке)
int selNumber;
bool cancelSpell = false;

// Создаём стандартный задник с рамкой цвета игрока
void setStdBackground(_Dlg_* dlg)
{
    CALL_5 (int, __thiscall, 0x48FA80, dlg, dlg->x, dlg->y, dlg->width, dlg->height);

    for (int i = dlg->field_4C; i <= dlg->field_50; ++i)
    {
        CALL_5 (int, __thiscall, 0x5FF400, dlg, 512, 13, i, o_GameMgr->GetMeID());   
    }
}

void __fastcall scrollDlgCallback_1(int click_id, _Dlg_* dlg)
{
    // Выводим в текстовое поле текущее значение, выбранное с помощью ползунка
    // click_id;
    // Уровень Магии воздуха определяет доступные для выбора значения

    if(click_id == 0)
        sprintf(o_TextBuffer, "%s", "Поменьше");
    if(click_id == 1)
        sprintf(o_TextBuffer, "%s", "Побольше");
    // селектор - преобразуем выбранный номер к унифицированной последовательности
    selNumber = click_id + 4;

    ((_DlgStaticText_*)dlg->GetItem(14))->SetText(o_TextBuffer);

    _DlgMsg_ m = {512, 3, 40, 0, 0, 0, 0, 0};
    CALL_2 (void, __thiscall, 0x5FF3A0, dlg, &m);   
    dlg->Redraw(TRUE);
}

void __fastcall scrollDlgCallback_2(int click_id, _Dlg_* dlg)
{
    // Выводим в текстовое поле текущее значение, выбранное с помощью ползунка
    // click_id;
    // Уровень Магии воздуха определяет доступные для выбора значения

    if(click_id == 0)
        sprintf(o_TextBuffer, "%s", "В 2 раза\nменьше");
    if(click_id == 1)
        sprintf(o_TextBuffer, "%s", "Поменьше");
    if(click_id == 2)
        sprintf(o_TextBuffer, "%s", "Побольше");
    if(click_id == 3)
        sprintf(o_TextBuffer, "%s", "В 2 раза\nбольше");
    // селектор - преобразуем выбранный номер к унифицированной последовательности
    selNumber = click_id + 3;

    ((_DlgStaticText_*)dlg->GetItem(14))->SetText(o_TextBuffer);

    _DlgMsg_ m = {512, 3, 40, 0, 0, 0, 0, 0};
    CALL_2 (void, __thiscall, 0x5FF3A0, dlg, &m);   
    dlg->Redraw(TRUE);
}

void __fastcall scrollDlgCallback_3(int click_id, _Dlg_* dlg)
{
    // Выводим в текстовое поле текущее значение, выбранное с помощью ползунка
    // click_id;
    // Уровень Магии воздуха определяет доступные для выбора значения

    if(click_id == 0)
        sprintf(o_TextBuffer, "%s", "В 5 раз\nменьше");
    if(click_id == 1)
        sprintf(o_TextBuffer, "%s", "В 3 раза\nменьше");
    if(click_id == 2)
        sprintf(o_TextBuffer, "%s", "В 2 раза\nменьше");
    if(click_id == 3)
        sprintf(o_TextBuffer, "%s", "Поменьше");
    if(click_id == 4)
        sprintf(o_TextBuffer, "%s", "Побольше");
    if(click_id == 5)
        sprintf(o_TextBuffer, "%s", "В 2 раза\nбольше");
    if(click_id == 6)
        sprintf(o_TextBuffer, "%s", "В 3 раза\nбольше");
    if(click_id == 7)
        sprintf(o_TextBuffer, "%s", "В 5 раз\nбольше");
    // селектор - преобразуем выбранный номер к унифицированной последовательности
    selNumber = click_id + 1;

    ((_DlgStaticText_*)dlg->GetItem(14))->SetText(o_TextBuffer);

    _DlgMsg_ m = {512, 3, 40, 0, 0, 0, 0, 0};
    CALL_2 (void, __thiscall, 0x5FF3A0, dlg, &m);   
    dlg->Redraw(TRUE);
}


void yDlgShow(int width, int height, int count, char* captionText, char* messageText, bool middle = true, int x = 0, int y = 0)
{
    // Создаём по центру экрана пустой диалог с тенью
    _Dlg_* dlg = _CustomDlg_::Create(DLG_X_CENTER, DLG_Y_CENTER, width, height, DF_SCREENSHOT | DF_SHADOW, NULL);
   
    // Добавляем задник (фон) и рамку по цвету игрока
    setStdBackground(dlg);
       
    // Добавляем заголовок диалога
    dlg->AddItem(_DlgStaticText_::Create(0, 20, dlg->width, 30,
        captionText, "bigfont.fnt", 7, 1, ALIGN_H_CENTER | ALIGN_V_TOP, 0));

    // Добавляем текст
    dlg->AddItem(_DlgStaticText_::Create(0, 50, dlg->width, 50,
        messageText, "medfont.fnt", 1, 0, ALIGN_H_CENTER | ALIGN_V_TOP, 0));

    // Добавляем полосу горизонтальной прокрутки
    //dlg->AddItem(_DlgScroll_::Create(30, dlg->height - 90, dlg->width - 30 * 2 + 1, 16, 40, count,
    //    (_ptr_)scrollDlgCallback, 0, 18, 0));
   
   
    // Добавляем текстовое поле для отображения значений, выбираемых с помощью ползунка
    if(count == 2)
    {
        dlg->AddItem(_DlgStaticText_::Create((dlg->width - 72) / 2 + 1, dlg->height - 65, 72, 40,
            "Поменьше", "medfont.fnt", 1, 14, ALIGN_H_CENTER | ALIGN_V_CENTER, 0));
        dlg->AddItem(_DlgScroll_::Create(30, dlg->height - 90, dlg->width - 30 * 2 + 1, 16, 40, count,
        (_ptr_)scrollDlgCallback_1, 0, 0, 0));
        selNumber = 4;
    }
    if(count == 4)
    {
        dlg->AddItem(_DlgStaticText_::Create((dlg->width - 72) / 2 + 1, dlg->height - 65, 72, 40,
            "В 2 раза\nменьше", "medfont.fnt", 1, 14, ALIGN_H_CENTER | ALIGN_V_CENTER, 0));
        dlg->AddItem(_DlgScroll_::Create(30, dlg->height - 90, dlg->width - 30 * 2 + 1, 16, 40, count,
        (_ptr_)scrollDlgCallback_2, 0, 0, 0));
        selNumber = 3;
    }
    if(count == 8)
    {
        dlg->AddItem(_DlgStaticText_::Create((dlg->width - 72) / 2 + 1, dlg->height - 65, 72, 40,
            "В 5 раз\nменьше", "medfont.fnt", 1, 14, ALIGN_H_CENTER | ALIGN_V_CENTER, 0));
        dlg->AddItem(_DlgScroll_::Create(30, dlg->height - 90, dlg->width - 30 * 2 + 1, 16, 40, count,
        (_ptr_)scrollDlgCallback_3, 0, 0, 0));
        selNumber = 1;
    }

    // Добавляем кнопку OK
    dlg->AddItem(_DlgStaticPcx8_::Create(30, dlg->height - 61, 0, "Box64x30.pcx"));
    dlg->AddItem(_DlgButton_::Create(31, dlg->height - 60, 64, 30, 30722, "iOkay.def", 0, 1, 1, 28, 2));

    // Добавляем кнопку Cancel
    dlg->AddItem(_DlgStaticPcx8_::Create(dlg->width - 31 - 64, dlg->height - 61, 0, "Box64x30.pcx"));
    dlg->AddItem(_DlgButton_::Create(dlg->width - 30 - 64, dlg->height - 60, 64, 30, 30721, "iCancel.def", 0, 1, 1, 1, 2));

    if ( !middle ) dlg->SetPos(x, y);
    dlg->Run();
    dlg->Destroy(TRUE);
}

float __stdcall getUnpackedMultiplier(int n)
{
    float selFloat[8] = {0.2, 0.333, 0.5, 0.666, 1.5, 2.0, 3.0, 5.0};
    return selFloat[n/2 - 1];
}

int __stdcall makeGhostHero(LoHook* h, HookContext* c)
{
    _Hero_* hero;
    int* heroReg;
    float multiplier = 1.0;

    h->GetAddress() == ghostHeroHookAddr[0] ? heroReg = &c->esi : heroReg = &c->eax;
    hero = *(_Hero_**)heroReg;
   
    if ( hero->disguise != -1)
    {
        // Копируем героя в "призрачного" героя
        *ghostHero = *hero;
       
        // Своих героев просматриваем в истинном виде (в оригинальной игре это не так)
        //if(hero->owner_id != o_GameMgr->GetMeID())
        multiplier = getUnpackedMultiplier((int)hero->disguise);

        ghostHero->disguise = -1;
     
        for (int i = 0; i < 7; ++i)
        {
            ghostHero->army.count[i] = ghostHero->army.count[i] * multiplier;
        }
     
        *(int*)heroReg = (int)ghostHero;
    }

    return EXEC_DEFAULT;
}


int __stdcall decPackedDisguise(LoHook* h, HookContext* c)
{
    for (int i = 0; i < o_HEROES_COUNT; ++i)
    {
        if ( o_GameMgr->GetHero(i)->owner_id == o_GameMgr->GetMeID() && o_GameMgr->GetHero(i)->disguise != -1 )
        {
            if(((int)(o_GameMgr->GetHero(i)->disguise)) % 2 == 0)
                o_GameMgr->GetHero(i)->disguise = -1;
            else
                --o_GameMgr->GetHero(i)->disguise;
        }
    }

    return EXEC_DEFAULT;
}

int __stdcall setPackedDisguise(LoHook* h, HookContext* c)
{
    // Получаем количество делений для ползунка исходя из уровня Магии воздуха
    int local_ecx = c->ecx;
    int local_eax = c->eax;
    cancelSpell = false;
    int arPos[4] = {2,2,4,8};
    int nPos = arPos[*(char*)(c->esi + 201 + 15)];
    // Если герой скастовал Disguise, показываем диалог
    //yDlgShow(240, 140, nPos, "Маскировка", "Выберите мнимую\nсилу отрядов");
    yDlgShow(300, 175, nPos, "Маскировка", "Выберите мнимую\nсилу отрядов");
   
    // Если нажата кнопка OK (30722), то формируем поле disguise у героя,
    // проигрываем WAV файл
    // иначе - возвращаем герою израсходованную ману
    if ( o_WndMgr->result_dlg_item_id == 30722 )
    {
        c->edi = selNumber * 2 + 1;
        c->eax = 0x6854A0;
        c->ecx = *(int*)(0x6856C4);
        CALL_0(__int64, __fastcall, 0x59A770);
        c->ecx = local_ecx;
        c->eax = local_eax;
    }
    else
    {
        c->edi = -1;
        cancelSpell = true;
    }
   

    return EXEC_DEFAULT;
}

int __stdcall cancelSpellCost(LoHook* h, HookContext* c)
{
    if(cancelSpell == true)
        c->eax = 0;

    return EXEC_DEFAULT;
}

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

         _PI->WriteHexPatch(0x4C7DA5, "90 90 90");
         _PI->WriteHexPatch(0x41C7BA, "90 90 90 90 90");
         _PI->WriteLoHook(0x4C75E0, decPackedDisguise);
         _PI->WriteLoHook(0x41C7C6, setPackedDisguise);
         _PI->WriteLoHook(0x41C7DD, cancelSpellCost);
         for (int i = 0; i < 4; ++i) _PI->WriteLoHook(ghostHeroHookAddr[i], makeGhostHero);
        }
    }

   return TRUE;
}

Последний раз редактировалось Ben80 07 окт 2017, 12:02, всего редактировалось 3 раз(а).
Вернуться к началу

offlineBen80  
имя: Сергей
Эксперт
Эксперт
 
Сообщения: 1318
Зарегистрирован: 18 июн 2017, 06:49
Пол: Не указан
Поблагодарили: 336 раз.

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

Сообщение Ben80 » 06 окт 2017, 21:07

Ben80 писал(а):

1 баг вроде уже нашел. Скорее всего в этом месте:
Код: Выделить всё
ghostHero->army.count[i] = ghostHero->army.count[i] * multiplier;


multiplier здесь float и компилятор предупреждал, что возможна потеря данных при прeобразовании от float к int
При опции "В 5 раз меньше" результат отличался в различных испытаниях.


Этой проблемы, по видимому нет, отличия в испытаниях объяснялись тем, что не было скроллинга (человек не скроллил) и, следовательно, выбранного значения.

Так что плагин готов полностью.
Последний раз редактировалось Ben80 07 окт 2017, 12:03, всего редактировалось 1 раз.
Вернуться к началу

offlineАватара пользователя
VDV_forever  
имя: Дмитрий
Администратор
 
Сообщения: 3862
Зарегистрирован: 22 мар 2009, 12:36
Пол: Мужчина
Поблагодарили: 986 раз.

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

Сообщение VDV_forever » 07 окт 2017, 04:34

Хорошая, познавательная тема! :smile20:
Помню интересовался модами на Герои 3. Когда увидел сколько их уже сделано, то был реально удивлен. :smile1: До сих пор 3-ка является самой популярной частью в геройском мире, тут уж как не крути :smile23: Интерес у людей не пропадает, даже стримы крутят, периодически :smile1:
http://www.handbookhmm.ru- Познай все тонкости игры!
Вернуться к началу

offlineBen80  
имя: Сергей
Эксперт
Эксперт
 
Сообщения: 1318
Зарегистрирован: 18 июн 2017, 06:49
Пол: Не указан
Поблагодарили: 336 раз.

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

Сообщение Ben80 » 07 окт 2017, 12:05

Готовая Dll.
NewDisguise.zip
(5.37 КБ) Скачиваний: 259


Есть идея сделать улучшение по сравнению с оригинальной игрой - учитывать Disguise
и в процедуре Dlg_TownSmallRMCInfo (или GetTownGuardsMonArr).
Вернуться к началу

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 » 07 окт 2017, 22:26

Написал на основе Вашего кода плагин для Disguise. Добавил для симметричности множитель 0. Герой без Магии Воздуха автоматически (без диалога) кастует Disguise с множителем 0.

Изображение

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

Patcher* _P;
PatcherInstance* _PI;

static _bool_ plugin_On = 0;

const int ghostHeroHookAddr[] = {0x41664C, 0x4DE583, 0x5D44A0, 0x5D451D};
_Hero_* ghostHero = new _Hero_();
_Hero_* curHero;

int nPos;

struct disguiseStruct
{
    char duration : 4;
    char index : 4;
    char unusedByte;
    short unusedWord;
} *disguise;

float getDisguiseMultiplier(int n)
{
    float multiplier[] = {0.0f, 1.5f, 2.0f, 3.0f, 5.0f};
    return n >= 0 ? multiplier[n] : 1.0f / multiplier[-n];
}

char* getArmyCountText(const _Hero_* hero, const int stackId, const int index = 0)
{
    char Text[10] = "";
   
    if ( hero->army.type[stackId] != ID_NONE )
    {
        int armyCount = (int)(getDisguiseMultiplier(index) * hero->army.count[stackId]);
       
        // Не палимся, если выбрали модификатор, отличный от нуля
        if ( index && armyCount < 1 ) armyCount = 1;
       
        armyCount < 10000 ? sprintf(Text, "%d", armyCount) : sprintf(Text, "%dk", armyCount / 1000);
    }
   
    return Text;
}

// Создаём стандартный задник с рамкой цвета игрока
void setStdBackground(_Dlg_* dlg)
{
    CALL_5(int, __thiscall, 0x48FA80, dlg, dlg->x, dlg->y, dlg->width, dlg->height);

    for (_ptr_ i = dlg->field_4C; i <= dlg->field_50; ++i)
    {
        CALL_5(int, __thiscall, 0x5FF400, dlg, 512, 13, i, o_GameMgr->GetMeID());   
    }
}

void __fastcall scrollDlgCallback(int click_id, _Dlg_* dlg)
{
    nPos = click_id - ((_DlgScroll_*)dlg->GetItem(40))->ticks_count / 2;
    float multiplier = getDisguiseMultiplier(nPos);
   
    char Text[10];
    sprintf(Text, "%d%s", (int)(multiplier * 100), "%");

    ((_DlgStaticText_*)dlg->GetItem(14))->SetText(Text);

    for (int i = 0; i < 7; ++i)
    {
        ((_DlgStaticText_*)dlg->GetItem(20 + i))->SetText(getArmyCountText(curHero, i, nPos));
    }

    // Устанавливаем ползунок в ближайшее к точке клика положение
    _DlgMsg_ m = {512, 3, 40, 0, 0, 0, 0, 0};
    CALL_2(void, __thiscall, 0x5FF3A0, dlg, &m);
    dlg->Redraw(TRUE);
}

void yDlgShow(int width, int height, int count, char* captionText, char* messageText, bool middle = true, int x = 0, int y = 0)
{
    // Создаём по центру экрана пустой диалог с тенью
    _Dlg_* dlg = _CustomDlg_::Create(DLG_X_CENTER, DLG_Y_CENTER, width, height, DF_SCREENSHOT | DF_SHADOW, NULL);
   
    // Добавляем задник (фон) и рамку по цвету игрока
    setStdBackground(dlg);
       
    // Добавляем заголовок диалога
    dlg->AddItem(_DlgStaticText_::Create(0, 24, dlg->width, 30,
        captionText, "bigfont.fnt", 7, 1, ALIGN_H_CENTER | ALIGN_V_TOP, 0));

    // Добавляем текст
    dlg->AddItem(_DlgStaticText_::Create(0, 54, dlg->width, 50,
        messageText, "medfont.fnt", 1, 0, ALIGN_H_CENTER | ALIGN_V_TOP, 0));

    // Добавляем иконки армии и численность
    for (int i = 0; i < 7; ++i)
    {
        int armyTypeId = ID_NONE;
        if ( curHero->army.type[i] > ID_NONE ) armyTypeId = curHero->army.type[i] + 2;

        int leftX = (dlg->width - 236) / 2;
        // Добавляем тёмный фон для иконок
        dlg->AddItem(_DlgStaticDef_::Create(leftX + i * 34, 110, 0,
            "cprsmall.def", 0, 0, 0));
       
        // Добавляем иконки армии
        if ( armyTypeId != ID_NONE )
        {
            dlg->AddItem(_DlgStaticDef_::Create(leftX + i * 34, 110, 30 + i,
                "cprsmall.def", armyTypeId, 0, 0));
        }
       
        // Добавляем рамку для иконок
        dlg->AddItem(_DlgStaticDef_::Create(leftX + i * 34, 110, 0, "cprsmall.def", 1, 0, 0));
       
        // Добавляем надписи с численностью отрядов
        dlg->AddItem(_DlgStaticText_::Create(leftX + i * 34, 144, 32, 16,
            getArmyCountText(curHero, i), "tiny.fnt", 1, 20 + i, ALIGN_H_CENTER | ALIGN_V_TOP, 0));
    }

    // Добавляем полосу горизонтальной прокрутки и устанавливаем ползунок по центру
    _DlgScroll_* disguiseScroll = _DlgScroll_::Create(30, dlg->height - 90, dlg->width - 30 * 2 + 1, 16, 40, count,
        (_ptr_)scrollDlgCallback, 0, 0, 0);
    disguiseScroll->tick = count / 2;
    disguiseScroll->tick_value = count / 2;
    disguiseScroll->btn_position = (disguiseScroll->width - disguiseScroll->btn_size2) / 2;
    dlg->AddItem(disguiseScroll);
   
    // Добавляем текстовое поле для отображения значений, выбираемых с помощью ползунка
    dlg->AddItem(_DlgStaticText_::Create((dlg->width - 72) / 2 + 1, dlg->height - 65, 72, 40,
        "0%", "medfont.fnt", 1, 14, ALIGN_H_CENTER | ALIGN_V_CENTER, 0));

    // Добавляем кнопку OK
    dlg->AddItem(_DlgStaticPcx8_::Create(30, dlg->height - 61, 0, "Box64x30.pcx"));
    dlg->AddItem(_DlgButton_::Create(31, dlg->height - 60, 64, 30, 30722, "iOkay.def", 0, 1, 1, 28, 2));

    // Добавляем кнопку Cancel
    dlg->AddItem(_DlgStaticPcx8_::Create(dlg->width - 31 - 64, dlg->height - 61, 0, "Box64x30.pcx"));
    dlg->AddItem(_DlgButton_::Create(dlg->width - 30 - 64, dlg->height - 60, 64, 30, 30721, "iCancel.def", 0, 1, 1, 1, 2));

    if ( !middle ) dlg->SetPos(x, y);
    dlg->Run();
    dlg->Destroy(TRUE);
}

int __stdcall makeGhostHero(LoHook* h, HookContext* c)
{
    _Hero_* hero;
    int* heroReg;

    h->GetAddress() == ghostHeroHookAddr[0] ? heroReg = &c->esi : heroReg = &c->eax;
    hero = *(_Hero_**)heroReg;
    disguise = (disguiseStruct*)&hero->disguise;

    if ( disguise->duration != -1 )
    {
        // Копируем героя в "призрачного" героя
        *ghostHero = *hero;
        ghostHero->disguise = -1;

        for (int i = 0; i < 7; ++i)
        {
            if ( hero->army.type[i] != ID_NONE )
            {
                ghostHero->army.count[i] = (int)(getDisguiseMultiplier(disguise->index) * ghostHero->army.count[i]);

                // Не палимся, если выбрали модификатор, отличный от нуля
                if ( disguise->index && ghostHero->army.count[i] < 1 ) ghostHero->army.count[i] = 1;
            }
        }
     
        *(int*)heroReg = (int)ghostHero;
    }

    return EXEC_DEFAULT;
}


int __stdcall decDisguise(LoHook* h, HookContext* c)
{
    for (int i = 0; i < o_HEROES_COUNT; ++i)
    {
        if ( o_ActivePlayer->IsHuman() && o_GameMgr->GetHero(i)->owner_id == o_GameMgr->GetMeID() )
        {
            disguise = (disguiseStruct*)&o_GameMgr->GetHero(i)->disguise;
            if (  disguise->duration != -1 )
            {
                if ( --disguise->duration == -1 ) o_GameMgr->GetHero(i)->disguise = -1;
            }
        }
    }

    return EXEC_DEFAULT;
}

int __stdcall setDisguise(LoHook* h, HookContext* c)
{
    nPos = 0;
    bool disguiseCast = true;
    _Hero_* hero = (_Hero_*)c->esi;
   
    if ( hero->second_skill[HSS_AIR_MAGIC] )
    {
        curHero = hero;

        // Если у героя есть Магия Воздуха, показываем диалог с горизонтальной полосой прокрутки
        // на 3/5/9 значений для Базового/Продвинутого/Экспертного навыка
        char Text[100];
        sprintf(Text, "{%s}, select your army\nstrength modifier.", hero->name);
        yDlgShow(320, 256, (1 << hero->second_skill[HSS_AIR_MAGIC]) + 1, "{Disguise}", Text);
        disguiseCast = o_WndMgr->result_dlg_item_id == 30722;
    }

    // Индекс множителя маскировки помещаем в старший полубайт,
    // длительность "Маскировки" (2 хода) - в младший
    int spellCost = c->eax;
    if ( disguiseCast )
    {
        disguise = (disguiseStruct*)&hero->disguise;
        disguise->index = nPos;
        disguise->duration = 1;
    } else spellCost = 0; // Если заклинание отменено, устанавливаем расход маны в 0

    // Расходуем ману
    CALL_2(int, __thiscall, 0x4D9540, hero, spellCost);
   
    c->return_address = 0x41C7E5;
    return NO_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.TestDisguise");

            _PI->WriteCodePatch(0x4C7DA5, "%n", 3);
            _PI->WriteCodePatch(0x41C7C6, "%n", 6);
            _PI->WriteLoHook(0x41C7DD, setDisguise);
            _PI->WriteLoHook(0x4C75E0, decDisguise);
            for (int i = 0; i < 4; ++i) _PI->WriteLoHook(ghostHeroHookAddr[i], makeGhostHero);
        }
    }

    return TRUE;
}

Проверка o_ActivePlayer->IsHuman() нужна, чтобы во время хода компов не уменьшалась длительность Disguise. Дело в том, что во время их хода GetMeID() == ID последнего походившего игрока.

В сейв пишется только младший байт поля Disguise, поэтому паковать нужно в него. Я запаковываю индекс множителя в страший полубайт, а длительность - в младший. Этот способ налагает ограничения на максимальное кол-во положений ползунка (максимум 15 от -7 до 7) и на максимальную длительность Disguise - до 9 ходов (от 0 до 8).

* * *
Пока работает только в сингле и хотсите. В сетевой игре длительность не уменьшается. Буду смотреть позже, в чём дело.

* * *
Закомментировал условие в decDisguise. Всё равно в сетевой игре длительность не уменьшается. Видимо, нужно искать другое место для хука или для сетевой игры работает другая функция, нежели чем для сингла/хотсита.
Последний раз редактировалось AlexSpl 08 окт 2017, 09:47, всего редактировалось 1 раз.
Вернуться к началу

offlineBen80  
имя: Сергей
Эксперт
Эксперт
 
Сообщения: 1318
Зарегистрирован: 18 июн 2017, 06:49
Пол: Не указан
Поблагодарили: 336 раз.

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

Сообщение Ben80 » 08 окт 2017, 04:59

AlexSpl писал(а):

Герой без Магии Воздуха автоматически (без диалога) кастует Disguise с множителем 0.


У героя без Магии Воздуха должны быть такие же возможности, как и у героя с Магией воздуха 1 ступени. Поскольку в оригинальной игре эффект заклинаний при отсутствии Школы магии и Школы на 1 ступени - одинаков.

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

Пред.След.

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

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

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

cron