Объявления

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

Как Вам такая идея? Есть вектор, например, (Ancient Behemoths, Cyclops Kings, Rocs, Wolf Riders, Hobgoblins). Мы подменяем его нашим вектором: (Ancient Behemoths, Behemoths, Cyclops Kings, Cyclops, Rocs, Wolf Riders, Hobgoblins, Goblins). Т.е. не трогаем алгоритмы и пальцем. Мне кажется, это хорошее решение. AI сам решит, хочет ли он нанимать не грейды или нет, но в безвыходной ситуации (Ancient Behemoths vs. Behemoths) будет обязан.
Вернуться к началу

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

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

Сообщение Rolex » 27 апр 2021, 18:58

А сейчас, разве как-то иначе это работает в жилищах после ваших правок AI?
Вернуться к началу

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, 19:00

Сейчас комп игнорит не грейды в городах (и в оригинале тоже) при наличии ул. постройки, конечно. Нужно будет внедрить подмену вектора в двух местах: туда, где он думает о том, чтобы нанять, и туда, где нанимает. Правда ценность в первом случае нужно считать по грейдам, а не суммарную.
Вернуться к началу

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

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

Сообщение Rolex » 27 апр 2021, 19:10

Например, в жилище с плагином накопления 10 Древних чудищ / Чудищ. Нужно, чтобы имея, например, 2 Кристалла и 18 000 Золота и если до конца текущего хода другими героями AI не сможет найти еще Кристаллы, то нужно, чтобы он нанял 2 Древних чудища и 8 Чудищ.
Вернуться к началу

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

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

Сообщение void_17 » 28 апр 2021, 12:04

Кто-нибудь знает, что за функция __OFSUB__( , ) ?
Вернуться к началу

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:24

Это макрос для получение флага переполнения (OF) при вычитании. Вот тут почитайте. В код на С++ такое переносить не нужно. Всегда можно найти альтернативу.
Вернуться к началу

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

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

Сообщение void_17 » 28 апр 2021, 15:54

Как сделать проверку на включенные HW-правила в игре?
Вернуться к началу

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, 16:52

Такую возможность должен предоставлять сам плагин (HW Rules не является частью HD-мода), всё остальное - костыли.
Вернуться к началу

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

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

Сообщение Rolex » 28 апр 2021, 18:45

Ну как Ваши успехи, AlexSpl, все получается, что запланировали? Или столкнулись с какими-то непредвиденными трудностями?
Последний раз редактировалось Rolex 28 апр 2021, 19:22, всего редактировалось 4 раз(а).
Вернуться к началу

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, 18:46

Код почти готов. Реализованы новые жилища (выбор между грейдом и не грейдом, 1-й уровень платный) и найм AI негрейженых существ в городах при наличии ул. постройки. Осталось побороть баг оригинальной игры с откатом ресурсов. Вы можете самостоятельно убедиться в его наличии (в плагине включён дебаг-мод; закомментируйте строчку для того, чтобы отключить). Баг не критичный, вылетов не должно быть (максимум неправильный откат ресурсов). Попробую найти причину. Суть в том, что при покупке нового героя игра может нанять существ, а потом их вернуть обратно, а потраченные деньги - AI игроку.

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

#define DEBUGL1
//#define DEBUGL2

Patcher* _P;
PatcherInstance* _PI;

short hiredIdx = ID_NONE;

// For debugging purposes
void showDebugInfo(const char* format, ...)
{
    va_list args;
    va_start(args, format);

    vsprintf(o_TextBuffer, format, args);
    b_MsgBox(o_TextBuffer, MBX_OK);

    va_end(args);
}

