Объявления
Поздравляем
Roman2211


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

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

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

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

Сообщение Rolex » 24 янв 2021, 23:30

AlexSpl писал(а):

Насчёт однозначного определения собственного номера игрока. Посмотрел код, который показывает хинты по правому клику игрокам. Думаю, правильно использовать o_GameMgr->GetMeID() вместо o_ActivePlayerID (или o_MeID). Если верить коду, эта функция всегда возвращает именно номер игрока (т.е. наш), играем ли мы синглплеер, хотсит или по сети. o_ActivePlayerID нужно использовать, если мы хотим получить номер игрока, который ходит в данный момент. o_MeID я бы вообще не использовал из-за нюансов в хотсите.

В ХотСите с исправлением showGuards один раз словил даже вылет при посещении Склепа. Вместо начала боя получил вылет с ошибкой. Но больше, вроде, не было. ХЗ с чем связано.

AlexSpl, а подскажите, пжл, как все же убрать диалог обыска (Хотите обыскать могилы? И кнопки Да и Нет) при посещении Склепа и Кораблей, когда они уже разграблены (это с обновленной showGuards, которая устанавливает флаг посещения банков). Потому что вот такая штука получается:
Rolex писал(а):

В нашем же случае получется, что когда Склеп не взят выводится диалог "Хотите обыскать могилы?" и охрана, а когда уже взят, то только диалог, но уже без охраны. Соответственно игроки будут тупо отказыватся, так как будут понимать, что Склеп уже пуст. Поэтому надо бы этот код с обновленной showGuards как-то допилить для Склепа и Кораблей, сделав так, чтобы при посещении игроком уже разграбленного Склепа не выводился диалог без охраны с возможностью отказаться от обыска, а сразу же как в Хота прилетал сюрприз в виде -1 Морали. Как будто бы посещая игрок заведомо дает свое согласие на обыск.
Вернуться к началу

offlineigrik  
Подмастерье
Подмастерье
 
Сообщения: 108
Зарегистрирован: 14 сен 2017, 12:35
Пол: Не указан
Поблагодарили: 84 раз.

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

Сообщение igrik » 25 янв 2021, 17:40

AlexSpl писал(а):

Например, сижу-туплю, не знаю, как получить _Dwelling_* по _MapItem_* :smile14:


Код: Выделить всё
_Dwelling_* GetDwelling(_int_ dwellingId) {
    return ((_Dwelling_ *)(DwordAt((_ptr_)o_GameMgr + 0x4E39C) + 92 * dwellingId));
}


if(mapItem->object_type == 17 || mapItem->object_type == 20)
   _Dwelling_* dwelling = GetDwelling(mapItem->setup);
Вернуться к началу

offlineigrik  
Подмастерье
Подмастерье
 
Сообщения: 108
Зарегистрирован: 14 сен 2017, 12:35
Пол: Не указан
Поблагодарили: 84 раз.

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

Сообщение igrik » 25 янв 2021, 18:04

Rolex писал(а):

подскажите, пжл, как все же убрать диалог обыска (Хотите обыскать могилы? И кнопки Да и Нет) при посещении Склепа и Кораблей, когда они уже разграблены (это с обновленной showGuards, которая устанавливает флаг посещения банков).


