Объявления

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

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

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

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

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

AlexSpl писал(а):

Если всё-таки нужно, чтобы отражало только на существо с абилкой, закомментируйте третью строчку и раскомментируйте вторую: Но тогда реально отражение будет работать для Thunderbirds и единорогов, т.к. Curse не действует на нежить.

Так и нужно. А Curse со Сферой уязвиомости должно работать на атакующий отряд с абилкой.

AlexSpl писал(а):

Получается, придётся править также и синглкаст, чтобы привести всё к общему знаменателю, либо убирать резист от зеркала для отражённых заклинаний в других случаях.

Какой вариант на ваш взгляд лучше впишится в концепцию этого плагина?
Вернуться к началу

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

Цитата:
Какой вариант на ваш взягляд лучше впишится в концепцию этого плагина?

Нужно добавлять шанс резиста от зеркала в случае синглкаста. Наша идея в других случаях ведь такая: отражённые заклы отряд отражает в пустоту.
Вернуться к началу

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

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

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

AlexSpl писал(а):

Нужно добавлять шанс резиста от зеркала в случае синглкаста. Наша идея в других случаях ведь такая: отражённые заклы отряд отражает в пустоту.

Вот и я так думаю. А касательно 1 и 2 пункта, то там ничего сложного нет. Код первого пункта я привел, его только переписать (аналог коду с площадными с некоторыми дополнениями). А по Слепоте там условие добавить только, что если на отряд наложена Слепота (stack->active_spell_duration[SPL_BLIND] > 0), то его не считаем (НО ТОЛЬКО ДЛЯ УДАРНЫХ ЗАКЛОВ), как с иммунными (или под Антимагией).
Последний раз редактировалось Rolex 12 апр 2021, 18:48, всего редактировалось 4 раз(а).
Вернуться к началу

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

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

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

Цитата:
А касательно 1 и 2 пункта, то там ничего сложного нет.

Тут ещё как посмотреть. Например, если у нас есть Архангелы и Копейщики, а закл не убивает ни одного Архангела, будут выбраны Копейщики. Однако, может, именно урона от этого каста как раз не хватит, чтобы остальные отряды смогли добить Архангелов в текущем раунде.
Вернуться к началу

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

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

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

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

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

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

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

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

Сообщение Rolex » 12 апр 2021, 17:01

Тогда, думаю, можно делать проверку уроня существа. Допустим закл наносит 100 ед урона, а у верхнего в стеке Арха полное здоровье (250 ед.), проверяем следующего по силе, если это отряд 4-6 уровня, то тогда наносим урон самому высокоуровневому из них (если таких несколько, то самому сильному по Fight Value) из доступных в армии 4-6 уровня (если урон не убиваем стек полностью), если же остались отряды 1-3 уровня, то тогда направляем на Арха. Либо коэф. (урона от закла / полное здоровье существа) опустить с 1.0 до 0.5 (в случае с ул. существами 7-го уровня).
***
UPD: Давайте тогда пока решим все то, с чем уже определились:
- отражение только на существ с абилкой (Птицы и Единороги, Проклятие Мумий и Рыцарей оставить - должно работать со Сферой уязвимости и на нежить).
- добавить шанс резиста от зеркала в случае синглкаста.
- исключать из подсчета и игнорить как цель отряды под Слепотой (в случае с площадными нужно подумать, чтобы эти отряды не цепляло или в сложных ситуациях цепляло как можно реже).
- сделать пока что для ударных одиночных заклов выбор цели аналогично площадным (потом еще подумаем как его улучшить, для неударных оставить все как есть).
Вернуться к началу

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 » 12 апр 2021, 21:52

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

Тогда уж и Паралич с Окаменением учитывать нужно.
Вернуться к началу

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

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

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

Цитата:
Тогда уж и Паралич с Окаменением учитывать нужно.

Согласен. Хотя Слепота, если наложена Героем может и 99 раундов действовать, а абилки существ (вкл Слепоту Единорогов) - 3 раунда. И еще момент, если урон очень приличный, убивает весь стек под Слепотой или более 50% от кол-ва существ в стеке, то все же выгодней их атаковать.
Вернуться к началу

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