void showInfo(const _Army_* army, const int action)
{
    char txtBuffer[1024];
    char infoStr[1024];

    sprintf(infoStr, "Player: {%d}  |  Hero: {%s}  |  Action: {%d}\n\n", o_ActivePlayer->id,
        o_ActivePlayer->selected_hero_id != ID_NONE ? o_GameMgr->hero[o_ActivePlayer->selected_hero_id].name : "N/A", action);
   
    for (int i = 0; i < 7; ++i)
    {
        if ( army->type[i] != ID_NONE )
            sprintf(txtBuffer, "{%s}: %d\n", o_pCreatureInfo[army->type[i]].name_plural, army->count[i]);
        else
            sprintf(txtBuffer, "%s\n", "[Empty]");
        strcat(infoStr, txtBuffer);
    }
   
    showDebugInfo("%s\nWood: {%d} | Ore: {%d}\nMercury: {%d} | Sulfur: {%d} | Crystal: {%d} | Gems: {%d}\nGold: {%d}",
        infoStr, o_ActivePlayer->resourses.wood, o_ActivePlayer->resourses.ore, o_ActivePlayer->resourses.mercury,
        o_ActivePlayer->resourses.sulfur, o_ActivePlayer->resourses.crystal, o_ActivePlayer->resourses.jems,
        o_ActivePlayer->resourses.gold);
}

int getCreatureBasicType(const int id)
{
    int basicType = id;
   
    if ( id % 2 && (id < CID_AIR_ELEMENTAL || id == CID_SPRITE || id == CID_MAGIC_ELEMENTAL || id == CID_PHOENIX) )
        basicType = id - 1;
    else
    switch ( id )
    {
    case CID_STORM_ELEMENTAL:
        basicType = CID_AIR_ELEMENTAL;
        break;
    case CID_MAGMA_ELEMENTAL:
        basicType = CID_EARTH_ELEMENTAL;
        break;
    case CID_ENERGY_ELEMENTAL:
        basicType = CID_FIRE_ELEMENTAL;
        break;
    case CID_ICE_ELEMENTAL:
        basicType = CID_WATER_ELEMENTAL;
        break;
    }

    return basicType;
}

_Dwelling_* GetDwelling(const int id) {
    return (_Dwelling_ *)(o_GameMgr->Field<int>(0x4E39C) + 92 * id);
}

int __stdcall initDwelling(LoHook* h, HookContext* c)
{
    _Dwelling_* dwelling = (_Dwelling_*)c->esi;
    int upgMonId = CALL_1(int, __fastcall, 0x47AAD0, dwelling->creature_types[0]);
   
    if ( dwelling->type == 0x11 && upgMonId != ID_NONE )
    {
        dwelling->creature_types[1] = dwelling->creature_types[0];
        dwelling->creature_counts[1] = dwelling->creature_counts[0];
        dwelling->creature_types[0] = upgMonId;
        if ( dwelling->defenders.type[0] != ID_NONE )
            dwelling->defenders.type[0] = upgMonId;
    }

    return EXEC_DEFAULT;
}

void __stdcall fixDwellingDefenders(HiHook* h, _Dwelling_* dwelling, bool unused)
{
    CALL_2(void, __thiscall, h->GetDefaultFunc(), dwelling, unused);

    dwelling->defenders.type[1] = ID_NONE;
    dwelling->defenders.count[1] = 0;
}

_Dwelling_* getDwelling(_Hero_* hero)
{
    _Dwelling_* dwelling = 0;
   
    if ( hero )
    {
        _MapItem_* item = o_GameMgr->Map.GetItem(hero->x, hero->y, hero->z);
        if ( item && (item->object_type == 0x11 || item->object_type == 0x14) ) dwelling = GetDwelling(item->setup);
    }

    return dwelling;
}

int __stdcall afterHiring(LoHook* h, HookContext* c)
{
    _Dwelling_* dwelling = 0;
   
    if ( o_ActivePlayer->selected_hero_id != ID_NONE )
        dwelling = getDwelling(&o_GameMgr->hero[o_ActivePlayer->selected_hero_id]);
   
    if ( dwelling )
    {
        dwelling->creature_counts[0] = min(dwelling->creature_counts[0], dwelling->creature_counts[1]);
        dwelling->creature_counts[1] = dwelling->creature_counts[0];

        if ( h->GetAddress() == 0x5510B1 )
        {
            c->return_address = 0x55121B;
            return NO_EXEC_DEFAULT;
        }
    }

    return EXEC_DEFAULT;
}

struct AIStackToRecruit
{
    int creatureType;
    short* sourceArmyCounts;
    short creaturesAvailable;
    char r1;
    char r2;
    void updateAvailableCreatures();
} *AIStacksToRecruit = new AIStackToRecruit[16];

