Объявления

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

Короче, закрыл я To-Do List :smile1: Добавил всех предложенных Вами монстров.

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

using namespace std;

Patcher* _P;
PatcherInstance* _PI;

struct _BattleStackEx_ : public _BattleStack_
{
    bool approvedTarget(const int spell);
    bool magicMirrorLucky(const int spell);
    _BattleStackEx_* getStackToRedirect(const int spell);
    inline int fightValue() {
        return this->creature.fight_value * this->count_current;
    };
    inline float getMagicVulnerability(const int spell) {
        return CALL_7(float, __thiscall, 0x5A83A0, o_BattleMgr, spell, o_BattleMgr->current_side, this, 1, 1, 0);
    };
    inline int getHitChance(const int spell) {
        return (int)(100 * getMagicVulnerability(spell));
    }
    inline bool isImmuneTo(const int spell) {
        return getMagicVulnerability(spell) == 0;
    };
};

bool showMagicMirrorAnim, showMagicResistAnim, showSpellAnim, redirectAreaSpell;
bool needMagicMirrorAnim[2][20], needMagicResistAnim[2][20], needSpellAnim[2][20];
vector<_BattleStackEx_*> stackVector;
int stackIndex;

#define SPL_DEATH_RIPPLE 24
#define CID_AZURE_DRAGON 132

#define AnimateStack (((bool(*)[20])&o_BattleMgr->Field<bool>(0x547C)))

enum A {A_REFLECTSPELL_HOOK, A_MASSREFLECTION_HOOK, A_REFLECTSPELL_SKIP, A_REFLECTSPELL_DEFAULT, A_MASSANIMATION};
enum E {E_MASS, E_BERSERK, E_AREA, E_DEATH_RIPPLE, E_DESTROY_UNDEAD, E_ARMAGEDDON, E_SIZE};

const _ptr_ hAddr[][E_SIZE] =
{
    {0x5A6A50, 0x5A20F4, 0x5A4D74, 0x5A1006, 0x5A1203, 0x5A4F4E}, // A_REFLECTSPELL_HOOK
    {0x5A13E5, 0x5A2156, 0x5A4C80, 0x5A1151, 0x5A12BD, 0x5A4FFF}, // A_MASSREFLECTION_HOOK
    {0x5A6A83, 0x5A210E, 0x5A4D89, 0x5A107B, 0x5A1278, 0x5A4FBB}, // A_REFLECTSPELL_SKIP
    {0x000000, 0x000000, 0x5A4DA8, 0x5A1020, 0x5A121D, 0x5A4F63}, // A_REFLECTSPELL_DEFAULT
    {0x5A13FC, 0x5A2168, 0x000000, 0x5A116F, 0x5A12E0, 0x000000}  // A_MASSANIMATION
};

struct SpellDesc
{
    char* DefaultDesc;
    char* BasicDesc;
    char* AdvancedDesc;
    char* ExpertDesc;
};

SpellDesc NewMagicMirror =
{
    "{Магическое зеркало}\n\nЦелевой отряд способен отразить вражеское заклинание на самый сильный отряд противника с вероятностью в 40%.",
    "{Базовое Магическое зеркало}\n\nЦелевой отряд способен отразить вражеское заклинание на самый сильный отряд противника с вероятностью в 40%.",
    "{Продвинутое Магическое зеркало}\n\nЦелевой отряд способен отразить вражеское заклинание на самый сильный отряд противника с вероятностью в 50%.",
    "{Экспертное Магическое зеркало}\n\nВсе дружественные отряды способны отразить вражеское заклинание на все или самые сильные отряды противника с вероятностью в 50%."
};

int __stdcall afterInit(LoHook* h, HookContext* c)
{
    o_Spell[SPL_MAGIC_MIRROR].effect[0] = 40;
    o_Spell[SPL_MAGIC_MIRROR].effect[1] = 40;
    o_Spell[SPL_MAGIC_MIRROR].effect[2] = 50;
    o_Spell[SPL_MAGIC_MIRROR].effect[3] = 50;

    *(SpellDesc*)o_Spell[SPL_MAGIC_MIRROR].description = NewMagicMirror;

    return EXEC_DEFAULT;
}

_ptr_ getRA(const int spell)
{
    _ptr_ retAddr;

    switch ( spell )
    {
    case SPL_FROST_RING: case SPL_FIREBALL: case SPL_INFERNO: case SPL_METEOR_SHOWER:
        retAddr = hAddr[A_REFLECTSPELL_DEFAULT][E_AREA];
        break;
    case SPL_DEATH_RIPPLE:
        retAddr = hAddr[A_REFLECTSPELL_DEFAULT][E_DEATH_RIPPLE];
        break;
    case SPL_DESTROY_UNDEAD:
        retAddr = hAddr[A_REFLECTSPELL_DEFAULT][E_DESTROY_UNDEAD];
        break;
    case SPL_ARMAGEDDON:
        retAddr = hAddr[A_REFLECTSPELL_DEFAULT][E_ARMAGEDDON];
        break;
    }
   
    return retAddr;
}