1. Заменил SPL_LIGHTNING_BOLT на SPL_THUNDERBOLT для Thunderbirds.
2. Отражение только на существо-кастера (Thunderbirds, Unicorns/War Unicorns, Black Knights/Dread Knights, Mummies).
3. Учтён шанс резиста от Magic Mirror для перенаправленного заклинания при синглкасте.
4. Теперь ослеплённые, парализованные и окаменевшие отряды не попадают в stackVector.
5. Двойная сортировка stackVector (по damageFightValue и по extraFightValue) для ударных заклинаний при синглкасте.

 
Код: Выделить всё
#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);
    float getFullVulnerability(const int spell);
       
    inline int current_health() {
        return this->count_current * this->full_hp - this->lost_hp;
    }
    inline int full_health() {
        return this->count_current * this->full_hp;
    }
    inline int fightValue() {
        return this->creature.fight_value * this->count_current;
    };
    inline double damageFightValue(const int spell, const int spellDamage) {
        return this->getFullVulnerability(spell) * min((double)spellDamage / this->full_hp,
            this->count_current - (double)this->lost_hp / this->full_hp) * this->creature.fight_value;
    }
    inline double extraFightValue(const int spell, const int spellDamage) {
        return spellDamage >= this->current_health() && spellDamage < this->full_health() ?
            this->getFullVulnerability(spell) * this->creature.fight_value : 0;
    }
    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 getHitChanceResist(const int spell) {
        return (int)(100 * getMagicVulnerability(spell));
    }
    inline int getHitChanceFull(const int spell) {
        return (int)(100 * getFullVulnerability(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);
    }
};

class cmpFightValue
{
    int spell;

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

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

class cmpDamageFightValue
{
    int spell;
    _Hero_* attHero;
    _Hero_* defHero;

public:
    cmpDamageFightValue(int id, _Hero_* aHero, _Hero_* dHero) : spell(id), attHero(aHero), defHero(dHero) { }

    bool operator () (_BattleStackEx_* a, _BattleStackEx_* b)
    {
        return a->damageFightValue(spell, a->calcMagicDamage(spell, attHero, defHero)) >
            b->damageFightValue(spell, b->calcMagicDamage(spell, attHero, defHero));
    }
};

class cmpExtraFightValue
{
    int spell;
    _Hero_* attHero;
    _Hero_* defHero;

public:
    cmpExtraFightValue(int id, _Hero_* aHero, _Hero_* dHero) : spell(id), attHero(aHero), defHero(dHero) { }

    bool operator () (_BattleStackEx_* a, _BattleStackEx_* b)
    {
        return a->extraFightValue(spell, a->calcMagicDamage(spell, attHero, defHero)) >
            b->extraFightValue(spell, b->calcMagicDamage(spell, attHero, defHero));
    }
};

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

bool showMagicMirrorAnim, showMagicResistAnim, showSpellAnim;
bool redirectedSingleCast, redirectedAreaSpell;
bool showResistAfterAttack = false, showMagicMirrorAfterAttack = false;
bool needMagicMirrorAnim[2][20], needMagicResistAnim[2][20], needSpellAnim[2][20];
_BattleStackEx_* targetStack = 0;

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) &&
        !this->active_spell_duration[SPL_BLIND] && !this->active_spell_duration[SPL_STONE] && !this->active_spell_duration[SPL_PARALYZE];
}

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) ++i;
        stackFoe = (_BattleStackEx_*)&o_BattleMgr->stack[sideFoe][i];
    }
   
    return stackFoe;
}

float _BattleStackEx_::getFullVulnerability(const int spell)
{
    float magicMirrorChance = this->active_spell_duration[SPL_MAGIC_MIRROR] ?
        o_Spell[SPL_MAGIC_MIRROR].effect[this->active_spells_power[SPL_MAGIC_MIRROR]] * 0.01f : 0;
    return (1 - magicMirrorChance) * this->getMagicVulnerability(spell);
};

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

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);
    redirectedSingleCast = true;

    return EXEC_DEFAULT;
}

float __stdcall calcMagicMirrorResist(HiHook* h, _BattleMgr_* battleMgr, int spell, int side, _BattleStackEx_* stack, int a5, int a6, int a7)
{
    float magicVulnerability = CALL_7(float, __thiscall, h->GetDefaultFunc(), o_BattleMgr, spell, side, stack, a5, a6, a7);
    float magicMirrorChance = stack->active_spell_duration[SPL_MAGIC_MIRROR] ?
        o_Spell[SPL_MAGIC_MIRROR].effect[stack->active_spells_power[SPL_MAGIC_MIRROR]] * 0.01f : 0;
   
    return redirectedSingleCast ? (1 - magicMirrorChance) * magicVulnerability : magicVulnerability;   
}

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

