Краткий гайд по добавлению новых заклинаний с помощью плагина NewSpellsЧасть первая. Бафы/дебафы
0. Первым делом следует создать три картинки для заклинания: для магической гильдии (свиток), для книги магии и для эффекта в бою. Для этого следует открыть ресурсный файл NewSpells.lod, что лежит в папке Data, программой MMArchive от
GrayFace и извлечь оттуда кадры заклинаний, хранящиеся в файлах: SpellScr.def, spells.def и SpellInt.def (нужно просто кликнуть правой кнопкой мыши по каждому файлу в списке и выбрать пункт меню "Extract For DefTool..."). Получится три папки. В каждую из них для удобства копируем соответствующую картинку нового заклинания: в SpellScr - картинку для магической гильдии (свиток), в spells - картинку для книги магии, в SpellInt - картинку для эффекта в бою.
Далее, запускаем программу DefTool за авторством того же
GrayFace, открываем файл с расширением .hdl в папке - давайте начнём с картинок для гильдии магов - SpellScr (или как Вы её назвали при извлечении кадров) и, выделив название последней картинки в списке файлов картинок слева, переходим на вкладку Frames. Здесь выбираем нашу новую картинку для магической гильдии и жмём Enter (может, можно и перетаскиванием, но я не пробовал). Картинка будет добавлена сразу после выделенного файла. Возвращаемся на предыдущую вкладку, сохраняем проект (иконка дискеты) и создаём новый def-файл с добавленной картинкой нового заклинания, щёлкнув на кнопке "Make Def". Повторяем операцию для двух других картинок, т.е. создаём новые spells.def и SpellInt.def. Обратите внимание на то, что в SpellScr.def кадр нового заклинания должен располагаться до последнего кадра с изображением магической книги для гильдии магии Conflux c Aurora Borealis, т.е. быть предпоследним.
Осталось упаковать полученные SpellScr.def, spells.def и SpellInt.def в архив NewSpells.lod. Для этого просто перетащите файлы в окно MMArchive с открытым архивом NewSpells.lod, по желанию нажмите кнопку "Optimize" и смело закрывайте программу MMArchive (lod будет сохранён автоматически). Если для нового заклинания требуется собственная анимация, перед выходом из программы добавьте сюда также def-файл анимации.
Если для нового заклинания Вам требуется добавить звук, воспроизводимый во время каста, добавьте его в архив NewSpells.snd, лежащий в папке Data, с помощью всё той же программы MMArchive.
Поздравляю. Самое сложное позади. Теперь пишем код нового заклинания. Почему, спросите Вы, самое сложное позади, ведь код - это же сложнее? А потому, что в примере мы будем добавлять простейшее заклинание Магии Воды
Water Shield, повышающее защиту дружественного отряда на 1 + 10%/1 + 10%/2 + 20%/3 + 30% с округлением вниз для None/Basic/Advanced/Expert Water Magic соответственно на SP раундов. Для определённости бонус защиты будем считать в процентах от базовой защиты существа. Например, для Архангела прирост защиты составит 4/8/12 ед. (Basic/Advanced/Expert). Чтобы было чуть интересней, пусть заклинание, пока действует, также снимает с отряда один случайный негативный эффект в начале каждого раунда. Естественно, в общем случае, чем сложнее заклинание, тем больше кода для него потребуется написать. Итак.
1. Первым делом добавляем структуру нового заклинания в файл NewSpells.ini и заполняем её. Так как мы добавляем баф, то можно просто скопировать структуру заклинания Toughness, например, и изменить параметры заклинания на нужные Вам. Ключ "<Animation.ID>" пока не трогаем: мы будем использовать новую анимацию, а поэтому вернёмся сюда, когда присвоим ей порядковый номер. Вопросы могут возникать и по ключу "<Attributes>". Атрибуты заклинания (или, по-другому, флаги) - это битовое поле свойств заклинания в десятичной системе счисления. Здесь я приведу несколько известных флагов из HoMM3_ids.h:
- Код: Выделить всё
// spells flags
#define SPF_BATTLE 0x00000001 //- BF spell
#define SPF_MAP 0x00000002 //- MAP spell
#define SPF_TIME 0x00000004 //- Has a time scale (3 rounds or so)
#define SPF_CREATURE 0x00000008 //- Creature Spell
#define SPF_ON_STACK 0x00000010 //- target - single stack
#define SPF_ON_SHOOTING_STACK 0x00000020 //- target - single shooting stack (???)
#define SPF_HAS_MASS_ON_EXPERT 0x00000040 //- has a mass version at expert level
#define SPF_ON_LOCATION 0x00000080 //- target - any location
//0x00000100 -
//0x00000200 -
#define SPF_MIND 0x00000400 //- Mind spell
#define SPF_FRIENDLY_HAS_MASS 0x00000800 //- friendly and has mass version
#define SPF_NOT_ON_MACHINE 0x00001000 //- cannot be cast on SIEGE_WEAPON
#define SPF_ARTIFACT 0x00002000 //- Spell from Artifact
Как пользоваться? Решите, какие свойства будет иметь Ваше заклинание, выберите подходящие флаги и сложите их, сумму переведите в десятичную систему счисления. Для нашего заклинания подойдут флаги SPF_BATTLE + SPF_TIME + SPF_ON_STACK + 0x40000 (флаг AI для бафов/дебафов) = 0x40015 = 262165.
Далее, идём в папку Language, открываем ini-файл с переводом (пока у нас есть только English.ini) и добавляем структуру с текстами для нового заклинания. Теперь заполняем значения ключей имени (краткого и полного) и описания (общего и для каждой ступени развития школы магии). Сохраняем NewSpells.ini и ini-файл с переводом.
2. Подготовка структур нового заклинания для будущего кода.
2.1. Добавьте идентификатор нового заклинания и увеличьте количество заклинаний в игре на 1:
- Код: Выделить всё
#define SPELLS_NUM 95
...
#define SPL_EXPLOSION 93
#define SPL_WATER_SHIELD 94
Если Вы используете собственную анимацию для нового заклинания, также добавьте её идентификатор в список идентификаторов анимаций и увеличьте количество анимаций на 1:
- Код: Выделить всё
#define ANIMS_NUM 90
...
#define ANIM_INCINERATION 88
#define ANIM_WATER_SHIELD 89
Не забудьте указать номер анимации в NewSpells.ini: <Animation.ID> = 89
Добавьте по одному элементу в каждую из таблиц spellIndirectTableA-F (это будут значения по умолчанию для случая, когда что-то пойдёт не так). Далее приведены значения по умолчанию для бафов/дебафов: таблица A - 4, таблица B - 17, таблица C - 32, таблица D - 9, таблица E - 16, таблица F - 9. В будущих версиях плагина я всё перепроверю и постараюсь избавиться от spellIndirectTable.
2.2. В общем случае потребуется завести структуру для хранения параметров нового заклинания, а также массив таких структур для каждого отряда на поле боя. Делается это следующим образом:
- Код: Выделить всё
struct WaterShieldSpell
{
int defenseBonus;
}
waterShieldSpell[2][21];
Здесь в поле defenseBonus мы будем хранить бонус к защите, получаемый отрядом при касте заклинания
Water Shield. Для простых заклинаний можно использовать поле expertise и хранить там уровень школы магии, а величины эффектов брать из NewSpells.ini ("<Effect.None>" .. "<Effect.Expert>"), однако в нашем случае у заклинания два параметра: фиксированный и прибавка в процентах от базовой защиты, а в общем случае они могут быть разными (например, 1 + 40% или 3 + 80% и т.п.), поэтому чтобы не усложнять код сверх необходимого, будем считать эту прибавку лишь однажды (при наложении заклинания прямо в коде функции applySpell()).
2.3. Добавьте идентификатор и название секции заклинания в следующие два массива:
- Код: Выделить всё
const int newSpell[] =
{
SPL_POISON, SPL_DISEASE, SPL_AGE, SPL_FEAR, SPL_DEATH_CLOUD_NEW,
SPL_DEATH_BLOW, SPL_DRAIN_LIFE, SPL_SPRITE, SPL_MAGIC_ELEMENTAL, SPL_FIREBIRD,
SPL_MOBILITY, SPL_EYE_OF_THE_MAGI, SPL_TOUGHNESS, SPL_BEHEMOTHS_CLAWS, SPL_INCINERATION,
SPL_EXPLOSION, SPL_WATER_SHIELD
};
const char* newSpellName[] =
{
"Poison", "Disease", "Age", "Fear", "Death Cloud",
"Death Blow", "Drain Life", "Summon Sprite", "Summon Magic Elemental", "Summon Firebird",
"Mobility", "Eye of the Magi", "Toughness", "Behemoth's Claws", "Incineration",
"Explosion", "Water Shield"
};
2.4. В функцию afterInit() добавляем инициализацию новой анимации:
- Код: Выделить всё
// Water Shield
SpellAnim[ANIM_WATER_SHIELD].defName = "WaterShield.def";
SpellAnim[ANIM_WATER_SHIELD].name = "WaterShield";
SpellAnim[ANIM_WATER_SHIELD].type = 1;
Если Вы хотите инициализировать параметры заклинания для отрядов перед битвой, делайте это в функции initSpellMods(). Для
Water Shield ничего инициализировать не нужно. Повезло. Но имейте в виду, что такая возможность имеется, если будете пробовать добавлять более сложные заклинания.
3. Переходим к коду. Для бафов/дебафов обычно достаточно двух функций: applySpell(), которая вызывается при касте заклинания, и resetSpell(), которая вызывается при его снятии (Cure, Dispel, окончание действия). Так как у нашего заклинания есть дополнительный эффект, то нам потребуется ещё одна функция, выполняющаяся в начале каждого игрового раунда - newRoundSpellSettings().
3.1. Пишем код для applySpell(). Здесь нужно добавить кейс для
Water Shield:
- Код: Выделить всё
case SPL_WATER_SHIELD:
if (schoolLevel == SL_NONE || schoolLevel == SL_BASIC)
waterShieldSpell[stack->side][stack->index_on_side].defenseBonus += (int)(1 + o_pCreatureInfo[stack->creature_id].defence * 0.1);
if (schoolLevel == SL_ADVANCED)
waterShieldSpell[stack->side][stack->index_on_side].defenseBonus += (int)(2 + o_pCreatureInfo[stack->creature_id].defence * 0.2);
if (schoolLevel == SL_EXPERT)
waterShieldSpell[stack->side][stack->index_on_side].defenseBonus += (int)(3 + o_pCreatureInfo[stack->creature_id].defence * 0.3);
stack->creature.defence += waterShieldSpell[stack->side][stack->index_on_side].defenseBonus;
break;
Желательно, конечно, иметь базовые знания C++, но можно и без них, если делать всё по аналогии. stack->side - сторона отряда (атакующая/обороняющаяся), stack->index_on_side - порядковый номер отряда у атакующей/обороняющейся стороны, o_pCreatureInfo[stack->creature_id].defence - базовая защита существа в отряде. Дальше - математика.
3.2. Пишем код для resetSpell(). Аналогично пункту 3.1 добавляем новый кейс для
Water Shield. Теперь мы забираем бонус (waterShieldSpell[stack->side][stack->index_on_side].defenseBonus) обратно:
- Код: Выделить всё
case SPL_WATER_SHIELD:
stack->creature.defence -= waterShieldSpell[stack->side][stack->index_on_side].defenseBonus;
if (stack->creature.defence < 0)
stack->creature.defence = 0;
break;
Последнее условие нужно для того, чтобы защита отряда не упала ниже 0.
3.3. Осталось сделать самое сложное для этого заклинания: снятие случайного негативного эффекта в начале раунда (для этого существует функция newRoundSpellSettings()) - и новое заклинание можно тестировать в бою.
Добавляем условие в newRoundSpellSettings():
- Код: Выделить всё
if (stack->active_spell_duration[SPL_WATER_SHIELD])
{
int negativeSpell[SPELLS_NUM];
int negativeSpellsNum = 0;
for (int i = 0; i < SPELLS_NUM; ++i)
if (stack->active_spell_duration[i] && o_Spell[i].type == -1)
negativeSpell[negativeSpellsNum++] = i;
if (negativeSpellsNum > 0)
stack->CancelIndividualSpell(negativeSpell[Randint(0, negativeSpellsNum - 1)]);
}
Здесь мы в цикле проходим по всем заклинаниям, которые висят на отряде, и снимаем случайное негативное. При желании можно добавить анимацию снятия заклинания, если какое-то было действительно снято, но реализация этого выходит за рамки данного краткого гайда.
Вот и всё. Новое заклинание готово радовать игроков