bool _BattleStackEx_::approvedTarget(const int spell)
{
    return !this->is_killed && !this->isImmuneTo(spell) && !(spell == SPL_FORGETFULNESS && this->creature.shots == 0);
}

bool _BattleStackEx_::magicMirrorLucky(const int spell)
{
    int effSchoolLevel = this->active_spells_power[SPL_MAGIC_MIRROR];

    return !this->isImmuneTo(spell) && !(o_Spell[spell].flags & SPF_FRIENDLY_HAS_MASS) && (this->side != o_BattleMgr->current_side ||
        spell == SPL_FROST_RING || spell == SPL_FIREBALL || spell == SPL_INFERNO || spell == SPL_METEOR_SHOWER ||
        spell == SPL_BERSERK || spell == SPL_DEATH_RIPPLE || spell == SPL_DESTROY_UNDEAD || spell == SPL_ARMAGEDDON) &&
        this->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(1, 100) <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel];
}

_BattleStackEx_* _BattleStackEx_::getStackToRedirect(const int spell)
{
    int sideFoe = 1 - this->side;
    _BattleStackEx_* stackFoe = stackIndex < (int)stackVector.size() ?
        (_BattleStackEx_*)&o_BattleMgr->stack[sideFoe][stackVector[stackIndex]->index_on_side] : 0;
   
    ++stackIndex;
    return stackFoe;
}

bool __stdcall massSpellMagicMirror(HiHook *h, int spell, int ssLevel)
{
    return spell == SPL_MAGIC_MIRROR && ssLevel == 3 ? false : CALL_2(bool, __fastcall, h->GetDefaultFunc(), spell, ssLevel);
}

bool cmpFightValue(_BattleStackEx_* a, _BattleStackEx_* b)
{
    return a->creature.fight_value * a->count_current > b->creature.fight_value * b->count_current;
}

// Костыль для C++98
class cmpSpellDuration
{
    int spell;

public:
    cmpSpellDuration(int id) : spell(id) { }

    bool operator () (_BattleStackEx_* a, _BattleStackEx_* b)
    {
        return a->active_spell_duration[spell] < b->active_spell_duration[spell];
    }
};

int __stdcall newTarget(LoHook* h, HookContext* c)
{
    if ( stackIndex < (int)stackVector.size() )
        c->eax = (int)&o_BattleMgr->stack[o_BattleMgr->current_side][stackVector[stackIndex]->index_on_side];
    else {
        int i = 0;
        // Пропускаем погибшие отряды
        while ( o_BattleMgr->stack[o_BattleMgr->current_side][i].is_killed ) ++i;
        c->eax = (int)&o_BattleMgr->stack[o_BattleMgr->current_side][i];
    }
   
    return EXEC_DEFAULT;
}

int __stdcall skipSpellSound(LoHook* h, HookContext* c)
{
    if ( !c->edi ) {
        int spell = *(int*)(c->ebp + 8);
        char switchCase = ((char*)0x5A2B48)[spell - 10];

        // При следующих условиях не проигрываем звук заклинания
        if ( spell == SPL_DEATH_RIPPLE || spell == SPL_DESTROY_UNDEAD ||
            switchCase == 17 || switchCase == 24 || o_BattleMgr->ShouldNotRenderBattle() )
        {
            c->return_address = 0x5A0646;
            return NO_EXEC_DEFAULT;
        }
    }

    return EXEC_DEFAULT;
}

int getMonsterSpell(const int creature_id)
{
    int spell;

    switch ( creature_id )
    {
    case CID_THUNDERBIRD:
        spell = SPL_LIGHTNING_BOLT;
        break;
    case CID_DRAGON_FLY:
        spell = SPL_WEAKNESS;
        break;
    case CID_UNICORN: case CID_WAR_UNICORN:
        spell = SPL_BLIND;
        break;
    case CID_BLACK_KNIGHT: case CID_DREAD_KNIGHT: case CID_MUMMY:
        spell = SPL_CURSE;
        break;
    }

    return spell;
}

