Объявления

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

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

Герои Меча и Магии III: Возрождение Эрафии, Герои Меча и Магии III Дыхание Смерти, Герои Меча и Магии III Клинок Армагеддона, Герои Меча и Магии III Хроники Героев
offlineBen80  
имя: Сергей
Эксперт
Эксперт
 
Сообщения: 1318
Зарегистрирован: 18 июн 2017, 06:49
Пол: Не указан
Поблагодарили: 336 раз.

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

Сообщение Ben80 » 29 апр 2021, 14:19

void_17 писал(а):

Я до сих пор не могу понять: если вставить хук по адресу, то команда, которая были по адресу, заменяется на наш хук, верно? Если хук вернет EXEC_DEFAULT, то это значит, что оригинальная команда выполнится? Если да, то в каком порядке: до или после нашего хука?


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

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

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

Сообщение AlexSpl » 30 апр 2021, 03:15

Исправил защитников Golem Factory. Версия без правок оригинальной механики найма 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);

    if ( dwelling->type == 0x11 )
    {
        dwelling->defenders.type[1] = ID_NONE;
        dwelling->defenders.count[1] = 0;
    }
}

int getObjectType(const _Hero_* hero)
{
    int objId = 0;

    if ( hero )
    {
        _MapItem_* item = o_GameMgr->Map.GetItem(hero->x, hero->y, hero->z);
        if ( item ) objId = item->object_type;
    }

    return objId;
}

_Dwelling_* getDwelling(const _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->type == 0x11 )
    {
        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;

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

            int basicType = getCreatureBasicType(AIStacksToRecruit[stackPos].creatureType);
            int objType = getObjectType(&o_GameMgr->hero[o_ActivePlayer->selected_hero_id]);
            if ( AIStacksToRecruit[stackPos].creatureType != basicType && objType != 0x11 && objType != 0x14 && objType != 0x4E )
            {
                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);

        // Показываем армию и ресурсы до найма
        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);
     
#ifdef DEBUGL2
   if ( stacksBegin )
    {
        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;
}

Так, нашёл баг. В Golem Factory можно нанять и Stone Golems, и Iron Golems, а в плагине мы окно закрываем после покупки. В случае с Golem Factory (и жилищем элементалей) оно закрываться не должно. Исправил.
Вернуться к началу

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

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

Сообщение AlexSpl » 30 апр 2021, 11:49

Эсперимент #1. AI нанимает отряды существ в порядке убывания Fight Value 1-го существа из отряда.

Пример.
В оригинале AI предпочёл бы нанять 3 Чудищ вместо 1 Древнего Чудища + 2 Чудища. С плагином 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 _Hero_* hero, const int action, const bool upgradeDone = false)
{
    char txtBuffer[1024];
    char infoStr[1024];

    if ( upgradeDone )
        sprintf(txtBuffer, "{%s}", "UPGRADE");
    else
        sprintf(txtBuffer, "Action: {%d}", action);

    sprintf(infoStr, "Player: {%d}  |  Hero: {%s}  |  %s\n\n", o_ActivePlayer->id, hero ? hero->name : "N/A", txtBuffer);
   
    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;
}

int getCreatureUpgradeType(const int id)
{
    return CALL_1(int, __fastcall, 0x47AAD0, id);
}

_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 = getCreatureUpgradeType(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);

    if ( dwelling->type == 0x11 )
    {
        dwelling->defenders.type[1] = ID_NONE;
        dwelling->defenders.count[1] = 0;
    }
}

short getObjectType(const _Hero_* hero)
{
    short objType = ID_NONE;

    if ( o_ActivePlayer->selected_hero_id != -1 && hero )
    {
        _MapItem_* item = o_GameMgr->Map.GetItem(hero->x, hero->y, hero->z);
        if ( item ) objType = item->object_type;
    }

    return objType;
}

_Dwelling_* getDwelling(const _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->type == 0x11 )
    {
        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];

struct _ResourcesEx_ : public _Resources_
{
    friend const _ResourcesEx_ operator + (const _ResourcesEx_& a, const _ResourcesEx_& b);
    friend const _ResourcesEx_ operator - (const _ResourcesEx_& a, const _ResourcesEx_& b);
    friend _ResourcesEx_ operator += (_ResourcesEx_& a, const _ResourcesEx_& b);
    friend _ResourcesEx_ operator -= (_ResourcesEx_& a, const _ResourcesEx_& b);
    friend const _ResourcesEx_ operator * (const int n, const _ResourcesEx_& r);
    friend const _ResourcesEx_ operator * (const _ResourcesEx_& r, const int n);
    friend bool operator >= (const _ResourcesEx_& a, const _ResourcesEx_& b);
};

