Объявления

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

Да не было особых. Информатика не мой профиль был :smile1: Т.е. я не увлекался, но знаю, какие задачки предлагают. Там задротство и скукотища. Я больше по физике (вот там есть скромные успехи), но пошёл всё равно на ФПМИ и не жалею. Физиком нужно быть просто выдающимся, чтобы жить на зарплату физика. А в IT дефицит специалистов.
Вернуться к началу

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

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

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

Ну да, физика тоже интересная наука. Как раз тройка моих любимых. А какие по физике успехи были?
Вернуться к началу

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 » 08 апр 2021, 18:32

Диплом второй степени на республиканской :smile1: Два года подряд абсолютное первое место в области (это когда у тебя диплом первой степени, но ты всё равно всех сделал по баллам). Но к "республике" я не особо готовился. Там семейные обстоятельства. Такие риски и реакцию на них нельзя заранее учесть. Там вероятность очень маленькая, но всё равно произошло то, чего не должно было. Жизнь, короче :smile7:

Воспоминание из прошлого. Мне и не особо комфортно было. Я пришёл в душное помещение в очень тёплом свитере. Мне было очень жарко и я постоянно думал о том, как мне нехорошо. Но стеснялся снять этот свитер, потому что рядом были девочки. Вот такой я был дол**** :smile1: Диплом второй степени - потому что я решил пару задач, которые не смогли решить те, у кого был первый диплом. Мне просто времени не хватило (и, разумеется, баллов). Чувствовал я себя ужасно просто. Там реально душно было.
Вернуться к началу

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

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

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

Значит, если бы не теплый свитер, то с большой долей вероятности у вас был бы диплом первой степени. И, может быть, был бы шанс попасть на международную по физике. На самом деле по себе знаю, если тебе не комфортно, что-то мешает, то ты не может полностью сосредоточится на задаче, постоянно отвлекаешься и думаешь о том, как бы это исправить. А на таком уровне любая мелочь играет большое значение. :smile2:
Последний раз редактировалось Rolex 09 апр 2021, 15:42, всего редактировалось 2 раз(а).
Вернуться к началу

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

Цитата:
Значит, если бы не теплый свитер, то с большей долей вероятности у вас был бы диплом первой степени. И, можем быть, был бы шанс попасть на международную по физике.

Вполне возможно. Этот факт не давал сосредоточиться и постоянно перебивал мысли. Но ещё я не особо готовился. Не до этого было. Зато задачу на вращение идеально решил :smile1:

У меня есть знакомые с несколькими золотыми медалями на международной. Я ж из их тусовки :smile1: Т.е. не один и не два. Например, призёр международных (у него была бронза, а потом две золотых) сейчас работает в Microsoft (это когда я последний раз чекал его судьбу). Короч, в Штатах. И не только он. Много ребят из моей школы уехали в Штаты и не только. Так что, утечка мозгов вполне реальна. А у нас - Yandex :smile1:
Вернуться к началу

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

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

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

А вы почему не уехали уже после окончания университета?
Вернуться к началу

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 » 08 апр 2021, 19:10

Если бы я мог, я бы учился в MIT (см. аватарку). Конечно, деньги. Ладно, не только деньги (можно было выбить стипендию). Короче, сформулирую так: за меня никто не думал в мои 18 лет, а в 18 лет редко кто может думать и принимать решения самостоятельно, особенно если они связаны со сменой страны пребывания :smile1: Не хочу об этом. Что было - то прошло. У каждого своя судьба и свои возможности.

Так. После универа? Даже не хочу об этом говорить. Оставьте мою философию мне, а всю другую отдайте мудакам.
Вернуться к началу

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

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

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

Я понял. Это личное. Тогда не будем об этом. У нас здесь Герои все таки.
Вернуться к началу

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

Код со всеми последними изменениями. Так понимаю, нам осталось только пересмотреть заполнение вектора для синглкаста ударных заклинаний и учесть Blind.

 Код
Код: Выделить всё
#define _CRT_RAND_S
#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_
{
    int getMonsterSpell();
    bool approvedTarget(const int spell);
    bool magicMirrorLucky(const int spell);
    _BattleStackEx_* getStackToRedirect(const int spell, const bool massCast = true);
    int calcMagicDamage(const int spell, const _Hero_* attHero, const _Hero_* defHero);
    double damageFightValue(const int spell, const int spellDamage);
    int onesFightValue(const int spellDamage);
    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;
    };
    inline void playAnimation(const int id) {
        CALL_5(void, __thiscall, 0x4963C0, o_BattleMgr, id, this, 100, 0);
    }
};