int __stdcall dataInit(LoHook* h, HookContext* c)
{
    int spell = h->GetAddress() == 0x5A058B ? *(int*)(c->ebp + 8) :
        getMonsterSpell(((_BattleStackEx_*)c->ecx)->creature_id);

    showMagicMirrorAnim = false;
    showMagicResistAnim = false;
    showSpellAnim = false;
    redirectAreaSpell = false;
    memset(needMagicMirrorAnim, false, 40);
    memset(needMagicResistAnim, false, 40);
    memset(needSpellAnim, false, 40);

    int casterSide = o_BattleMgr->current_side;
    int maxSize = o_BattleMgr->stacks_count[casterSide];
    stackVector.resize(maxSize);

    int n = 0;
    for (int i = 0; i < maxSize; ++i)
    {
        _BattleStackEx_* stack = (_BattleStackEx_*)&o_BattleMgr->stack[casterSide][i];
        if ( stack->approvedTarget(spell) ) stackVector[n++] = stack;
    }

    stackVector.resize(n);
    sort(stackVector.begin(), stackVector.end(), cmpFightValue);
    stable_sort(stackVector.begin(), stackVector.end(), cmpSpellDuration(spell));

    stackIndex = 0;

    return EXEC_DEFAULT;
}

int __stdcall magicMirrorAI(LoHook* h, HookContext* c)
{
    _BattleStackEx_* stack = (_BattleStackEx_*)c->edi;

    if ( stack )
    {
        _Hero_* hero = o_BattleMgr->hero[1 - o_BattleMgr->current_side];
        int spell = *(int*)(c->ebp + 8);
        int effSchoolLevel = stack->active_spells_power[SPL_MAGIC_MIRROR];

        // Если герой - AI, на отряде висит Magic Mirror и оно сработало, а также оригинальные проверки на Teleport и Sacrifice
        if ( !CALL_2(bool, __thiscall, 0x4CE600, o_GameMgr, o_BattleMgr->owner_id[o_BattleMgr->current_side]) &&
             stack->active_spell_duration[SPL_MAGIC_MIRROR] > 0 && Randint(1, 100) <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel] &&
             spell != SPL_TELEPORT && spell != SPL_SACRIFICE )
        {
            // Проигрываем одиночную анимацию
            CALL_5(void, __thiscall, 0x4963C0, o_BattleMgr, o_Spell[SPL_MAGIC_MIRROR].animation_ix, c->edi, 100, 0);

            c->return_address = 0x5A05CC;
            return NO_EXEC_DEFAULT;
        }
    }
   
    return EXEC_DEFAULT;
}

void playMagicResist()
{
    // Проигрываем массовую анимацию и звук резиста
    if ( showMagicResistAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, "MagicRes.wav", -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicResistAnim, 78, 0);
    }
}

void playMagicMirror()
{
    // Проигрываем массовую анимацию и звук Magic Mirror
    if ( showMagicMirrorAnim )
    {
        CALL_3(void, __fastcall, 0x59A890, o_Spell[SPL_MAGIC_MIRROR].wav_name, -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needMagicMirrorAnim, o_Spell[SPL_MAGIC_MIRROR].animation_ix, 0);
    }
}

void playResistAndMagicMirror()
{
    playMagicResist();
    playMagicMirror();
}

int __stdcall massReflection(LoHook* h, HookContext* c)
{
    bool hitFlag = false;
    _ptr_ addr = h->GetAddress();

    if ( addr == hAddr[A_MASSREFLECTION_HOOK][E_ARMAGEDDON] )
    {
        memcpy(AnimateStack, needSpellAnim, 40);
        return EXEC_DEFAULT;
    }
   
    if ( addr == hAddr[A_MASSREFLECTION_HOOK][E_MASS] )
        c->return_address = hAddr[A_MASSANIMATION][E_MASS];
   
    if ( addr == hAddr[A_MASSREFLECTION_HOOK][E_BERSERK] )
        c->return_address = hAddr[A_MASSANIMATION][E_BERSERK];
   
    if ( addr == hAddr[A_MASSREFLECTION_HOOK][E_DEATH_RIPPLE] )
    {
        hitFlag = true;
        c->return_address = hAddr[A_MASSANIMATION][E_DEATH_RIPPLE];
    }

    if ( addr == hAddr[A_MASSREFLECTION_HOOK][E_DESTROY_UNDEAD] )
    {
        c->edi = *(int*)(c->ebp - 0x10);
        hitFlag = true;
        c->return_address = hAddr[A_MASSANIMATION][E_DESTROY_UNDEAD];
    }

    playResistAndMagicMirror();

    // Проигрываем массовую анимацию и звук кастуемого заклинания
    if ( showSpellAnim )
    {
        _Spell_* spell = (_Spell_*)*(int*)(c->ebp - 0x10);
        CALL_3(void, __fastcall, 0x59A890, spell->wav_name, -1, 3);
        CALL_4(void, __thiscall, 0x5A6AD0, o_BattleMgr, &needSpellAnim, spell->animation_ix, hitFlag);
    }

    return NO_EXEC_DEFAULT;
}

bool isValidHex(const int hex)
{
    return hex >= 0 && hex < 187;
}