void AIStackToRecruit::updateAvailableCreatures()
{
    *this->sourceArmyCounts = this->creaturesAvailable;
   
    // Если грейд, а справа - его базовое существо, уравниваем кол-во существ, доступных для найма
    // Пользуемся тем, что после грейда всегда идёт его базовое существо
    if ( this[1].creatureType == getCreatureBasicType(this->creatureType) )
    {
        *this[1].sourceArmyCounts = this->creaturesAvailable;
        this[1].creaturesAvailable = this->creaturesAvailable;
    }

    // Если базовое существо, а слева - его грейд, уравниваем кол-во существ, доступных для найма
    if ( hiredIdx && this->creatureType == getCreatureBasicType(this[-1].creatureType) )
    {
        *this[-1].sourceArmyCounts = this->creaturesAvailable;
        this[-1].creaturesAvailable = this->creaturesAvailable;
    }
}

int __stdcall AI_TryingToHire(LoHook* h, HookContext* c)
{
    AIStackToRecruit* stacksBegin = *(AIStackToRecruit**)(c->edi + 0x30);
    AIStackToRecruit* stacksEnd = *(AIStackToRecruit**)(c->edi + 0x34);

    // Сохраняем оригинальные указатели
    AIStackToRecruit* stacksBeginSaved = stacksBegin;
    AIStackToRecruit* stacksEndSaved = stacksEnd;

    _Army_* army = *(_Army_**)(c->ebp + 8);
    int action = 0;
   
    if ( stacksBegin )
    {
        int stackPos = 0;

        _Dwelling_* dwelling = 0;
        bool isDwelling0x11 = false;
        bool isDwelling0x14 = false;
       
        if ( o_ActivePlayer->selected_hero_id != ID_NONE )
        {
            _Dwelling_* dwelling = getDwelling(&o_GameMgr->hero[o_ActivePlayer->selected_hero_id]);
            if ( dwelling ) {
                isDwelling0x11 = dwelling->type == 0x11;
                isDwelling0x14 = dwelling->type == 0x14;
            }
        }

        for (AIStackToRecruit* iStack = stacksBegin; iStack < stacksEnd; ++iStack)
        {
            AIStacksToRecruit[stackPos] = *iStack;

            int basicType = getCreatureBasicType(AIStacksToRecruit[stackPos].creatureType);
            if ( AIStacksToRecruit[stackPos].creatureType != basicType && !isDwelling0x11 && !isDwelling0x14 )
            {
                AIStacksToRecruit[stackPos + 1] = AIStacksToRecruit[stackPos];
                AIStacksToRecruit[stackPos + 1].creatureType = basicType;
                ++stackPos;
            }
            ++stackPos;
        }

        stacksBegin = &AIStacksToRecruit[0];
        stacksEnd = &AIStacksToRecruit[stackPos];

        // Подменяем оригинальный вектор существ для найма
        *(AIStackToRecruit**)(c->edi + 0x30) = stacksBegin;
        *(AIStackToRecruit**)(c->edi + 0x34) = stacksEnd;
       
#ifdef DEBUGL1
        int stacksNumber = stacksEnd - stacksBegin;
       
        char txtBuffer[1024] = "";
        char infoStr[1024];
       
        sprintf(infoStr, "Trying to hire {%d} stack%s:\n\n", stacksNumber, stacksNumber > 1 ? "s" : "");

        for (AIStackToRecruit* iStack = stacksBegin; iStack < stacksEnd; ++iStack)
        {
            sprintf(txtBuffer, "{%s}\n", o_pCreatureInfo[iStack->creatureType].name_plural);
            strcat(infoStr, txtBuffer);         
        }

        showDebugInfo(infoStr);
#endif
       
#ifdef DEBUGL1
        // Показываем армию и ресурсы до найма
        if ( army ) showInfo(army, action++);
#endif
    }
   
    int value;
    do
    {
        // Пытаемся нанять отряды
        value = CALL_2(int, __thiscall, 0x42D420, c->edi, c->esi);
        if ( value > 0 && hiredIdx != ID_NONE )
        {
            stacksBegin[hiredIdx].updateAvailableCreatures();
            hiredIdx = ID_NONE;

#ifdef DEBUGL1
            army = *(_Army_**)(c->ebp + 8);
            if ( army ) showInfo(army, action++);
#endif
        }
    }
    while (value > 0);
                       
    // Восстанавливаем оригинальный вектор существ для найма,
    // чтобы игра могла корректно освободить выделенную память
    *(AIStackToRecruit**)(c->edi + 0x30) = stacksBeginSaved;
    *(AIStackToRecruit**)(c->edi + 0x34) = stacksEndSaved;
   
    c->return_address = 0x42D768;
    return NO_EXEC_DEFAULT;
}