const _ResourcesEx_ operator + (const _ResourcesEx_& a, const _ResourcesEx_& b)
{
    _ResourcesEx_ result;

    result.wood = a.wood + b.wood;
    result.ore = a.ore + b.ore;
    result.mercury = a.mercury + b.mercury;
    result.sulfur = a.sulfur + b.sulfur;
    result.crystal = a.crystal + b.crystal;
    result.jems = a.jems + b.jems;
    result.gold = a.gold + b.gold;

    return result;
}

const _ResourcesEx_ operator - (const _ResourcesEx_& a, const _ResourcesEx_& b)
{
    _ResourcesEx_ result;

    result.wood = a.wood - b.wood;
    result.ore = a.ore - b.ore;
    result.mercury = a.mercury - b.mercury;
    result.sulfur = a.sulfur - b.sulfur;
    result.crystal = a.crystal - b.crystal;
    result.jems = a.jems - b.jems;
    result.gold = a.gold - b.gold;

    return result;
}

_ResourcesEx_ operator += (_ResourcesEx_& a, const _ResourcesEx_& b)
{
    a.wood += b.wood;
    a.ore += b.ore;
    a.mercury += b.mercury;
    a.sulfur += b.sulfur;
    a.crystal += b.crystal;
    a.jems += b.jems;
    a.gold += b.gold;

    return a;
}

_ResourcesEx_ operator -= (_ResourcesEx_& a, const _ResourcesEx_& b)
{
    a.wood -= b.wood;
    a.ore -= b.ore;
    a.mercury -= b.mercury;
    a.sulfur -= b.sulfur;
    a.crystal -= b.crystal;
    a.jems -= b.jems;
    a.gold -= b.gold;

    return a;
}

const _ResourcesEx_ operator * (const int n, const _ResourcesEx_& r)
{
    _ResourcesEx_ result;

    result.wood = n * r.wood;
    result.ore = n * r.ore;
    result.mercury = n * r.mercury;
    result.sulfur = n * r.sulfur;
    result.crystal = n * r.crystal;
    result.jems = n * r.jems;
    result.gold = n * r.gold;

    return result;
}

const _ResourcesEx_ operator * (const _ResourcesEx_& r, const int n)
{
    _ResourcesEx_ result;

    result.wood = n * r.wood;
    result.ore = n * r.ore;
    result.mercury = n * r.mercury;
    result.sulfur = n * r.sulfur;
    result.crystal = n * r.crystal;
    result.jems = n * r.jems;
    result.gold = n * r.gold;

    return result;
}