Код: Выделить всё
int __stdcall Crypt_SkipMsgIfVisited(LoHook* h, HookContext* c)
{
    if( DwordAt(DwordAt(c->ebp +0xC)) & 0x2000000 )
    {
        c->return_address = 0x4AC1E5;
        return NO_EXEC_DEFAULT;
    }

   return EXEC_DEFAULT;

...

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

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

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

Сообщение Rolex » 25 янв 2021, 20:50

Ну теперь вроде все работает так, как в HotA. Правда не знаю насколько все стабильно будет по сети, не будет ли вылетов. Тестил в сингле и хотсите, там вроде все ок.

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

using namespace std;

Patcher* _P;
PatcherInstance* _PI;
static _bool_ plugin_On = 0;

string getInterval(int n)
{
   string str;
   if (n < 5) str = "1-4"; else
      if (n < 10) str = "5-9"; else
         if (n < 20) str = "10-19"; else
            if (n < 50) str = "20-49"; else
               if (n < 100) str = "50-99"; else
                  if (n < 250) str = "100-249"; else
                     if (n < 500) str = "250-499"; else
                        if (n < 1000) str = "500-999"; else str = "1000+";
   return str;
}

#define o_CreatureInfo ((_CreatureInfo_*)0x6703B8)

int __stdcall showGuards(LoHook* h, HookContext* c)
{
   // Получаем состояние банка
   _MapItem_* mapItem = (_MapItem_*)*(int*)(c->ebp + 0xC);
   _CrBankState_* bankState = (_CrBankState_*)CALL_1(int, __fastcall, 0x405D80, mapItem);

   string str = (char*)c->ecx; // Оригинальное сообщение
   str += "\n";

   for (int i = 0; i < bankState->defenders.GetStacksCount(); ++i)
      str = str + "\n" + o_CreatureInfo[bankState->defenders.type[i]].name_plural + ": " +
      getInterval(bankState->defenders.count[i]);

   // Передаём адрес текстового буфера в качестве аргумента для диалога
   sprintf(o_TextBuffer, "%s", str.c_str());
   c->ecx = (int)o_TextBuffer;

   mapItem->SetAsVisited(o_ActivePlayerID);

   return EXEC_DEFAULT;
}

_MapItem_* mapItem;

int __stdcall saveObjEntranceAddr(LoHook* h, HookContext* c)
{
   mapItem = (_MapItem_*)c->eax;

   return EXEC_DEFAULT;
}

int __stdcall showGuardsRMB(LoHook* h, HookContext* c)
{
   if (mapItem && mapItem->IsVisited(o_MeID))
   {
      // mapItem->object_type = 0x10 - стандартные банки, 0x18 - Derelict Ship, 0x19 - Dragon Utopia, 0x54 - Crypt, 0x55 - Shipwreck
      int objID = mapItem->object_type;
      if (mapItem->object_type == 0x10 || mapItem->object_type == 0x18 || mapItem->object_type == 0x19 ||
         mapItem->object_type == 0x54 || mapItem->object_type == 0x55)
      {
         // Получаем состояние банка
         _CrBankState_* bankState = (_CrBankState_*)CALL_1(int, __fastcall, 0x405D80, mapItem);

         string str = (char*)c->ecx; // Оригинальное сообщение
         str += "\n";

         for (int i = 0; i < bankState->defenders.GetStacksCount(); ++i)
            str = str + "\n" + o_CreatureInfo[bankState->defenders.type[i]].name_plural + ": " +
            getInterval(bankState->defenders.count[i]);

         // Передаём адрес текстового буфера в качестве аргумента для диалога
         sprintf(o_TextBuffer, "%s", str.c_str());
         c->ecx = (int)o_TextBuffer;
      }
   }

   return EXEC_DEFAULT;
}

int __stdcall Crypt_SkipMsgIfVisited(LoHook* h, HookContext* c)
{
   if (DwordAt(DwordAt(c->ebp + 0xC)) & 0x2000000)
   {
      c->return_address = 0x4AC1E5;
      return NO_EXEC_DEFAULT;
   }

   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((char*)"HD.Plugin.ShowGuards");

         // Сообщения при посещении
         _PI->WriteLoHook(0x4A13E6, showGuards); // стандартных банков
         _PI->WriteLoHook(0x4A1E56, showGuards); // Dragon Utopia

         _PI->WriteLoHook(0x4AC19D, Crypt_SkipMsgIfVisited);

         _PI->WriteLoHook(0x4AC1B9, showGuards); // Crypt, Derelict Ship, Shipwreck

                                       // Сохраняем адрес тайла-входа в объект
         _PI->WriteLoHook(0x413917, saveObjEntranceAddr);

         // Сообщения по правому клику
         _PI->WriteLoHook(0x415AEE, showGuardsRMB);
      }
   }

   return TRUE;
}


AlexSpl писал(а):

Хук на сообщение по правому клику - универсальный, в нём можно обработать любой объект. А вот на посещение будет два или три дополнительных хука. 1) Для жилищ 1-го уровня, 2) для жилищ, где можно нанять один тип существ за ресурсы, и (возможно) 3) для жилищ, где можно нанимать больше одного типа существ.