bool showMagicMirrorAnim, showMagicResistAnim, showSpellAnim, redirectedAreaSpell;
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 NewMagicMirrorRus =
{
    "{Магическое зеркало}\n\nЦелевой отряд способен отразить вражеское заклинание на самый сильный отряд противника с вероятностью в 40%.",
    "{Базовое Магическое зеркало}\n\nЦелевой отряд способен отразить вражеское заклинание на самый сильный отряд противника с вероятностью в 40%.",
    "{Продвинутое Магическое зеркало}\n\nЦелевой отряд способен отразить вражеское заклинание на самый сильный отряд противника с вероятностью в 50%.",
    "{Экспертное Магическое зеркало}\n\nВсе дружественные отряды способны отразить вражеское заклинание на все или самые сильные отряды противника с вероятностью в 50%."
};

SpellDesc NewMagicMirrorEng =
{
    "{Magic Mirror}\n\nThe target stack is able to reflect the enemy spell on the strongest enemy stack with a 40% chance.",
    "{Basic Magic Mirror}\n\nThe target stack is able to reflect the enemy spell on the strongest enemy stack with a 40% chance.",
    "{Advanced Magic Mirror}\n\nThe target stack is able to reflect the enemy spell on the strongest enemy stack with a 50% chance.",
    "{Expert Magic Mirror}\n\nAll friendly stacks are able to reflect an enemy spell on all or the most strongest enemy stacks with a 50% chance."
};

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 =
        o_CreatureInfo[0].name_single[0] != 'P' ? NewMagicMirrorRus : NewMagicMirrorEng;

    return EXEC_DEFAULT;
}

int Randint_s()
{
    unsigned dice;
    rand_s(&dice);
    return (int)((double)dice / ((double)UINT_MAX + 1) * 100.0) + 1;
}

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);
}

_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_s() <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel];
}

_BattleStackEx_* _BattleStackEx_::getStackToRedirect(const int spell, const bool massCast)
{
    _BattleStackEx_* stackFoe = 0;
    int sideFoe = 1 - this->side;

    if ( stackIndex < (int)stackVector.size() && spell != SPL_DESTROY_UNDEAD && spell != SPL_DEATH_RIPPLE && spell != SPL_ARMAGEDDON )
        stackFoe = (_BattleStackEx_*)&o_BattleMgr->stack[sideFoe][stackVector[stackIndex++]->index_on_side];

    if ( !massCast && !stackFoe )
    {
        int i = 0;
        // Пропускаем погибшие отряды
        while ( o_BattleMgr->stack[o_BattleMgr->current_side][i++].is_killed );
        stackFoe = (_BattleStackEx_*)&o_BattleMgr->stack[sideFoe][i];
    }
   
    return stackFoe;
}

int _BattleStackEx_::calcMagicDamage(const int spell, const _Hero_* attHero, const _Hero_* defHero)
{
    int effSchoolLevel = 0;
   
    if ( attHero )
        effSchoolLevel = CALL_3(int, __thiscall, 0x4E52F0, attHero, spell, o_BattleMgr->spec_terr_type);
    // Если кастер - нейтралы на Magic Plains
    else if ( o_BattleMgr->spec_terr_type == 1 )
        effSchoolLevel = 3;

    int damage = attHero->power * o_Spell[spell].eff_power + o_Spell[spell].effect[effSchoolLevel];
    return CALL_7(int, __thiscall, 0x5A7BF0, o_BattleMgr, damage, spell, attHero, defHero, this, 0);
}

double _BattleStackEx_::damageFightValue(const int spell, const int spellDamage)
{
    return this->getMagicVulnerability(spell) * min((double)spellDamage / this->full_hp,
        this->count_current - (double)this->lost_hp / this->full_hp) * this->creature.fight_value;
};

int _BattleStackEx_::onesFightValue(const int spellDamage)
{
    int onesFightValue = 0;

    if ( spellDamage % this->full_hp != 0 && spellDamage / this->full_hp < this->count_current &&
        spellDamage % this->full_hp >= this->full_hp - this->lost_hp )
        onesFightValue = this->creature.fight_value;

    return onesFightValue;
}