int _BattleStackEx_::getMonsterSpell()
{
    int spell = ID_NONE;

    if ( this )
    {
        switch ( this->creature_id )
        {
        case CID_THUNDERBIRD:
            spell = SPL_THUNDERBOLT;
            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;
        redirectedSingleCast = 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);
       
        if ( spell >= SPL_MAGIC_ARROW && spell <= SPL_CHAIN_LIGHTNING || spell == SPL_TITANS_LIGHTNING_BOLT )
        {
            _Hero_* attHero = o_BattleMgr->hero[o_BattleMgr->current_side];
            _Hero_* defHero = o_BattleMgr->hero[1 - o_BattleMgr->current_side];

            sort(stackVector.begin(), stackVector.end(), cmpDamageFightValue(spell, attHero, defHero));
            stable_sort(stackVector.begin(), stackVector.end(), cmpExtraFightValue(spell, attHero, defHero));
        }
        else
        {
            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 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 __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, double>> 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;
        double extraFightValueUs = 0, extraFightValueThem = 0;
        double damageFightValueUs = 0, damageFightValueThem = 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);
                            extraFightValueThem += stack->extraFightValue(spell, spellDamage);
                        }
                        else {
                            ++countUs;
                            damageFightValueUs += stack->damageFightValue(spell, spellDamage);
                            extraFightValueUs += stack->extraFightValue(spell, 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 = extraFightValueThem - extraFightValueUs;

        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 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->getHitChanceFull(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_THUNDERBOLT;
        addrHit = 0x440F03;
        addrSkip = 0x4412AB;
        break;
    case 0x440372:
        spell = SPL_BLIND;
        addrHit = 0x44038D;
        addrSkip = 0x4402AE;
        break;
    case 0x440467:
        spell = SPL_CURSE;
        addrHit = 0x440482;
        addrSkip = 0x4402AE;
        break;
    }

    if ( stack->magicMirrorLucky(spell) )
    {
        c->return_address = addrSkip;
       
        // Если сработало Magic Mirror, отражаем обратно
        _BattleStackEx_* stackFoe = (_BattleStackEx_*)c->esi;
        if ( stackFoe )
        {
            int hitChanceFoe = stackFoe->getHitChanceResist(spell);
           
            if ( Randint_s() <= hitChanceFoe )
            {
                if ( spell == SPL_THUNDERBOLT )
                {
                    playSound(o_Spell[SPL_MAGIC_MIRROR].wav_name);
                    stack->playAnimation(o_Spell[SPL_MAGIC_MIRROR].animation_ix);
                }
                else
                {
                    targetStack = stack;
                    showMagicMirrorAfterAttack = true;
                }

                c->edi = (int)stackFoe;
                c->return_address = addrHit;
            }
            else if ( hitChanceFoe )
            {
                if ( spell == SPL_THUNDERBOLT )
                {
                    playSound("MagicRes.wav");
                    stackFoe->playAnimation(78);
                }
                else
                {
                    targetStack = stackFoe;
                    showResistAfterAttack = true;
                }
            }
        }
                       
        return NO_EXEC_DEFAULT;
    }
       
    return EXEC_DEFAULT;
}

int __stdcall playAnimForMonster(LoHook* h, HookContext* c)
{
    if ( showResistAfterAttack )
    {
        playSound("MagicRes.wav");
        targetStack->playAnimation(78);
        showResistAfterAttack = false;
    }

    if ( showMagicMirrorAfterAttack )
    {
        playSound(o_Spell[SPL_MAGIC_MIRROR].wav_name);
        targetStack->playAnimation(o_Spell[SPL_MAGIC_MIRROR].animation_ix);
        showMagicMirrorAfterAttack = false;
    }

    return EXEC_DEFAULT;
}

int __stdcall playMagicMirrorSound(LoHook* h, HookContext* c)
{
    playSound(o_Spell[SPL_MAGIC_MIRROR].wav_name);
   
    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);
            _PI->WriteHiHook(0x5A83A0, SPLICE_, EXTENDED_, THISCALL_, calcMagicMirrorResist);

            // Масскаст
            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(0x440467, mirrorMonsterSpell); // Curse
            _PI->WriteLoHook(0x468C99, playAnimForMonster);
        }
    }

    return TRUE;
}

В п. 5 не уверен, что приоритетнее, и можно ли вообще обойтись двумя сортировками.

Первая сортировка упорядочивает stackVector по убыванию damageFightValue. Здесь всё логично. А вместо второй всё-таки нужен проход по вектору в цикле, как это сделано для площадных заклинаний.
Вернуться к началу

Пред.След.

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

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

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