Не совсем понятно, а зачем нужны хуки на посещения жилищ (кроме 1-го уровня). Что-то не вспомню, что это за жилища и существа, которые покупаються только за ресурсы. И что-то не могу вспомнить, где в оригинальной тройке есть жилища, где можно нанимать больше одного типа существ. Хотя... может только Фабрика големов.

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

Ну и один универсальный хук по правому клику для всех жилищ 1-7 уровня (замковых и нейтральных) и для Лагеря беженцев, где после его посещения или захвата (для жилищ 5-7 уровня) по правому клику по жилищу будет в конце всплывающего диалога дописываться:

Доступные существа:
Название существ обитающих в данном жилище - доступное количество для найма на данный момент.

И тут вопрос: как доработать код выше, чтобы это отображалось? Там вроде есть хук на сообщение по правому клику:

Код: Выделить всё
_PI->WriteLoHook(0x415AEE, showGuardsRMB);


Я так понимаю, что нужно немного доработать код showGuardsRMB.
Вернуться к началу

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 » 26 янв 2021, 18:10

С кодом igrik'а показ существ в жилищах будет следующим (но не проверял те, в которых можно нанять больше одного типа существ):

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

using namespace std;

Patcher* _P;
PatcherInstance* _PI;
static _bool_ plugin_On = 0;

#define o_CreatureInfo ((_CreatureInfo_*)0x6703B8)

string getInterval(int n)
{
    string str;
    if ( n < 1 ) str = "0"; else
    if ( n < 5 ) str = "1-4"; else
    if ( n < 10 ) str = "5-9"; else
    if ( n < 20 ) str = "10-19"; else
    if ( n < 50 ) str = "20-49"; else
    if ( n < 100 ) str = "50-99"; else
    if ( n < 250 ) str = "100-249"; else
    if ( n < 500 ) str = "250-499"; else
    if ( n < 1000 ) str = "500-999"; else str = "1000+";
    return str;
}

_MapItem_* mapItem;
int __stdcall saveObjEntranceAddr(LoHook* h, HookContext* c)
{
    mapItem = (_MapItem_*)c->eax;
   
    return EXEC_DEFAULT;
}

// Получаем указатель на _Dwelling_ по ID жилища существ
_Dwelling_* GetDwelling(_int_ dwellingId) {
    return (_Dwelling_ *)(o_GameMgr->Field<int>(0x4E39C) + 92 * dwellingId);
}

int __stdcall Crypt_SkipMsgIfVisited(LoHook* h, HookContext* c)
{
   if ( *(int*)*(int*)(c->ebp + 0xC) & 0x2000000 )
   {
      c->return_address = 0x4AC1E5;
      return NO_EXEC_DEFAULT;
   }

   return EXEC_DEFAULT;
}

int __stdcall showGuards(LoHook* h, HookContext* c)
{
    // Получаем состояние банка
    _MapItem_* mapItem = (_MapItem_*)*(int*)(c->ebp + 0xC);
   
    if ( mapItem ) // Перестраховываемся, но можно и без этой проверки
    {
        _CrBankState_* bankState = CALL_1(_CrBankState_*, __fastcall, 0x405D80, mapItem);
       
        string str = (char*)c->ecx; // Оригинальное сообщение
        str += "\n";
           
        for (int i = 0; i < bankState->defenders.GetStacksCount(); ++i)
            str = str + "\n" + o_CreatureInfo[bankState->defenders.type[i]].name_plural + ": " +
            getInterval(bankState->defenders.count[i]);
               
        // Передаём адрес текстового буфера в качестве аргумента для диалога
        sprintf(o_TextBuffer, "%s", str.c_str());
        c->ecx = (int)o_TextBuffer;

        mapItem->SetAsVisited(o_GameMgr->GetMeID());
    }
   
    return EXEC_DEFAULT;
}