int __stdcall newTarget(LoHook* h, HookContext* c)
{
    _BattleStackEx_* stack = (_BattleStackEx_*)c->edi;
    int spell = *(int*)(c->ebp + 8);

    c->eax = (int)stack->getStackToRedirect(spell, false);

    return EXEC_DEFAULT;
}

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);
}

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

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

    bool operator () (_BattleStackEx_* a, _BattleStackEx_* b)
    {
        return a->getMagicVulnerability(spell) * a->fightValue() > b->getMagicVulnerability(spell) * b->fightValue();
    }
};

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 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 _BattleStackEx_::getMonsterSpell()
{
    int spell = ID_NONE;

    if ( this )
    {
        switch ( this->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;
   
    if ( h->GetAddress() == 0x5A058B )
        spell = *(int*)(c->ebp + 8);
    else {
        _BattleStackEx_* stack = (_BattleStackEx_*)c->esi;
        spell = stack->getMonsterSpell();
    }

    if ( spell != ID_NONE )
    {
        showMagicMirrorAnim = false;
        showMagicResistAnim = false;
        showSpellAnim = false;
        redirectedAreaSpell = 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(spell));
        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_s() <= o_Spell[SPL_MAGIC_MIRROR].effect[effSchoolLevel] &&
             spell != SPL_TELEPORT && spell != SPL_SACRIFICE )
        {
            // Проигрываем одиночную анимацию
            stack->playAnimation(o_Spell[SPL_MAGIC_MIRROR].animation_ix);

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

void playSound(const char* fileName)
{
    CALL_1(_Sample_, __fastcall, 0x59A770, fileName);
}

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

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

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

    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];
    }

    playMagicMirror();
    playMagicResist();
   
    if ( addr == hAddr[A_MASSREFLECTION_HOOK][E_ARMAGEDDON] )
    {
        memcpy(AnimateStack, needSpellAnim, 40);
        return EXEC_DEFAULT;
    }

    // Проигрываем массовую анимацию и звук кастуемого заклинания
    if ( showSpellAnim )
    {
        _Spell_* spell = (_Spell_*)*(int*)(c->ebp - 0x10);
        playSound(spell->wav_name);
        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;
               
    vector <pair<double, int>> ones;
    ones.resize(186);

    double maxDamageFightValueDelta = 0.0;
    int bestTargetHex = -1;

    _Hero_* attHero = o_BattleMgr->hero[o_BattleMgr->current_side];
    _Hero_* defHero = o_BattleMgr->hero[1 - o_BattleMgr->current_side];

    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;
        int onesFightValueUs = 0, onesFightValueThem = 0;
        double damageFightValueUs = 0.0, damageFightValueThem = 0.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) )
                    {
                        // Считаем магический урон, получаемый стеком stack
                        int spellDamage = stack->calcMagicDamage(spell, attHero, defHero);
                       
                        if ( stack->side == o_BattleMgr->current_side ) {
                            ++countThem;
                            damageFightValueThem += stack->damageFightValue(spell, spellDamage);
                            onesFightValueThem += stack->onesFightValue(spellDamage);
                        }
                        else {
                            ++countUs;
                            damageFightValueUs += stack->damageFightValue(spell, spellDamage);
                            onesFightValueUs += stack->onesFightValue(spellDamage);
                        }
                    }
                                   
                    if ( i + 1 < areaSize )
                    {
                        int iHexNext = hex + area[i + 1];
                        if ( isValidHex(iHexNext) && (_BattleStackEx_*)o_BattleMgr->hex[iHexNext].GetCreature() == stack ) ++i;
                    }
                }
            }
        }

        ones[hex].first = damageFightValueThem - damageFightValueUs;
        ones[hex].second = onesFightValueThem - onesFightValueUs;

        if ( countThem + countUs > 0 && damageFightValueThem - damageFightValueUs > maxDamageFightValueDelta )
        {
            maxDamageFightValueDelta = damageFightValueThem - damageFightValueUs;
            bestTargetHex = hex;
        }

        for (int i = 1; i < 186; ++i)
            if ( maxDamageFightValueDelta > 0.0 && ones[i].first == maxDamageFightValueDelta )
            {
                if ( ones[i].second > ones[bestTargetHex].second )
                    bestTargetHex = i;
            }
    }

    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);
    playMagicResist();
    playMagicMirror();

    // Если заклинание отражено
    if ( redirectedAreaSpell )
    {
        int bestHex = getSpellTargetHex(spell);
        if ( bestHex != ID_NONE )
        {
            showMagicMirrorAnim = false;
            showMagicResistAnim = false;
            memset(needMagicMirrorAnim, false, 40);
            memset(needMagicResistAnim, false, 40);

            playSound(o_Spell[spell].wav_name);
            CALL_5(void, __thiscall, h->GetDefaultFunc(), battleMgr, bestHex, spell, a3, a4);
            playMagicResist();
            playMagicMirror();
        }
    }
}