int getSpellTargetHex(const int spell)
{
    const int xSize = 17;
               
    int maxFightValueDelta = 0;
    int bestTargetHex = -1;
    for (int hex = 1; ; ++hex)
    {
        if ( hex % xSize == 16 ) hex += 2;
        if ( hex > 185 ) break;
     
        int y = hex / xSize;
        int d = y % 2;

        int areaR1[] = {-17 - d, -16 - d, -1, 0, 1, 17 - d, 18 - d};
        int areaFR[] = {-17 - d, -16 - d, -1, 1, 17 - d, 18 - d};
        int areaR2[] = {-35, -34, -33, -18 - d, -17 - d, -16 - d, -15 - d, -2, -1,
                0, 1, 2, 16 - d, 17 - d, 18 - d, 19 - d, 33, 34, 35};
       
        int* area = areaR1;
        int areaSize = sizeof(areaR1) / sizeof(int);
       
        if ( spell == SPL_FROST_RING ) {
            area = areaFR;
            areaSize = sizeof(areaFR) / sizeof(int);
        }

        if ( spell == SPL_INFERNO ) {
            area = areaR2;
            areaSize = sizeof(areaR2) / sizeof(int);
        }
       
        int countUs = 0, countThem = 0, fightValueUs = 0, fightValueThem = 0;
        for (int i = 0; i < areaSize; ++i)
        {
            int iHex = hex + area[i];
            if ( isValidHex(iHex) )
            {
                _BattleStackEx_* stack = (_BattleStackEx_*)o_BattleMgr->hex[iHex].GetCreature();
                if ( stack )
                {
                    if ( !stack->is_killed && !stack->isImmuneTo(spell) )
                    {
                        if ( stack->side == o_BattleMgr->current_side ) {
                            ++countThem;
                            fightValueThem += stack->fightValue();
                        }
                        else {
                            ++countUs;
                            fightValueUs += stack->fightValue();
                        }
                    }
                                   
                    if ( i + 1 < areaSize )
                    {
                        int iHexNext = hex + area[i + 1];
                        if ( isValidHex(iHexNext) && (_BattleStackEx_*)o_BattleMgr->hex[iHexNext].GetCreature() == stack ) ++i;
                    }
                }
            }
        }

        if ( countUs + countThem > 0 && fightValueThem - fightValueUs > maxFightValueDelta )
        {
            maxFightValueDelta = fightValueThem - fightValueUs;
            bestTargetHex = hex;
        }
    }

    return bestTargetHex;
}

void __stdcall areaReflection(HiHook* h, _BattleMgr_* battleMgr, int hex, int spell, int a3, int a4)
{
    memcpy(AnimateStack, needSpellAnim, 40);
    CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, hex, spell, a3, a4);
    playResistAndMagicMirror();

    // Если заклинание отражено
    if ( redirectAreaSpell && getSpellTargetHex(spell) != -1 )
    {
        showMagicMirrorAnim = false;
        showMagicResistAnim = false;
        memset(needMagicMirrorAnim, false, 40);
        memset(needMagicResistAnim, false, 40);

        CALL_3(void, __fastcall, 0x59A890, o_Spell[spell].wav_name, -1, 3);
        CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, getSpellTargetHex(spell), spell, a3, a4);
        playResistAndMagicMirror();
    }
}

int __stdcall playMagicMirrorSound(LoHook* h, HookContext* c)
{
    CALL_3(void, __fastcall, 0x59A890, o_Spell[SPL_MAGIC_MIRROR].wav_name, -1, 3);
   
    return EXEC_DEFAULT;
}

