Добавлен nazarpunk,
опубликован
История одного заклинания
Содержание:
В прошлой статье мы только привели наше заклинание в читаемую форму. Теперь же на его примере рассмотрим принцип Dummy cast.
Идея
Наше заклинание не затрагивает дружестенную нежить, враждебнуюю ненежить и самого кастера. Так наколдуем на все дружественные войска Духовное пламя, на врагов Замедление, а кастеру добавим 25% здоровья от излеченного. Если он никого не излечил, наколдуем Рёв.
Dummy cast
Так как Warcraft требует юнита, для того что-бы накладывать заклинания, то дадим ему его. И не нужно слушать лукавых, которые подбивают указывать в качестве модели .mdx. Плодить костыли, даже если движок вам об этом напрямую не говорит, это плохо. Дамми каст придуман не вчера и найти нужную модель не так уже и сложно.
После сих нехитрых действий можно приступать к написанию кода. В прошлый раз я привёл такую конструкцию.
library SpellMassHeal initializer onInit
function onInit takes nothing returns nothing
call BJDebugMsg("Мой первый vJass")
endfunction
endlibrary
Теперь же мы сделаем практически тоже самое но с объяснениями.
library Dummy {
function onInit(){
BJDebugMsg("Дамми каст не так уж и сложен!");
}
}
Когда я только начал изучать редактор то соответсвенно начал с триггеров. И в них мне срзу непонравился подход к переменным, сложенным в одну кучу. Потом перейдя на JASS, мне тоже не нравилось, что функции находятся в одной области видимости и приходилось изобретать хитроумные префиксы, чтоб в итоге не заблудиться в своём коде. Наконец-то придумали vJASS и его строгий синтаксис ZINC. Вот тогда то я и вздохнул с облегчением, потому что там появились библиотеки.
library Dummy {
}
Они позволяли изолировать одни функции от других и разбить код на логические модули. Вот и сейчас мы, вместо того, чтобы засунуть работу с дамми в заклинание, а потом копировать одни и теже строки в каждое заклинание, создадим себе универсальную библиотеку.
library Dummy {
integer DummyID = 'u000'; // равкод дамми
}
Теперь вспомним, какие заклинания нам нужно наложить. Это Рёв, Духовное пламя и Замедление. Создадим для этого функции.
library Dummy {
integer DummyID = 'u000'; // равкод дамми
public {
// Наложение бафов наподобии Духовное пламя, Замедление
function DummyCastBuff(player caster, unit target, integer abilityId, string order, real lifetime){
}
// Наложение бафов наподобии Рёв
function DummyCastAOE(player caster, real x, real y, integer abilityId, string order, real lifetime){
}
}
}
Блок public означает, что эти функции будут доступны из других библиотек. Осталось дописать необходимые действия.
library Dummy {
integer DummyID = 'u000'; // равкод дамми
public {
// Наложение бафов наподобии Духовное пламя, Замедление
function DummyCastBuff(player caster, unit target, integer abilityId, string order, real lifetime){
unit dummy;
// Небудем указывать мёртвых юнитов в качестве цели;
if (GetUnitState(target, UNIT_STATE_LIFE) > 0.405){
dummy = CreateUnit(caster, DummyID, GetUnitX(target), GetUnitY(target), 0.); // Создадим дамми в координатах цели
UnitApplyTimedLife(dummy, 'BTLF', lifetime); // Укажем время жизни дамми
UnitAddAbility(dummy, abilityId);
IssueTargetOrder(dummy, order, target);
dummy = null;
}
}
// Наложение бафов наподобии Рёв
function DummyCastAOE(player caster, real x, real y, integer abilityId, string order, real lifetime){
unit dummy = CreateUnit(caster, DummyID, x, y, 0.); // Создадим дамми переданных координатах
UnitApplyTimedLife(dummy, 'BTLF',lifetime); // Укажем время жизни дамми
UnitAddAbility(dummy, abilityId);
IssueImmediateOrder(dummy, order);
dummy = null;
}
}
}
Доработка заклинания
Закончив с подготовкой, можно переходить к самому вкусному.
library SpellMassHeal requires Dummy {
В блоке requires мы указываем, какие библиотеки использует наше заклинание. При компиляции в JASS код заклинания будет расположен ниже кода используемых библиотек и не вызовет ошибок. На сей раз не будем создавать нестандартные способности, а просто уберём требования из стандартных и поднастроим наше заклинание.
library SpellMassHeal requires Dummy {
integer AllyBuffID = 'Ainf'; // равкод способности Духовное пламя
string AllyBuffOrder = "innerfire"; // приказ способности Духовное пламя
integer EnemyBuffID = 'Aslo'; // равкод способности Замедление
string EnemyBuffOrder = "slow"; // приказ способности Замедление
integer CasterBuffID = 'Aroa'; // равкод способности Рёв
string CasterBuffOrder = "roar"; // приказ способности Рёв
Когда наш код влазил в монитор, то переменные
unit tu = GetTriggerUnit(); // Сократим для читабельности
unit u; // Заводим локального юнита для перебора сразу
не вызывали ужаса и сокращали код, но с его расширением нужно дать им более понятное название и заодно наложить бафы на юнитов.
// Перебираем юнитов в группе
while (true) {
target = FirstOfGroup(g); // Выбираем первого юнита в группе
if (target == null) { break; } // Если группа пустая, юнит не выберется и мы выйдем из цикла
// Если юнит не сам кастер
if (caster != target) {
// Дружественные войска
if (IsPlayerAlly(GetOwningPlayer(caster), GetOwningPlayer(target))){
// Кастуем Духовное пламя
DummyCastBuff(GetOwningPlayer(caster), target, AllyBuffID, AllyBuffOrder, 0.1);
// Лечим не нежить
if (IsUnitType(target, UNIT_TYPE_UNDEAD) == false && GetUnitState(target, UNIT_STATE_LIFE) < GetUnitState(target, UNIT_STATE_MAX_LIFE)){
// Добавим здоровья в размере: уровень способности * HealAlly
SetUnitState(target, UNIT_STATE_LIFE, GetUnitState(target, UNIT_STATE_LIFE) + AbilityLevel * HealAlly);
// Сразу уничтожим проигранный эффект
DestroyEffect(AddSpecialEffectTarget(HealEffect, target, "origin"));
}
}
// Враждебные войска
if (IsPlayerEnemy(GetOwningPlayer(caster), GetOwningPlayer(target))){
// Кастуем Замедление
DummyCastBuff(GetOwningPlayer(caster), target, EnemyBuffID, EnemyBuffOrder, 0.1);
// Наносим урон нежити
if (IsUnitType(target, UNIT_TYPE_UNDEAD)){
// Наносим урон от лица кастера в размере: уровень способности * DamageEnemy
UnitDamageTarget(caster, target, AbilityLevel * DamageEnemy, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS);
// Сразу уничтожим проигранный эффект
DestroyEffect(AddSpecialEffectTarget(DamageEffect, target, "origin"));
}
}
}
GroupRemoveUnit(g, target); // Убираем юнита из группы
}
С кастером так просто не получится ибо нам нужно посчитать количество вылеченного здоровья. Так сделаем это
//! zinc
library SpellMassHeal requires Dummy {
// Вне функции переменные будут приватными и глобальными. Нет смысла лишний раз это указывать
integer AbilityID = 'A000'; // Указываем равкод нашей способности, чтоб можно было удобно изменить
real HealAlly = 100.0; // На сколько умножить уровень способности, для лечения
real DamageEnemy = 50.0; // На сколько умножить уровень способности, для урона
string HealEffect = "Effect\\Holy_Heal_Small.mdx";
string DamageEffect = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl";
real HealCaster = 0.25; // На сколько будем умножать вылеченное здоровье, чтобы вылечить кастера
integer AllyBuffID = 'Ainf'; // равкод способности Духовное пламя
string AllyBuffOrder = "innerfire"; // приказ способности Духовное пламя
integer EnemyBuffID = 'Aslo'; // равкод способности Замедление
string EnemyBuffOrder = "slow"; // приказ способности Замедление
integer CasterBuffID = 'Aroa'; // равкод способности Рёв
string CasterBuffOrder = "roar"; // приказ способности Рёв
//функция onInit вызывается автоматически
function onInit() {
trigger t = CreateTrigger();
integer i = 0;
// Добавляем условие срабатывания триггера
for (0 <= i < bj_MAX_PLAYER_SLOTS) {
TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null);
}
// Условие срабатывания триггера
TriggerAddCondition(t, Condition( function()-> boolean {
return GetSpellAbilityId() == AbilityID;
}));
// Основные действия триггера
TriggerAddAction(t, function() {
group g = CreateGroup(); // Создаём группу
unit caster = GetTriggerUnit(); // Сократим для читабельности
unit target; // Заводим локального юнита для перебора сразу
real x = GetUnitX(caster); // По возможности используем координаты,
real y = GetUnitY(caster); // что-бы не плодить лишние location
real range = 400.0; // Устанавливаем радиус способности
real AbilityLevel = I2R(GetUnitAbilityLevel(caster, AbilityID));
real CountCasterHeal = 0.0; // Заведём счётчик вылеченного здоровья
real CountMustHeal = 0.0; // Она нам понадобится чуть ниже
// Добавляем в группу живых юнитов вокруг кастера
GroupEnumUnitsInRange(g, x, y, range, Condition( function() -> boolean {
// Проверяем жив ли юнит
return GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0.405;
}));
// Перебираем юнитов в группе
while (true) {
target = FirstOfGroup(g); // Выбираем первого юнита в группе
if (target == null) { break; } // Если группа пустая, юнит не выберется и мы выйдем из цикла
// Если юнит не сам кастер
if (caster != target) {
// Дружественные войска
if (IsPlayerAlly(GetOwningPlayer(caster), GetOwningPlayer(target))){
// Кастуем Духовное пламя
DummyCastBuff(GetOwningPlayer(caster), target, AllyBuffID, AllyBuffOrder, 0.1);
// Лечим не нежить
if (IsUnitType(target, UNIT_TYPE_UNDEAD) == false && GetUnitState(target, UNIT_STATE_LIFE) < GetUnitState(target, UNIT_STATE_MAX_LIFE)){
// Добавим здоровья в размере: уровень способности * HealAlly
CountMustHeal = AbilityLevel * HealAlly; // Сколько мы должны вылечить
SetUnitState(target, UNIT_STATE_LIFE, GetUnitState(target, UNIT_STATE_LIFE) + CountMustHeal);
// Добавим вылеченное здоровье в общий счётчик
CountCasterHeal = CountCasterHeal + RMinBJ(CountMustHeal, GetUnitState(target, UNIT_STATE_MAX_LIFE) - GetUnitState(target, UNIT_STATE_LIFE));
// Сразу уничтожим проигранный эффект
DestroyEffect(AddSpecialEffectTarget(HealEffect, target, "origin"));
}
}
// Враждебные войска
if (IsPlayerEnemy(GetOwningPlayer(caster), GetOwningPlayer(target))){
// Кастуем Замедление
DummyCastBuff(GetOwningPlayer(caster), target, EnemyBuffID, EnemyBuffOrder, 0.1);
// Наносим урон нежити
if (IsUnitType(target, UNIT_TYPE_UNDEAD)){
// Наносим урон от лица кастера в размере: уровень способности * DamageEnemy
UnitDamageTarget(caster, target, AbilityLevel * DamageEnemy, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS);
// Сразу уничтожим проигранный эффект
DestroyEffect(AddSpecialEffectTarget(DamageEffect, target, "origin"));
}
}
}
GroupRemoveUnit(g, target); // Убираем юнита из группы
}
DestroyGroup(g); // Удаляем группу
// Лечим кастера или кастуем Рёв
if (CountCasterHeal > 0.0) {
// Добавим здоровья кастеру
SetUnitState(caster, UNIT_STATE_LIFE, GetUnitState(caster, UNIT_STATE_LIFE) + CountCasterHeal * HealCaster);
// Сразу уничтожим проигранный эффект
DestroyEffect(AddSpecialEffectTarget(HealEffect, caster, "origin"));
} else {
// Кастуем Рёв, а так как анимация заклинания крепится к origin даммика,
// дадим даммику пожить подольше чем 0.1 секунды
DummyCastAOE(GetOwningPlayer(caster), GetUnitX(caster), GetUnitY(caster), CasterBuffID, CasterBuffOrder, 1.5);
}
/*
По идее мы должны ещё вызвать
GroupClear(g)
target = null
но мы это уже сделали при переборе
*/
caster = null;
g = null; // Очищаем переменную группы
});
t = null; // Так как триггер у нас в локальной переменной, то нужно её очистить.
}
}
//! endzinc
Придумать красивое описание к заклинанию я снова оставлю в качестве домашнего задания.
Содержание
`
ОЖИДАНИЕ РЕКЛАМЫ...
1
sLIL MID
3 года назад
1
Очень полезно, спасибо.
0
Берги
3 года назад
0
sLIL MID, Тут кстати больше инфы
https://xgm.guru/p/wc3/dummy
Особенно есть настройка самого даммика в РО, чего нет в этой статье
0
Гуванч
3 года назад
0
Имхо обычный джасс намного удобней и понятней для меня так не знаю чего люди вцепились в эти цджасс и вджасс
0
rsfghd
3 года назад
0
Гуванч, для начала тебе нужно понять что даёт сджасс и вджасс))
Чтобы оставить комментарий, пожалуйста, войдите на сайт.