int __stdcall showMonsters(LoHook* h, HookContext* c)
{
    // Получаем состояние жилища
    _MapItem_* mapItem = (_MapItem_*)*(int*)(c->ebp + 0xC);

    if ( mapItem ) // Перестраховываемся, но можно и без этой проверки
    {
        string str = (char*)c->ecx; // Оригинальное сообщение
        str += "\n";

        // Получаем состояние жилища существ
        _Dwelling_* dwelling = GetDwelling(mapItem->setup);

        // Если мы владельцы, показываем количество существ
        if ( dwelling && dwelling->owner_ix == o_GameMgr->GetMeID() )
        {
            string str = (char*)c->ecx; // Оригинальное сообщение
            str += "\n";

            for (int i = 0; i < 4; ++i)
                if ( dwelling->creature_types[i] != ID_NONE )
                    str = str + "\n" + o_CreatureInfo[dwelling->creature_types[i]].name_plural + ": " +
                    getInterval(dwelling->creature_counts[i]);

            // Передаём адрес текстового буфера в качестве аргумента для диалога
            sprintf(o_TextBuffer, "%s", str.c_str());
            c->ecx = (int)o_TextBuffer;
        }
    }
   
    return EXEC_DEFAULT;
}

int __stdcall showRefugeeCamp(LoHook* h, HookContext* c)
{
    // Получаем состояние жилища
    _MapItem_* mapItem = (_MapItem_*)*(int*)(c->ebp + 0xC);

    if ( mapItem ) // Перестраховываемся, но можно и без этой проверки
    {
        // Передаём адрес текстового буфера в качестве аргумента для диалога
        sprintf(o_TextBuffer, "%s\n\n%s: %s", (char*)c->ecx, o_CreatureInfo[mapItem->os_type].name_plural, getInterval(mapItem->setup).c_str());
        c->ecx = (int)o_TextBuffer;
    }
   
    return EXEC_DEFAULT;
}

int __stdcall showGuardsRMB(LoHook* h, HookContext* c)
{
    if ( mapItem )
    {
        // Банки существ
        if ( mapItem->IsVisited(o_GameMgr->GetMeID()) )
        {
            // mapItem->object_type = 0x10 - стандартные банки, 0x18 - Derelict Ship, 0x19 - Dragon Utopia, 0x54 - Crypt, 0x55 - Shipwreck
            if ( mapItem->object_type == 0x10 || mapItem->object_type == 0x18 || mapItem->object_type == 0x19 ||
                 mapItem->object_type == 0x54 || mapItem->object_type == 0x55 )
            {
                // Получаем состояние банка
                _CrBankState_* bankState = CALL_1(_CrBankState_*, __fastcall, 0x405D80, mapItem);

                string str = (char*)c->ecx; // Оригинальное сообщение
                str += "\n";

                for (int i = 0; i < bankState->defenders.GetStacksCount(); ++i)
                    str = str + "\n" + o_CreatureInfo[bankState->defenders.type[i]].name_plural + ": " +
                    getInterval(bankState->defenders.count[i]);
                                     
                // Передаём адрес текстового буфера в качестве аргумента для диалога
                sprintf(o_TextBuffer, "%s", str.c_str());
                c->ecx = (int)o_TextBuffer;
            }
        }

        // Жилища существ
        if ( mapItem->object_type == 0x11 || mapItem->object_type == 0x14 )
        {
            // Получаем состояние жилища существ
            _Dwelling_* dwelling = GetDwelling(mapItem->setup);

            // Если мы владельцы, показываем количество существ
            if ( dwelling && dwelling->owner_ix == o_GameMgr->GetMeID() )
            {
                string str = (char*)c->ecx; // Оригинальное сообщение
                str += "\n";

                for (int i = 0; i < 4; ++i)
                    if ( dwelling->creature_types[i] != ID_NONE )
                        str = str + "\n" + o_CreatureInfo[dwelling->creature_types[i]].name_plural + ": " +
                        getInterval(dwelling->creature_counts[i]);

                // Передаём адрес текстового буфера в качестве аргумента для диалога
                sprintf(o_TextBuffer, "%s", str.c_str());
                c->ecx = (int)o_TextBuffer;
            }
        }

        // Лагерь беженцев
        if ( mapItem->object_type == 0x4E )
        {
            // Передаём адрес текстового буфера в качестве аргумента для диалога
            sprintf(o_TextBuffer, "%s\n\n%s: %s", (char*)c->ecx, o_CreatureInfo[mapItem->os_type].name_plural, getInterval(mapItem->setup).c_str());
            c->ecx = (int)o_TextBuffer;
        }
    }
   
    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((char*)"HD.Plugin.ShowGuards");

            // Сообщения по правому клику
            _PI->WriteLoHook(0x415AEE, showGuardsRMB);

            // Сообщения при посещении
            _PI->WriteLoHook(0x4A13E6, showGuards); // стандартных банков
            _PI->WriteLoHook(0x4A1E56, showGuards); // Dragon Utopia
            _PI->WriteLoHook(0x4AC1B9, showGuards); // Crypt, Derelict Ship, Shipwreck
            _PI->WriteLoHook(0x4A18FF, showMonsters); // жилища существ 1-го уровня
            _PI->WriteLoHook(0x4A435C, showRefugeeCamp); // Refugee Camp

            // Сохраняем адрес тайла-входа в объект
            _PI->WriteLoHook(0x413917, saveObjEntranceAddr);

            // Пропускаем сообщения для Склепа, если он посещён
            _PI->WriteLoHook(0x4AC19D, Crypt_SkipMsgIfVisited);
        }
    }

    return TRUE;
}