int __stdcall AI_ThinkingOfHiring(LoHook* h, HookContext* c)
{
    AIStackToRecruit* stacksBegin = *(AIStackToRecruit**)(c->esi + 0x30);
    AIStackToRecruit* stacksEnd = *(AIStackToRecruit**)(c->esi + 0x34);

    if ( stacksBegin )
    {
        int stackPos = 0;

        for (AIStackToRecruit* iStack = stacksBegin; iStack < stacksEnd; ++iStack)
        {
            AIStacksToRecruit[stackPos] = *iStack;

            int basicType = getCreatureBasicType(AIStacksToRecruit[stackPos].creatureType);
            if ( AIStacksToRecruit[stackPos].creatureType != basicType )
            {
                AIStacksToRecruit[stackPos + 1] = AIStacksToRecruit[stackPos];
                AIStacksToRecruit[stackPos + 1].creatureType = basicType;
                ++stackPos;
            }
            ++stackPos;
        }

        stacksBegin = &AIStacksToRecruit[0];
        stacksEnd = &AIStacksToRecruit[stackPos];
   
#ifdef DEBUGL2
        int stacksNumber = stacksEnd - stacksBegin;
       
        char txtBuffer[1024] = "";
        char infoStr[1024];
       
        sprintf(infoStr, "Thinking of hiring {%d} stack%s:\n\n", stacksNumber, stacksNumber > 1 ? "s" : "");

        for (AIStackToRecruit* iStack = stacksBegin; iStack < stacksEnd; ++iStack)
        {
            sprintf(txtBuffer, "{%s}\n", o_pCreatureInfo[iStack->creatureType].name_plural);
            strcat(infoStr, txtBuffer);         
        }

        showDebugInfo(infoStr);
#endif
    }

    c->edx = *(int*)(c->ebp + 8);
    int value;
    do
    {
        value = CALL_2(int, __thiscall, 0x42D420, c->esi, 0);
        c->edx += value;
    }
    while (value > 0);
       
    c->return_address = 0x42D864;
    return NO_EXEC_DEFAULT;
}

int __stdcall AI_SaveHiredCreatureIdx(LoHook* h, HookContext* c)
{
    hiredIdx = *(short*)(c->ebp - 0x18);

    return EXEC_DEFAULT;
}

int __stdcall skipMessages(LoHook* h, HookContext* c)
{
    if ( _P->VarGetValue("HD.UI.AdvMgr.SkipMapMsgs", 1) )
    {
        c->return_address = 0x4A1917;
        return NO_EXEC_DEFAULT;
    }

    return EXEC_DEFAULT;
}

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

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

            _PI->WriteLoHook(0x4A17BD, skipMessages);
            _PI->WriteLoHook(0x4B85EE, initDwelling);
            _PI->WriteLoHook(0x5510B1, afterHiring);
            _PI->WriteHexPatch(0x4A197B, "90 90");
            _PI->WriteHexPatch(0x4AB812, "90 90");
            _PI->WriteHiHook(0x4B8760, SPLICE_, EXTENDED_, THISCALL_, fixDwellingDefenders);
           
            // AI
            _PI->WriteLoHook(0x42D71C, AI_TryingToHire);
            _PI->WriteLoHook(0x42D84F, AI_ThinkingOfHiring);
            _PI->WriteLoHook(0x42D67B, AI_SaveHiredCreatureIdx);
            _PI->WriteLoHook(0x42D768, afterHiring);
            _PI->WriteHexPatch(0x42CC06, "90 90 90");
        }
    }

    return TRUE;
}

UPD: В вектор существ для двеллинга 0x14 не попадает неулучшенный отряд Iron Golems. Нужно обновить код для Summoning Portal и Mercenary Camp.
Последний раз редактировалось AlexSpl 29 апр 2021, 04:46, всего редактировалось 4 раз(а).
Вернуться к началу

Пред.След.

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

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

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

cron