int __stdcall reflectSpell(LoHook* h, HookContext* c)
{
    _Hero_* hero = 0;
    _BattleStackEx_* stack = 0;
    int spell, spellPower, schoolLevel;
    int casterSide = o_BattleMgr->current_side;

    _ptr_ addr = h->GetAddress();

    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_MASS] )
    {
       stack = (_BattleStackEx_*)c->esi;
       spell = *(int*)(c->ebp + 0xC);
       spellPower = *(int*)(c->ebp + 0x14);
       schoolLevel = *(int*)(c->ebp + 0x10);
       hero = *(_Hero_**)(c->ebp + 8);
       c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_MASS];
    }

    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_BERSERK] )
    {
       stack = *(_BattleStackEx_**)(c->ebp + 0x14);
       spell = *(int*)(c->ebp + 8);
       spellPower = *(int*)(c->ebp + 0x1C);
       schoolLevel = c->esi;
       hero = *(_Hero_**)(c->ebp - 0x14);
       c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_BERSERK];
    }

    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_AREA] )
    {
        stack = (_BattleStackEx_*)c->edi;
        spell = c->esi;
        schoolLevel = *(int*)(c->ebp - 0x44);
        c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_AREA];
    }

    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_DEATH_RIPPLE] )
    {
        stack = (_BattleStackEx_*)c->edi;
        spell = SPL_DEATH_RIPPLE;
        c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_DEATH_RIPPLE];
    }

    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_DESTROY_UNDEAD] )
    {
        stack = (_BattleStackEx_*)c->edi;
        spell = SPL_DESTROY_UNDEAD;
        c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_DESTROY_UNDEAD];
    }

    if ( addr == hAddr[A_REFLECTSPELL_HOOK][E_ARMAGEDDON] )
    {
        stack = (_BattleStackEx_*)c->esi;
        spell = SPL_ARMAGEDDON;
        c->return_address = hAddr[A_REFLECTSPELL_SKIP][E_ARMAGEDDON];
    }

    // Получаем оригинальный шанс срабатывания заклинания
    int hitChance = c->eax;

    if ( stack )
    {
        bool isSpellAoE = spell == SPL_FROST_RING || spell == SPL_FIREBALL || spell == SPL_INFERNO || spell == SPL_METEOR_SHOWER;
        bool isSpellSpec = spell == SPL_DEATH_RIPPLE || spell == SPL_DESTROY_UNDEAD || spell == SPL_ARMAGEDDON;

        if ( stack->magicMirrorLucky(spell) )
        {
            showMagicMirrorAnim = true;
            needMagicMirrorAnim[stack->side][stack->index_on_side] = true;

            // Не перенаправляем площадные заклинания, отражённые от дружественных отрядов
            if ( !(isSpellAoE && stack->side == casterSide) ) redirectAreaSpell = true;
           
            if ( isSpellAoE || isSpellSpec ) return NO_EXEC_DEFAULT;

            // Если сработало Magic Mirror, перенаправляем заклинание на вражеский отряд
            _BattleStackEx_* stackFoe = stack->getStackToRedirect(spell);
            if ( stackFoe )
            {
                // Определяем, сработает ли резист против отражённого заклинания
                int hitChanceFoe = stackFoe->getHitChance(spell);

                if ( Randint(1, 100) <= hitChanceFoe )
                {
                    showSpellAnim = true;
                    needSpellAnim[stackFoe->side][stackFoe->index_on_side] = true;
                    CALL_5(void, __thiscall, 0x444610, stackFoe, spell, spellPower, schoolLevel, hero);
                }
                else if ( hitChanceFoe )
                {
                    showMagicResistAnim = true;
                    needMagicResistAnim[stackFoe->side][stackFoe->index_on_side] = true;
                }
            }
        }
        else
        {
            if ( Randint(1, 100) <= hitChance )
            {
                showSpellAnim = true;
                needSpellAnim[stack->side][stack->index_on_side] = true;

                if ( isSpellAoE || isSpellSpec )
                {
                    c->return_address = getRA(spell);
                    return NO_EXEC_DEFAULT;
                }
               
                // Если не сработал резист, применяем эффект заклинания
                CALL_5(void, __thiscall, 0x444610, stack, spell, spellPower, schoolLevel, hero);
            }
            else if ( hitChance )
            {
                showMagicResistAnim = true;
                needMagicResistAnim[stack->side][stack->index_on_side] = true;

                if ( isSpellAoE || isSpellSpec ) return NO_EXEC_DEFAULT;
            }
        }
    }
   
    return NO_EXEC_DEFAULT;
}

int __stdcall afterArmageddon(LoHook* h, HookContext* c)
{
    playResistAndMagicMirror();
   
    return EXEC_DEFAULT;
}