UPD: Добавил проверку на посещение Склепа (код igrik'а выше) и Refugee Camp, но с Refugee Camp есть проблема: у них нет владельца и их нельзя отметить как посещённые, поэтому нужно либо самостоятельно отмечать владельца в неиспользуемых полях структуры (что всегда опасно), либо заводить свой массив или список владельцев Refugee Camp. Может, есть более простое решение.

* * *
Кстати, заметил очень странную вещь, которую пока не могу объяснить. Для Вампиров (Vampires) текст с количеством при посещении Refugee Camp не показывается (SoD 3.2 EN). Пока списываю это на то, как игра выбирает размер диалога в зависимости от размера текста. Кому интересно, проверьте сами :smile1:

Изображение Изображение

И такая проблема почему-то только с Вампирами :smile5: Даже более короткая строка с Boars не переносится. Ещё есть вариант, что "Vampires" на самом деле "\nVampires", но это вряд ли.
Вернуться к началу

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

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

Сообщение Rolex » 27 янв 2021, 00:08

AlexSpl писал(а):

Добавил проверку на посещение Склепа

Да, я тоже его добавил еще в коде ранее. Все работает. Правда заметил, что Вы в своем коде DwordAt заменили на *(int*). Какой в этом смысл?

В общем немного причесал ваш код:
1. В банках существ после посещения и отказа, по правому клику нет необходимости выводить дополнительно кол-во по отрядам, ибо в оригинале там вывод кол-ва идет итак. Нет смысла дублировать. Так что этот кусок кода я закоментил. То есть вывод кол-ва нужен был именно во время посещения банка в диалоге.
2. Кол-во существ в жилищах как раз нужно точное в отличии от банков существ, а потому пришлось убрать обращение к функции getInterval, то есть getInterval(dwelling->creature_counts[i]) заменить на to_string(dwelling->creature_counts[i]).
3. Сам вывод немного изменил и приукрасил для удобства восприятия и выделил доступное кол-во.
4. Проверил на Фабрике големов, это, вероятно, единственное жилище, где можно нанять больше одного типа существ. Все ОК. :smile2:

Осталось разобраться с Refugee Camp:
По поводу Refugee Camp (Лагерь беженцев), вот здесь по ПКМ даже до посещения отображается тип и кол-во доступных существ для найма. А нужно, чтобы это становилось доступно только после посещения.

AlexSpl писал(а):

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

То, что опасно, лучше всегда обходить стороной, а то потом пойдут вылеты и различного рода проблемы. Я думаю второй вариант будет предпочтительней, каждого игрока (или может только игрока-человека) посетившего Refugee Camp кладем в массив и делаем для него после посещения в диалоге по ПКМ доступным тип и кол-во существ доступных для найма.

AlexSpl писал(а):

Кстати, заметил очень странную вещь, которую пока не могу объяснить. Для Вампиров (Vampires) текст с количеством при посещении Refugee Camp не показывается (SoD 3.2 EN). Пока списываю это на то, как игра выбирает размер диалога в зависимости от размера текста.

Надо бы еще проверить на русской версии с русскими шрифтами.

У меня все ОК. :smile1:
Изображение
Это не HotA, а наш плагин. Просто я вывод сделал как в HotA. Но я тестирую как плагин к HD-моду, а мод все эти диалоги в жилищах "Хотите нанять таких-то существ?" для удобства и ускорения процесса игры убирает.

А каким образом вы без мода подцепили этот плагин к оригиналу, как ASI-плагин?

Я так понимаю HotA вы вообще не запускаете/не играете?

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

using namespace std;

Patcher* _P;
PatcherInstance* _PI;
static _bool_ plugin_On = 0;

#define o_CreatureInfo ((_CreatureInfo_*)0x6703B8)

string getInterval(int n)
{
   string str;
   if (n < 1) str = "0"; else
      if (n < 5) str = "1-4"; else
         if (n < 10) str = "5-9"; else
            if (n < 20) str = "10-19"; else
               if (n < 50) str = "20-49"; else
                  if (n < 100) str = "50-99"; else
                     if (n < 250) str = "100-249"; else
                        if (n < 500) str = "250-499"; else
                           if (n < 1000) str = "500-999"; else str = "1000+";
   return str;
}

_MapItem_* mapItem;
int __stdcall saveObjEntranceAddr(LoHook* h, HookContext* c)
{
   mapItem = (_MapItem_*)c->eax;

   return EXEC_DEFAULT;
}

// Получаем указатель на _Dwelling_ по ID жилища существ
_Dwelling_* GetDwelling(_int_ dwellingId) {
   return (_Dwelling_ *)(o_GameMgr->Field<int>(0x4E39C) + 92 * dwellingId);
}

int __stdcall Crypt_SkipMsgIfVisited(LoHook* h, HookContext* c)
{
   if (*(int*)*(int*)(c->ebp + 0xC) & 0x2000000)
   {
      c->return_address = 0x4AC1E5;
      return NO_EXEC_DEFAULT;
   }

   return EXEC_DEFAULT;
}

int __stdcall showGuards(LoHook* h, HookContext* c)
{
   // Получаем состояние банка
   _MapItem_* mapItem = (_MapItem_*)*(int*)(c->ebp + 0xC);

   if (mapItem) // Перестраховываемся, но можно и без этой проверки
   {
      _CrBankState_* bankState = CALL_1(_CrBankState_*, __fastcall, 0x405D80, mapItem);

      string str = (char*)c->ecx; // Оригинальное сообщение
      str += "\n";

      for (int i = 0; i < bankState->defenders.GetStacksCount(); ++i)
         str = str + "\n" + o_CreatureInfo[bankState->defenders.type[i]].name_plural + ": " +
         getInterval(bankState->defenders.count[i]);

      // Передаём адрес текстового буфера в качестве аргумента для диалога
      sprintf(o_TextBuffer, "%s", str.c_str());
      c->ecx = (int)o_TextBuffer;

      mapItem->SetAsVisited(o_GameMgr->GetMeID());
   }

   return EXEC_DEFAULT;
}

int __stdcall showMonsters(LoHook* h, HookContext* c)
{
   // Получаем состояние жилища
   _MapItem_* mapItem = (_MapItem_*)*(int*)(c->ebp + 0xC);

   if (mapItem) // Перестраховываемся, но можно и без этой проверки
   {
      string str = (char*)c->ecx; // Оригинальное сообщение
      str += "\n";

      // Получаем состояние жилища существ
      _Dwelling_* dwelling = GetDwelling(mapItem->setup);

      // Если мы владельцы, показываем количество существ
      if (dwelling && dwelling->owner_ix == o_GameMgr->GetMeID())
      {
         string str = (char*)c->ecx; // Оригинальное сообщение
         str += "\n\n";

         str += "Доступные существа:";

         for (int i = 0; i < 4; ++i)
            if (dwelling->creature_types[i] != ID_NONE)
               str = str + "\n" + o_CreatureInfo[dwelling->creature_types[i]].name_plural + " — " + "{" +
               to_string(dwelling->creature_counts[i]) + "}";

         // Передаём адрес текстового буфера в качестве аргумента для диалога
         sprintf(o_TextBuffer, "%s", str.c_str());
         c->ecx = (int)o_TextBuffer;
      }
   }

   return EXEC_DEFAULT;
}

int __stdcall showRefugeeCamp(LoHook* h, HookContext* c)
{
   // Получаем состояние жилища
   _MapItem_* mapItem = (_MapItem_*)*(int*)(c->ebp + 0xC);

   if (mapItem) // Перестраховываемся, но можно и без этой проверки
   {
      string str = (char*)c->ecx; // Оригинальное сообщение

                           // Передаём адрес текстового буфера в качестве аргумента для диалога
      sprintf(o_TextBuffer, "%s\n\nДоступные существа:\n%s — {%d}", str.c_str(), o_CreatureInfo[mapItem->os_type].name_plural, mapItem->setup);
      c->ecx = (int)o_TextBuffer;
   }

   return EXEC_DEFAULT;
}

int __stdcall showGuardsRMB(LoHook* h, HookContext* c)
{
   if (mapItem)
   {
   /*   // Банки существ
      if (mapItem->IsVisited(o_GameMgr->GetMeID()))
      {
         // mapItem->object_type = 0x10 - стандартные банки, 0x18 - Derelict Ship, 0x19 - Dragon Utopia, 0x54 - Crypt, 0x55 - Shipwreck
         int objID = mapItem->object_type;

         if (mapItem->object_type == 0x10 || mapItem->object_type == 0x18 || mapItem->object_type == 0x19 ||
            mapItem->object_type == 0x54 || mapItem->object_type == 0x55)
         {
            // Получаем состояние банка
            _CrBankState_* bankState = CALL_1(_CrBankState_*, __fastcall, 0x405D80, mapItem);

            string str = (char*)c->ecx; // Оригинальное сообщение
            str += "\n";

            for (int i = 0; i < bankState->defenders.GetStacksCount(); ++i)
               str = str + "\n" + o_CreatureInfo[bankState->defenders.type[i]].name_plural + ": " +
               getInterval(bankState->defenders.count[i]);

            // Передаём адрес текстового буфера в качестве аргумента для диалога
            sprintf(o_TextBuffer, "%s", str.c_str());
            c->ecx = (int)o_TextBuffer;
         }
      }
   */
      // Жилища существ
      if (mapItem->object_type == 0x11 || mapItem->object_type == 0x14)
      {
         // Получаем состояние жилища существ
         _Dwelling_* dwelling = GetDwelling(mapItem->setup);

         // Если мы владельцы, показываем количество существ
         if (dwelling && dwelling->owner_ix == o_GameMgr->GetMeID())
         {
            string str = (char*)c->ecx; // Оригинальное сообщение
            str += "\n\n";

            str += "Доступные существа:";

            for (int i = 0; i < 4; ++i)
               if (dwelling->creature_types[i] != ID_NONE)
                  str = str + "\n" + o_CreatureInfo[dwelling->creature_types[i]].name_plural + " — " + "{" +
                  to_string(dwelling->creature_counts[i]) + "}";

            // Передаём адрес текстового буфера в качестве аргумента для диалога
            sprintf(o_TextBuffer, "%s", str.c_str());
            c->ecx = (int)o_TextBuffer;
         }
      }

      // Лагерь беженцев
      if (mapItem->object_type == 0x4E)
      {
         string str = (char*)c->ecx; // Оригинальное сообщение

                              // Передаём адрес текстового буфера в качестве аргумента для диалога
         sprintf(o_TextBuffer, "%s\n\nДоступные существа:\n%s — {%d}", str.c_str(), o_CreatureInfo[mapItem->os_type].name_plural, mapItem->setup);
         c->ecx = (int)o_TextBuffer;
      }
   }

   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((char*)"HD.Plugin.ShowGuards");

         // Сообщения по правому клику
         _PI->WriteLoHook(0x415AEE, showGuardsRMB);

         // Сообщения при посещении
         _PI->WriteLoHook(0x4A13E6, showGuards); // стандартных банков
         _PI->WriteLoHook(0x4A1E56, showGuards); // Dragon Utopia
         _PI->WriteLoHook(0x4AC1B9, showGuards); // Crypt, Derelict Ship, Shipwreck
         _PI->WriteLoHook(0x4A18FF, showMonsters); // жилища существ 1-го уровня
         _PI->WriteLoHook(0x4A435C, showRefugeeCamp); // Refugee Camp

         _PI->WriteLoHook(0x413917, saveObjEntranceAddr); // Сохраняем адрес тайла-входа в объект
         _PI->WriteLoHook(0x4AC19D, Crypt_SkipMsgIfVisited); // убираем диалог обыска при посещении Склепа и Кораблей, когда они уже разграблены
      }
   }

   return TRUE;
}
Последний раз редактировалось Rolex 27 янв 2021, 01:46, всего редактировалось 6 раз(а).
Вернуться к началу

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 » 27 янв 2021, 01:03