int __stdcall playMagicMirrorSound(LoHook* h, HookContext* c)
{
    playSound(o_Spell[SPL_MAGIC_MIRROR].wav_name);
   
    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) )
        {
            _BattleStackEx_* stackFoe = stack->getStackToRedirect(spell);

            if ( stackFoe && !redirectedAreaSpell ) {
                showMagicMirrorAnim = true;
                needMagicMirrorAnim[stack->side][stack->index_on_side] = true;
            }
            else {
                showMagicResistAnim = true;
                needMagicResistAnim[stack->side][stack->index_on_side] = true;
            }

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

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

                if ( Randint_s() <= 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_s() <= 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;
            }
        }
    }
   
    return NO_EXEC_DEFAULT;
}

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

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

    if ( stack->magicMirrorLucky(spell) )
    {
        c->return_address = addrSkip;
       
        playSound(o_Spell[SPL_MAGIC_MIRROR].wav_name);
        stack->playAnimation(o_Spell[SPL_MAGIC_MIRROR].animation_ix);
       
        // Если сработало Magic Mirror, перенаправляем заклинание на вражеский отряд
        _BattleStackEx_* stackFoe = stack->getStackToRedirect(spell, false);
        if ( stackFoe )
        {
            // Определяем, сработает ли резист против отражённого заклинания
            int hitChanceFoe = stackFoe->getHitChance(spell);

            if ( Randint_s() <= hitChanceFoe )
            {
                c->edi = (int)stackFoe;
                c->return_address = addrHit;
            }
            else if ( hitChanceFoe )
            {
                playSound("MagicRes.wav");
                stackFoe->playAnimation(78);
            }
        }
       
        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.NewMagicMirrorBeta");

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

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

    return TRUE;
}

Самый большой плагин в этой теме пока :smile1:
Вернуться к началу

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

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

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

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

Код: Выделить всё
// Заводим вектор:
vector <pair<double, int>> target;
int bestTarget;

// Как и в случае с площадными берем минимум в кол-ве возможно убитых существ и текущем кол-ве и умножаем на его fight_value одного существа. В некоторых случаях кол-во самых сильных существ может быть гораздо меньше чем тот урон который может нанести закл. А в таком случае после сортировки этот отряд вряд ли будет самым сильным.

// Ибо в армии героя могут быть чуть менее слабые существа, но в большем кол-ве, которые смогут принять весь урон закла. Следовательно кол-во убитых таких существ может быть сильнее чем гораздо меньшее кол-во более сильных, где большая часть урона ушла в пустую.

target[i].first = ( min( (double)spellDemage / stack->full_hp ), stack->count_current ) * o_pCreatureInfo[stack->creature_id].fight_value;
target[i].second = i;

// Сортим по убыванию target[i].first с сохранением индексов.
// То есть после сортировки в target[0].second у нас будет хранится индекс самого сильного существа на поле боя (от 0 до 9), target[1].second второго по силе и тд.

for (int i = 0; i < target.size(); ++i)
{
   _BattleStack_* stack = &o_BattleMgr->stack[o_BattleMgr->current_side][ target[i].second ];
   
   // если урон от нашего дамажащего закла разделенный на остаток здоровья проверяемого существа более или равен единице, то выбираем стек с индексом target[i].second - наша цель.
   if ( (double)spellDemage / (stack->full_hp - stack->lost_hp) >= 1.0 )
   {
      bestTarget = target[i].second
      break;
   }
}


Подробней я писал здесь:
viewtopic.php?f=56&t=518&start=2360#p21853