bool operator >= (const _ResourcesEx_& a, const _ResourcesEx_& b)
{
    return a.gold >= b.gold &&
        a.mercury >= b.mercury && a.sulfur >= b.sulfur && a.crystal >= b.crystal && a.jems >= b.jems &&
        a.wood >= b.wood && a.ore >= b.ore;
}

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);
    _Hero_* hero = army ? (_Hero_*)((int)army - 0x91) : 0;
    _Army_* garrisonArmy = *(_Army_**)(c->ebp + 0x10);
    _Town_* town = garrisonArmy ? (_Town_*)((int)garrisonArmy - 0xE0) : 0;
    _ResourcesEx_* playerResources = *(_ResourcesEx_**)(c->ebp + 0x14);
   
    int action = 0;
   
    if ( stacksBegin )
    {
        int stackPos = 0;

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

            int basicType = getCreatureBasicType(AIStacksToRecruit[stackPos].creatureType);
            int objType = getObjectType(hero);
            if ( AIStacksToRecruit[stackPos].creatureType != basicType && objType != 0x11 && objType != 0x14 && objType != 0x4E )
            {
                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);
       
        // Показываем армию и ресурсы до найма
        if ( army ) showInfo(army, hero, 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
            if ( army ) showInfo(army, hero, 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);
     
#ifdef DEBUGL2
    if ( stacksBegin )
    {
        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_HiringBestCreaturesFirst(LoHook* h, HookContext* c)
{
    AIStackToRecruit* stacksBegin = *(AIStackToRecruit**)(c->esi + 0x30);
    int maxValue = *(int*)(c->ebp - 0xC);
    int stackIdx = *(int*)(c->ebp - 0x18);
    int stackType = *(int*)(c->ebp - 0x20);
       
    int fightValue = o_pCreatureInfo[stackType].fight_value;
    c->edi = fightValue;
   
    return 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");

            // AI Experiments
            _PI->WriteLoHook(0x42D57D, AI_HiringBestCreaturesFirst);
        }
    }

    return TRUE;
}

В хуке AI_HiringBestCreaturesFirst() можно определить собственное правило, которому будет следовать AI при найме отрядов, заменив условие if ( fightValue > maxValue ). Например, будем нанимать первыми отряды с наибольшей скоростью: ( if o_pCreatureInfo[stacksBegin[stackIdx].creatureType].speed > maxValue ), где stacksBegin[stackIdx] - это текущий отряд, рассматриваемый AI. Критерий (в примере это скорость) следует возвращать в регистре edi, как это сделано в коде выше.

Можно запретить покупку существ, вернув отрицательное значение (например, -1) в edi. Таким образом, можно добиться того, чтобы AI не брал в армию существ со скоростью ниже 5, например. А чтобы он сумел защитить свои замки, можно разрешить AI скупать таких существ при приближении вражеского героя к замку.

Так, одного отрицательного edi недостаточно: AI всё равно купит такой отряд в самом конце. Но запретить реально. Может, понадобится отдельный хук для запрета найма.

Я придумал, как можно 100% запретить: выкинуть его из вектора найма :smile20:

* * *
UPD: Исправил ошибки в AI_HiringBestCreaturesFirst() (для первого стека в maxValue попадал оригинальный критерий, что приводило к неправильному порядку найма) и getObjectType().
Последний раз редактировалось AlexSpl 02 май 2021, 18:55, всего редактировалось 2 раз(а).
Вернуться к началу

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

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

Сообщение Ben80 » 30 апр 2021, 12:15

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

***

Ну то есть дело, конечно, не в том, что это именно 1-й уровень, а в том, что усиление армии получается незначительное, зато происходит падение скорости движения героя.
Вернуться к началу

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

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

Сообщение AlexSpl » 30 апр 2021, 12:21

Это реализуемо в AI_TryingToHire(). Когда проходимся по вектору существ в цикле for (AIStackToRecruit* iStack = stacksBegin; iStack < stacksEnd; ++iStack) { ... }, не переносим (AIStacksToRecruit[stackPos] = *iStack;) слабый и медленный отряд в наш вектор существ для найма.

Правда, я не знаю, будет ли AI пытаться докупить его :smile5: Возможен и такой вариант с зацикливанием. Нужно обязательно проверить. То, что не купит, - это 100%, а вот то, что не захочет купить снова, - не факт...

Проверил. Не покупает, но настойчиво возвращается в город. Видимо, нужно хукать вышестоящую :smile19: функцию. Какая-то же функция формирует этот вектор? Вот в ней и нужно отсеивать отряды.
Вернуться к началу

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

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

Сообщение Ben80 » 30 апр 2021, 12:42

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

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

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

Сообщение AlexSpl » 30 апр 2021, 12:44

Точно. Я видел этот свитч. Но тогда другой вопрос: получается, герой компа не уйдёт из города, пока не скупит всех? Но это же бред. Он спокойно уходит. Есть что-то ещё.

Ещё пример. Комп скупил прирост, построил здание, сколько смог существ выкупил, закончил ход в паре клеток от города. Разве он не пойдёт дальше по своим делам? Как Вы думаете, что он сделает на следующий день: вернётся в город, чтобы докупить существа, или пойдёт дальше?
Вернуться к началу

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

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

Сообщение Ben80 » 30 апр 2021, 12:49

Вполне может вернуться и докупить. И не сделает этого, только если оценка другого объекта (где-то на фронтире, например) будет выше.
(насколько понимаю, этот более вкусный объект может быть и более чем в 1 дне пути от героя)
Вернуться к началу

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

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

Сообщение AlexSpl » 30 апр 2021, 12:51

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

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

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

Сообщение Ben80 » 30 апр 2021, 12:54

Так что мне так кажется, игра ИИ тут более-менее ОК. Обычно у него есть денежные средства, которых хватит для выкупа большей части армии. Пока она не скуплена - замок как объект получит большой вес. А когда большая часть уже скуплена - вес не такой большой и какой-то объект на фронтире вполне может перебить ставку :smile1:
Вернуться к началу

Пред.След.

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

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

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

cron