Цитата:
Да, я тоже его добавил еще в коде ранее. Все работает. Правда заметил, что Вы в своем коде DwordAt заменили на *(int*). Какой в этом смысл?

Указатель - это, конечно, DWORD, но в пользовательском режиме нет разницы. Плюс тема для новичков (в той же homm3.h многого нет). Можно обойтись вообще одной patcher_x86, и это будет ещё полезнее для понимания того, откуда у плагина ноги растут. Но homm3.h в этой теме полезна тем, что не меняется. Всегда можно запустить примеры, которые были написаны в самом начале темы. А те, кто научатся писать плагины с помощью неё, однажды почувствуют желание перейти на H3API, но раньше времени не стоит вещи усложнять. Конечно, *(int*) всегда можно заменить на *(_dword_*) или DwordAt() или иначе запутать.

Цитата:
2. Кол-во существ в жилищах как раз нужно точное в отличии от банков существ, а потому пришлось убрать обращение к функции getInterval, то есть getInterval(dwelling->creature_counts[i]) заменить на to_string(dwelling->creature_counts[i]).

Логично. Я по инерции заменил. Кстати, std::to_string() - это уже С++11. Я теперь стараюсь приводить примеры, которые будут работать в VS 2008.

Цитата:
То, что опасно, лучше всегда обходить стороной, а то потом пойдут вылеты и различного рода проблемы. Я думаю второй вариант будет предпочтительней, кождого игрока (или может только игрока-человека) посетившего Refugee Camp кладем в массив и делаем для него после посещения в диалоге по ПКМ доступным тип и кол-во существ доступных для найма.