int __stdcall mirrorMonsterSpell(LoHook* h, HookContext* c)
{
    int spell;
    _BattleStackEx_* stack = (_BattleStackEx_*)c->edi;

    _ptr_ addrHit, addrResist;
    switch ( h->GetAddress() )
    {
    case 0x440EE8:
        spell = SPL_LIGHTNING_BOLT;
        addrHit = 0x440F03;
        addrResist = 0x4412AB;
        break;
    case 0x441178:
        spell = SPL_WEAKNESS;
        addrHit = 0x441193;
        addrResist = 0x4412AB;
        break;
    case 0x440372:
        spell = SPL_BLIND;
        addrHit = 0x44038D;
        addrResist = 0x4402AE;
        break;
    case 0x4404DB:
        spell = SPL_CURSE;
        addrHit = 0x4404F6;
        addrResist = 0x4402AE;
        break;
    }

    if ( stack->magicMirrorLucky(spell) )
    {
        showMagicMirrorAnim = true;
        needMagicMirrorAnim[stack->side][stack->index_on_side] = true;
        playMagicMirror();
       
        // Если сработало Magic Mirror, перенаправляем заклинание на вражеский отряд
        _BattleStackEx_* stackFoe = stack->getStackToRedirect(spell);
        if ( stackFoe )
        {
            // Определяем, сработает ли резист против отражённого заклинания
            int hitChanceFoe = stackFoe->getHitChance(spell);

            if ( Randint(1, 100) <= hitChanceFoe )
            {
                c->edi = (int)stackFoe;
                c->return_address = addrHit;
                return NO_EXEC_DEFAULT;
            }
            else if ( hitChanceFoe )
            {
                showMagicResistAnim = true;
                needMagicResistAnim[stackFoe->side][stackFoe->index_on_side] = true;
                playMagicResist();

                c->return_address = addrResist;
                return NO_EXEC_DEFAULT;
            }
        }
    }
    else
    {
        int hitChance = stack->getHitChance(spell);

        if ( Randint(1, 100) <= hitChance )
        {
            c->return_address = addrHit;
            return NO_EXEC_DEFAULT;
        }
        else if ( hitChance )
        {
            showMagicResistAnim = true;
            needMagicResistAnim[stack->side][stack->index_on_side] = true;
            playMagicResist();

            c->return_address = addrResist;
            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.NewMagicMirrorTest");

            // Пропускаем оригинальный звук заклинания, чтобы воспроизвезти его позже
            _PI->WriteLoHook(0x5A0634, skipSpellSound);
           
            // Инициализируем данные
            _PI->WriteLoHook(0x5A058B, dataInit);
            _PI->WriteLoHook(0x4408E0, dataInit);
            _PI->WriteLoHook(0x440220, dataInit);

            // Синглкаст
            _PI->WriteLoHook(0x5A05CC, newTarget);
            _PI->WriteLoHook(0x5A058B, magicMirrorAI);

            // Масскаст
            for (int i = 0; i < E_SIZE; ++i)
            {
                _PI->WriteLoHook(hAddr[A_REFLECTSPELL_HOOK][i], reflectSpell);
                if ( i == E_AREA )
                    _PI->WriteHiHook(hAddr[A_MASSREFLECTION_HOOK][i], SPLICE_, EXTENDED_, THISCALL_, areaReflection);
                else
                    _PI->WriteLoHook(hAddr[A_MASSREFLECTION_HOOK][i], massReflection);
            }
           
            // Проигрываем звук Magic Mirror при отражении одиночного заклинания
            _PI->WriteLoHook(0x5A059A, playMagicMirrorSound);

            _PI->WriteLoHook(0x4EE1C1, afterInit);
            _PI->WriteHiHook(0x59E360, SPLICE_, EXTENDED_, FASTCALL_, massSpellMagicMirror);

            // Проигрываем анимацию резиста и Magic Mirror для Armageddon
            _PI->WriteLoHook(0x5A5560, afterArmageddon);

            _PI->WriteLoHook(0x440EE8, mirrorMonsterSpell); // Thunderstrike
            _PI->WriteLoHook(0x441178, mirrorMonsterSpell); // Weakness
            _PI->WriteLoHook(0x440372, mirrorMonsterSpell); // Blind
            _PI->WriteLoHook(0x4404DB, mirrorMonsterSpell); // Curse
        }
    }

    return TRUE;
}

Остались тесты (+ что там с Гипнозом?, + замена, где это нужно, LoadWAVplayAsync() на PlayWAVFile()). Плюс нужно объединить newTarget() и getStackToRedirect() и доработать, потому как вторая функция возвращает 0 в случае отсутствия отрядов в векторе, что корректно обрабатывается только при масскасте.
Вернуться к началу

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

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

Сообщение Rolex » 02 апр 2021, 14:34

AlexSpl писал(а):

Пример с Fight Value - это просто пример одной из возможных реализаций алгоритма. Можно и урон максимизировать. Не вижу проблемы в реализации.

Написать я думаю и сам это смогу. Просто смысл. Если Вы опять его будете переписывать. Двойная работа. Раз уже все переписали, то я думаю лучше чтобы и это Вы уже довели до ума.

Вы все же меня не правильно поняли. Прочтите внимательно то, о чем я писал. Задача не стоит максимизировать урон. Максимальный урон будет тогда, когда мы сможем захватить максимум отрядов. Моя реализация итак сейчас дает максимальный урон. Задача состоит в том, чтобы выбрать самый оптимальный гекс для атаки. А это точно также и не всегда максимум по разнице Fight Value. Примеры я наводил.

Максимизация урона - это плохое решение. Пример, у нас есть 25 Копейщиков и 25 Лучников и 1 Архангел, которые находятся в стороне от первых двух отрядов. Инферно наносит, допустим, 250 ед. Алгоритм с макс уроном выберет вариант для атаки 25 Копейщиков и 25 Лучников. И это будет урон в 2 раза больше (500 ед суммарно) чем мы смогли бы нанести атакуя 1 Архангела. Но победят ли в бою 25 лучников и 25 Копейщиков 1 Архангела? Нет. Потому как, кроме здоровья, у существа есть показатели Урона и навыки Атаки и Защиты.