При этом для площадных этого делать ненужно (>=1.0). Там все сложнее и это будет лишнее и в некоторых случаях может сделать даже хуже.
------------------------------------------------------------------------------------------------------
2) Отряды под Слепотой мы игнорим до тех пор, пока в армии не останутся одни имунные монстры и этот отряд под Слепотой. Вот только тогда и пробуждаем его. То есть за цель такой отряд мы не считаем, а пропускает его аналогично отряду с Экспертной Антимагией!

getSpellTargetHex и, наверное, в approvedTarget
if (!stack->is_killed && !stack->isImmuneTo(spell)) добавит что-то врроде этого ( !stack->active_spell_duration[SPL_BLIND] )

Rolex писал(а):

Преимущество должно быть у того героя на чьих отрядах стоит Зеркало. То есть речь сейчас идет за Слепоту у кастера без Зеркала. Обычно Слепоту ставят на самые сильные отряды, чтобы разобраться с менее слабыми. Потому, думаю, чтобы не ломать тактику, которую выбрал тот или иной игрок, нужно отряды под Слепотой игнорить до тех пор, пока в войске кастера не остануться одни имунные монстры и этот отряд под Слепотой (или же пока не закончится ее действие, или же пока она не будет снята родным героем ---> stack->active_spell_duration[SPL_BLIND] == 0). Если, например, у кастера остались только Черные драконы и этот отряд под Слепотой, вот тогда мы можем на такой отряд отражать дамажащий закл.

------------------------------------------------------------------------------------------------------
3)
AlexSpl писал(а):

Но зато я понял, что нужно добавлять (а точнее, умножать на шанс срабатывания Зеркала):
20% (резист), 50% (зеркало), (1 - 0.2) * (1 - 0.5) = 40% (не сработал резист И не сработало зеркало).

Надо бы тогда и это добавить.

Только 0.5 (50%) - это Expert для всех отрядов и Advanced для одного. А вот для нулевого и Basic там 0.4 для одного. Итого: (1 - 0.2) * (1 - 0.4) = 48%.
------------------------------------------------------------------------------------------------------
4)
AlexSpl писал(а):

Я бы списал на добавочный Fight Value от единичек.

Только не понятно, что там с резистом в единичках. Допутим 4 Титана с остатком в 100 ед. здоровья посчитает как 3,33, а в массив единичек добавит Fight Value единичек, только если урон от закла будет от 100 до 399 ед.
После вашего переноса неплохо бы его также потестить. Можно на двух парах в верхней 5 Титанов и 5 Архов и в нижней также. Нижню пару немного покоцать, чтобы осталось по 100 ед урона, а верхнюю не трогать. Потом направить Метеоритный дождь с уроном 100-150 ед на вражеский отряд под Зеркалом и посмотреть на какую пару отразит.
------------------------------------------------------------------------------------------------------
5) Проверка отражения абилки Единорогов при атаке отряда под Зеркалом:

Сейчас у Вас такой порядок отражения Слепоты Единорогов:
Анимация зеркала на отряде под Зеркалом () - потом нанос атаки Единорогами отряду под Зеркалом - потом Ослепление отряда, который наносил атаку.
В оригинале Слепота накладывается уже после Атаки. И тут получается, что отразивший отряд не наносит ответку (хотя ее можно было нанести до наложения Слепоты).

На мой взгляд оптимальная последовательность должна быть следующей:
Атакующий отряд Единорогов наносит Атаку и только после этого срабатывает Зеркало (а не до, как сейчас) на отряде под Зеркалом (но сразу Слепоту на Единорога НЕ накладываем), далее отразивший отряд должен дать ответку Единорогам, и только после этого мы накладываем Слепоту!
------------------------------------------------------------------------------------------------------
6) Но это только в случае со Слепотой ответка перед наложением Слепоты, а с Ударом молнии отражение Молнии сразу после анимации Зеркала и потом уже ответка.

При этом у Молнии Громовых птиц последовательность правильная. Поскольку это не Слепота, то здесь ненужно делать ответку перед наложением. То есть сразу анимация Зеркала, анимация закла и потом ответка. Но там другая проблема - удар молнии после отражения от вражеского стека попадает нет на птиц которые нанесли атаку, а на самый сильный отряд, который может принят его. То есть срабатывает по общему алгоритму.