Да, но этот массив/список придётся обнулять при старте карты. Родные структуры обнуляются самой игрой.
Вернуться к началу

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

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

Сообщение Rolex » 27 янв 2021, 16:46

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

offlineRoseKavalier  
Мастер
Мастер
 
Сообщения: 331
Зарегистрирован: 23 сен 2017, 17:00
Пол: Не указан
Поблагодарили: 234 раз.

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

Сообщение RoseKavalier » 28 янв 2021, 05:09

Any reason to use custom string function when h3 already provides the same info?

Код: Выделить всё
/**
 * @brief Get a description of a creature group's size based on how many there are
 * @param amount How many creatures are present
 * @param text_variant 0..2 | 0=> X-Y | 1=> a XYZ of | 2=> A XYZ of (same as 1 but capitalized)
 * @return Description of group's size based on text files
 */
_H3API_ static LPCSTR GroupName(INT32 amount, INT32 text_variant);

...
{
        return FASTCALL_2(LPCSTR, 0x44AAB0, amount, text_variant);
}
Вернуться к началу

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

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

Сообщение AlexSpl » 28 янв 2021, 13:56

Well, i didn't know about that function anyway, but in the latest example I added "if ( n < 1 ) str = "0"; else", so you could see whether a dwelling is empty.

UPD: Tried to rewrite:

Код: Выделить всё
string getInterval(int n) {
    return n < 1 ? "0" : CALL_2(char*, __fastcall, 0x44AAB0, n, 0);
}

but when text_variant = 0, you don't see intervals (for example, you see "Horde" instead of "50-99").

* * *
Цитата:
@param text_variant 0..2 | 0=> X-Y | 1=> a XYZ of | 2=> A XYZ of (same as 1 but capitalized)

Tested. 0 => Horde | 1 => A Horde of | 2 => a horde of.
Вернуться к началу

Пред.След.

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

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

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