Суть в том, что в бою 100 ед здоровья для более слабого существа и 100 ед для более сильного - это не одно и тоже. Ибо у сеществ, кроме здоровья есть другие параметры, которые влияют на их силу. И убить 1 Архангела в бою куда сложнее чем 25 Копейщиков. В этом и есть вся суть Fight Value. Но для закла это не имеет значение ибо он наносит фиксированный урон всем существам не зависимо от их других параметров. Поэтому в некоторых случаях меньший урон, но более сильным существам может быть ценее чем больший урон менее слабым.

Цитата:
То есть сумма

Код: Выделить всё
min( (double)(урон_от_закла / полное_здоровье_одного_существа_в_отряде), кол-во_существ_в_отряде ) * Fight_Value_одного_существа_в_отряде


этих значений по всем отрядам в проверямом целевом гексе. После выбираем гекс с максимальной суммой.


AlexSpl писал(а):

В плагине newEagleEye используется, например, rand_s().

А rand_s() - это тоже линейный конгруэнтный метод? Лучше всего использовать именно Вихрь Мерсенна.
Вернуться к началу

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 » 02 апр 2021, 15:59

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

Цитата:
А rand_s() - это тоже линейный конгруэнтный метод? Лучше всего использовать именно Вихрь Мерсенна.

Вот не в курсе. Он криптографически устойчив, поэтому повторов от боя к бою не будет. Можете посмотреть плагин newEagleEye и в нашем коде везде заменить вызов Randint() на аналогичный. Даже с Вихрем Мерсенна.
Вернуться к началу

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

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

Сообщение Rolex » 02 апр 2021, 16:16

AlexSpl писал(а):

Я не имею ничего против Ваших алгоритмов выбора гекса, но в алгоритме прохода очень сложно разобраться, а чем сложнее реализация, тем легче не заметить баг.

Я уже взял ваш проход за основу.

AlexSpl писал(а):

Я и прикрутил. Думал глитч из-за того, что игра дублирует. Посмотрите отражение в следующем случае (в остальных всё гладко): птицы атакуют отряд под Magic Mirror, срабатывает зеркало, срабатывает резист союзных гномов, но анимация как бы застывает на несколько миллисекунд в самом конце, и после этого атакованный отряд отвечает птицам. Т.е. как будто отряд не дожидается конца анимации резиста и проводит ответную атаку. Можно попробовать заменить асинхронное воспроизведение wav.

А что с этим? Получилось заменить асинхронное воспроизведение wav?

AlexSpl писал(а):

Сделал "отражение" Слабости. Но разве её можно отразить? Перед ней же идёт Dispel? :smile4: Но код пусть остаётся ради анимации резиста.

Ага, раз сперва диспел, значит нужно убирать. Закомментируйте пока участок кода с отражением Слабости, потом разберемся.
Вернуться к началу

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 » 02 апр 2021, 16:24

Цитата:
Ага, раз сперва диспел, значит нужно убирать. Закомментируйте пока участок кода с отражением Слабости, потом разберемся.

Пусть остаётся. Хотя бы для анимации резиста. А он есть в оригинале, только не показывается.

Цитата:
А что с этим? Получилось заменить асинхронное воспроизведение wav?

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

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

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

Сообщение Rolex » 02 апр 2021, 16:40

Цитата:
Вот не в курсе. Он криптографически устойчив, поэтому повторов от боя к бою не будет.

Самое интересное, что Mersenne Twister не является криптоустойчивым и негодится для использование в криптографии. Но во всех остальных областях считается одним из лучших ГСПЧ по качеству генерации. И большинство программистов рекомендуют использовать именно его.
Вернуться к началу

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 » 02 апр 2021, 17:47

Опасное заклинание получилось. Особенно для оффлайна. Можно выносить очень мощных спеллкастеров :smile20:

Надо ещё, кстати, смотреть, что происходит с отражённым заклинанием. В случае нейтралов идёт каст, который мы не контролируем. Если в оригинале Magic Mirror не отражало магию нейтралов, то и у нас не будет отражать, но и не будет отражаться в пустоту. Может, всё-таки убрать функционал "дополнительного резиста" при отражении? Как-то два щита (зеркало и резист) в случае отражения путают. Magic Mirror, например, на боевых гномах сейчас увеличивает резист на 30%. Почему бы не показывать анимацию резиста, а не Magic Mirror, когда заклинание реально уходит в пустоту? Плюс снимается проблема последовательной анимации в этом случае. Какая разница игроку, что именно отразило закл: сопротивление или зеркало?
Вернуться к началу

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

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

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

AlexSpl писал(а):

Опасное заклинание получилось. Особенно для оффлайна. Можно выносить очень мощных спеллкастеров :smile20:

Месяц назад Вы говорили обратное. :smile1:
AlexSpl писал(а):