Здесь нужно как-то разграничивать, если это была отражена абилка Громовых птиц, то в этих же птиц эту Молнию и отражаем.
------------------------------------------------------------------------------------------------------
7) А вот абилка Проклятие (Мумий, Черных и Зловещих рыцарей) вообще не работает и не отражается. Здесь порядок должен быть такой, как с Ударом молнии. Нанесли атаку отряду под Зеркалом, потом анимация Зеркала на целевом отряде, потом анимация Проклятия уже на отряде, который атаковал, и потом уже ответка целевого отразившего закл отряда.
Если же первым атакует отряд под Зеркалом, и абилка срабатывает во время ответки. То тогда получается сделали ответку, потом проигралась анимация Зеркала (на отряде под Зеркалом, который атаковал первым), а потом анимация Проклятия уже на отряде, который давал ответку.
------------------------------------------------------------------------------------------------------
8) Если все отряды каждого из героев под Зеркалом, то при отражении целевого (-ых) отрядов на отряды кастующего при одиночном касте или при масскасте отряд или отряды на которые пошло отражение всегда принимают этот закл и получают урон со 100% вероятностью.

В то время как при отражении площадных на отряды под Зеркалом эти заклы блочаться с вероятностью 50% на Advanced/Expert и 40% на Basic (часть принимает, часть блокирует). С той, с которой у нас заложено отражение.

Правильней было бы привести все к общему знаменателю. Либо же при отражении закла кастера на отряды под Зеркалом отраженный закл всегда будет срабатывать со 100% вероятность на отряде под Зеркалом (как сейчас у синглкаста и масскаста).

Либо же как у площадных отраженный закл на отряды под Зеркалом срабатывает ровно с той вероятность, с которой и отражается от целевого (или целевых) отрядов.

Наверное, все же нужно делать сингкаст и максскаст также как и с площадными (согласно нашей вероятности 40% и 50%).
------------------------------------------------------------------------------------------------------
9) Баг с имунными монстрами. Допустим, в армии героя есть Черные драконы и, допустим, Минотавры. Так вот после гибели Минотавров при отражении закла направленног героем с Черными драконами принимает этот закл резистом труп самого сильного из погибших существ (если в армии остались толькоимуннеы к этому заклу, например, как у нас Черные драконы).
Если были Черные драконы и Баллиста, то если баллисту уничтожат, то все отраженные заклы будет принимать резист уже разваленной баллисты. В данном случае нужно всегда направлять на Черного дракона, если в живых большего никого нет, а Черный будет блочить резистом.

10) Проверил 1) пункт в новом коде. Он не был сделан. Алгоритм выбора цели для ударных заклов синглкаста старый. Не такой, как у площадных.

ПЛЮС!!! У героев с навыком Сопротивление (резистом), резист получается в приорите Зеркала. Нужно, чтобы было наоборот, так, как правило, вероятность отражения Зеркала выше чем срабатывания резиста. А то получается, что на отряд под Зеркалом накладывается закл и он может этот закл отразить на кастера и принести пользу, а выходит, что он блочится резистом и все. То есть нужно изначально проверять, есть ли на отряде Зеркало, и если есть, проверять сработает ли оно на этом отряде на который был направлен закл и только если нет (Зеркало не сработает или его нет на отряде), только тогда уже после проверки срабатывания Зеркала пускать резист. Так выгодней. Лучше отразить закл на вражеский отряд, чем просто его блокировать.
------------------------------------------------------------------------------------------------------
AlexSpl, ТЕСТИРОВАНИЕ проводилась на предыдущей версии кода. Пройдитесь, пожалуйста, по свободе внимательно по каждому пункту и внесите все изменения и исправления, которые еще не успели в новой версии кода. Я не смотрел пока еще ваш новый код, может быть, Вы уже часть пунктов из этого списка там исправили. В общем отпишитесь, пожалуйста, какие пункты из этого списка вы уже успели исправить/доделать. Этот список я собрал уже после тщательного тестирования. Если внимательно и аккуратно сделать ВСЕ по этому списку, то тогда плагин уже будет полностью готов к использованию. Мне лично больше нечего добавить. :smile2:

AlexSpl писал(а):

Самый большой плагин в этой теме пока :smile1:

И самый крутой. :smile1: До этого самым большим и самым крутым плагином была Маскировка, там 280 строчек кода, а здесь уже 800! :smile8:
Последний раз редактировалось Rolex 09 апр 2021, 22:38, всего редактировалось 10 раз(а).
Вернуться к началу

Пред.След.

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

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

Сейчас этот форум просматривают: Yandex [bot] и гости: 1

cron