Исправляя любое заклинание, нужно также вносить изменения в алгоритмы AI, а там дебри :smile2:

Ещё появляются вопросы, как поступать с иммунитетом существ. Если самый сильный отряд противника - Чёрные драконы, то что делать с отражённой в них Магической стрелой? И т.п. Короче, код не очень простой, а эффективность заклинания всё равно остаётся под сомнением.

Видимо, 20 страниц разработки прошли не зря.

AlexSpl писал(а):

Может, всё-таки убрать функционал "дополнительного резиста" при отражении? Как-то два щита (зеркало и резист) в случае отражения путают. Magic Mirror, например, на боевых гномах сейчас увеличивает резист на 30%. Почему бы не показывать анимацию резиста, а не Magic Mirror, когда заклинание реально уходит в пустоту? Плюс снимается проблема последовательной анимации в этом случае. Какая разница игроку, что именно отразило закл: сопротивление или зеркало?

На ваше усмотрение.

AlexSpl писал(а):

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

Я думаю, не стоит. Если судить по аналогии с другими заклами, то лучше оставить как есть. В оригинале существо под Гипнозом все равно на стороне своего родного героя, просто управлять он ним временно не может. Родной герой не может наложить на свой же отряд, который под Гипнозом отрицательное заклинание, но может положительное (только при масскасте положительное не накладывается), а герой загипнотизировавший вражеский отряд может наложить любое отрицательное и не может положительное. Если на отряде стоит Зеркало, то он не поддается Гипнозу, даже если проходит по здоровью для его наложения. Если же на отряде стоит Гипноз, то на него без проблем родной герой может наложить Зеркало, хотя и не будет ним управлять, пока он под Гипнозом.

AlexSpl писал(а):

+ замена, где это нужно, LoadWAVplayAsync() на PlayWAVFile()). Плюс нужно объединить newTarget() и getStackToRedirect() и доработать, потому как вторая функция возвращает 0 в случае отсутствия отрядов в векторе, что корректно обрабатывается только при масскасте.

А что с этим?

1) AlexSpl, а почему при касте Проклятия и Слабости на вражеские отряды под Зеркалом резист на отрядах кастера появляется еще до того как это заклинание было отражено Зеркалом вражеских отрядов? Нужно исправить. Изначально анимация Зеркала вражеских отрядов, потом резиста и потом направляемого закла отрядов кастующего героя.

2) Все же я думаю, что в случае с Армагеддоном нужно вернуть прежний порядок, который у нас был изначально. То есть сначала анимация резиста, потом Зеркала и после уже самого Армагеддона. Объясню почему. В случае со всеми другими площадными заклами в оригинале изначально проигрывается анимация самого закла, а после существа получают урон. В случае же с Армагеддоном изначально существа получают урон, а уже после проигрывается анимация самого Армагеддона. Вот поэтому и нужен. Сейчас у меня была возможность сравнить оба варианта и все же первый смотрится лучше. Поэтому Армагеддону возвращаем прежний порядок.

3) А что с имунными существами на которых отразился закл к которому у них иммунитет (других не осталось), резист что ли до сих пор не работает? Просто именно в этом случае даже в оригинале он работает, когда случайным образом выбирается имунное существо, а у нас не работает. Как-то не очень. Может через CALL_7 можно решить?
Последний раз редактировалось Rolex 02 апр 2021, 22:40, всего редактировалось 8 раз(а).
Вернуться к началу

offlineАватара пользователя
Владимир  
Эксперт
Эксперт
 
Сообщения: 1057
Зарегистрирован: 30 окт 2012, 18:37
Пол: Не указан
Награды: 3
Высшая медаль (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (1)
Поблагодарили: 638 раз.

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

Сообщение Владимир » 02 апр 2021, 20:07

Rolex писал(а):

Mersenne Twister ... во всех остальных областях считается одним из лучших ГСПЧ по качеству генерации.

Я с его помощью оба своих диплома писал.
Вернуться к началу

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 » 03 апр 2021, 09:55

Цитата:
1) AlexSpl, а почему при касте Проклятия и Слабости на вражеские отряды под Зеркалом резист на отрядах кастера появляется еще до того как это заклинание было отражено Зеркалом вражеских отрядов?

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

Цитата:
3) А что с имунными существами на которых отразился закл к которому у них иммунитет (других не осталось), резист что ли до сих пор не работает? Просто именно в этом случае даже в оригинале он работает, когда случайным образом выбирается имунное существо, а у нас не работает.

Работает в случае каста (отражение на первый живой отряд в случае синглкаста и в пустоту - в случае масскаста). Для абилок пока будет вылет (функция getStackToRedirect() вернёт 0, который игра не сможет корректно обработать).
Вернуться к началу

Пред.След.

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

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

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