Добавлен nazarpunk,
опубликован
Алгоритмы, Наработки и Способности
Способ реализации:
Zinc
Тип:
Способность
Версия Warcraft:
1.26+
Blink Strike
MUI: да
Импорт: иконка
Утечки: нет
Требования: JNGP
Описание: Герой телепортируется к каждой цели на своём пути и наносит ей 100*уровень заклинания магического урона.
Импорт: иконка
Утечки: нет
Требования: JNGP
Описание: Герой телепортируется к каждой цели на своём пути и наносит ей 100*уровень заклинания магического урона.
- Имеет баф, для работы с другими заклинаниями
- Прерывается оглушением и приказами игрока
- Учитывает неуязвимость к магии
- Гнев деревьев блокирует заклинание
- Не использует даммикаст
- Не паузит героя
- Использует хэштаблицу для хранения id таймера
Технические подробности
Перенос в свою карту
Способности
- 'AEbs' Blink Strike (герой) - способность для героя
- 'ABbs' Blink Strike (бафф) - способность для наложения бафа
Заклинания/Эффекты
- 'BEbs' Blink Strike - бафф способности
Триггеры
- SpellBlinkStrike
Импорт
- ReplaceableTextures\CommandButtons\BTNBlinkStrike.blp
- ReplaceableTextures\CommandButtonsDisabled\DISBTNBlinkStrike.blp
- ReplaceableTextures\PassiveButtons\PASBlinkStrike.blp
- ReplaceableTextures\CommandButtonsDisabled\DISPASBlinkStrike.blp
Настройка
constant integer AbilityID = 'AEbs'; // Равкод способности
constant integer AbilityBuffID = 'ABbs'; // Равкод способности для баффа
constant integer BuffID = 'BEbs'; // Равкод баффа
constant integer PathItemID = 'wolg'; // Предмет для нахождения финальной точки
constant real TimerPeriod = 0.25; // Время между блинками: 1/4 секунды.
constant integer DamageDistance = 64; // Расстояние захвата юнита
constant string EffectCasterHandsPath = "Abilities\\Weapons\\AvengerMissile\\AvengerMissile.mdl"; // Еффект, который крепится к "hand left" и "hand right"
constant string EffectBlinkTarget = "Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl"; // Еффект блинка для точки
constant string EffectBlinkCaster = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"; // Еффект блинка для кастера
constant string CasterAnimationName = "Attack 2"; // Анимация при ударе
constant real CasterAnimationDuration = 0.9; // Длительность анимации при ударе у модели
constant integer CasterOpacity = 150; // Прозрачность кастера на момент заклинания 0 - 255
constant real CasterAnimationScale = CasterAnimationDuration/TimerPeriod*1; // Не трогать
hashtable HT = InitHashtable(); // Хэштаблица для таймера
// Можете вписать туда вашу таблицу, например:
// hashtable HT = udg_HashTable;
// Текстаг над целью. Настроен на эмуляцию критического удара
function addTextTag(widget target, string text) {
real x = GetWidgetX(target);
real y = GetWidgetY(target);
texttag tt = CreateTextTag();
SetTextTagText(tt, text+"!", 0.024);
SetTextTagPos(tt, x, y, 0.0);
SetTextTagColor(tt, 255, 0, 0, 255);
SetTextTagVelocity(tt, 0.0, 0.04);
SetTextTagFadepoint(tt, 2.0);
SetTextTagLifespan(tt, 5.0);
SetTextTagVisibility(tt, IsVisibleToPlayer(x, y, GetLocalPlayer()));
SetTextTagPermanent(tt, false);
tt = null;
}
// Нанесение урона по цели
function onDamage(unit caster, unit target, integer level) {
// caster - юнит, использующий способность
// target - цель
// level - уровень способности
real damage = level*100; // считаем урон от уровня способности
UnitDamageTarget(
caster,
target,
damage,
true, // является ли атакой
false, // является ли дальним боем
ATTACK_TYPE_MAGIC,
DAMAGE_TYPE_NORMAL,
WEAPON_TYPE_WHOKNOWS
);
addTextTag(target, I2S(R2I(damage)));
}
// Дальность заклинания
function getRange(integer level) -> integer {
// level - уровень способности
return 800 + level*100;
}
// Ограничить количество целей. Снять ограничение: -1;
function getTargetLimit(integer level) -> integer {
// level - уровень способности
return -1;
}
// Проверяем, возможно ли кастовать заклинание
function canBlink(unit caster) -> boolean {
// caster - юнит, использующий способность
return (
GetUnitAbilityLevel(caster, 'BEer') == 0 // Гнев деревьев
);
}
// Проверка целей
function checkTarget(unit caster, unit target) -> boolean {
// caster - юнит, использующий способность
// target - цель проверки
return (
caster != target // Не кастер
&&
!IsUnitType(target, UNIT_TYPE_STRUCTURE) // Не здание
&&
!IsUnitType(target, UNIT_TYPE_FLYING) // Не летающий
&&
!IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) // Восприимчив к магии
&&
IsPlayerEnemy(GetOwningPlayer(caster), GetOwningPlayer(target)) // Враг
);
}
Код заклинания
//! zinc
library SpellBlinkStrike {
constant integer AbilityID = 'AEbs'; // Равкод способности
constant integer AbilityBuffID = 'ABbs'; // Равкод способности для баффа
constant integer BuffID = 'BEbs'; // Равкод баффа
constant integer PathItemID = 'wolg'; // Предмет для нахождения финальной точки
constant real TimerPeriod = 0.25; // Время между блинками: 1/4 секунды.
constant integer DamageDistance = 64; // Расстояние захвата юнита
constant string EffectCasterHandsPath = "Abilities\\Weapons\\AvengerMissile\\AvengerMissile.mdl"; // Еффект, который крепится к "hand left" и "hand right"
constant string EffectBlinkTarget = "Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl"; // Еффект блинка для точки
constant string EffectBlinkCaster = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"; // Еффект блинка для кастера
constant string CasterAnimationName = "Attack 2"; // Анимация при ударе
constant real CasterAnimationDuration = 0.9; // Длительность анимации при ударе у модели
constant integer CasterOpacity = 150; // Прозрачность кастера на момент заклинания 0 - 255
constant real CasterAnimationScale = CasterAnimationDuration/TimerPeriod*1; // Не трогать
hashtable HT = InitHashtable(); // Хэштаблица для таймера
// Можете вписать туда вашу таблицу, например:
// hashtable HT = udg_HashTable;
// Текстаг над целью. Настроен на эмуляцию критического удара
function addTextTag(widget target, string text) {
real x = GetWidgetX(target);
real y = GetWidgetY(target);
texttag tt = CreateTextTag();
SetTextTagText(tt, text+"!", 0.024);
SetTextTagPos(tt, x, y, 0.0);
SetTextTagColor(tt, 255, 0, 0, 255);
SetTextTagVelocity(tt, 0.0, 0.04);
SetTextTagFadepoint(tt, 2.0);
SetTextTagLifespan(tt, 5.0);
SetTextTagVisibility(tt, IsVisibleToPlayer(x, y, GetLocalPlayer()));
SetTextTagPermanent(tt, false);
tt = null;
}
// Нанесение урона по цели
function onDamage(unit caster, unit target, integer level) {
// caster - юнит, использующий способность
// target - цель
// level - уровень способности
real damage = level*100; // считаем урон от уровня способности
UnitDamageTarget(
caster,
target,
damage,
true, // является ли атакой
false, // является ли дальним боем
ATTACK_TYPE_MAGIC,
DAMAGE_TYPE_NORMAL,
WEAPON_TYPE_WHOKNOWS
);
addTextTag(target, I2S(R2I(damage)));
}
// Дальность заклинания
function getRange(integer level) -> integer {
// level - уровень способности
return 800 + level*100;
}
// Ограничить количество целей. Снять ограничение: -1;
function getTargetLimit(integer level) -> integer {
// level - уровень способности
return -1;
}
// Проверяем, возможно ли кастовать заклинание
function canBlink(unit caster) -> boolean {
// caster - юнит, использующий способность
return (
GetUnitAbilityLevel(caster, 'BEer') == 0 // Гнев деревьев
);
}
// Проверка целей
function checkTarget(unit caster, unit target) -> boolean {
// caster - юнит, использующий способность
// target - цель проверки
return (
caster != target // Не кастер
&&
!IsUnitType(target, UNIT_TYPE_STRUCTURE) // Не здание
&&
!IsUnitType(target, UNIT_TYPE_FLYING) // Не летающий
&&
!IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) // Восприимчив к магии
&&
IsPlayerEnemy(GetOwningPlayer(caster), GetOwningPlayer(target)) // Враг
);
}
//
// Заклинание
//
// Polar - https://xgm.guru/p/wc3/polar
function GetPolarOffsetX(real x, real distance, real angle) -> real {
return x + distance * Cos(angle * bj_DEGTORAD);
}
function GetPolarOffsetY(real y, real distance, real angle) -> real {
return y + distance * Sin(angle * bj_DEGTORAD);
}
function DistanceBetweenCoords(real x1, real y1, real x2, real y2) -> real {
real dx = x2 - x1;
real dy = y2 - y1;
return SquareRoot(dx*dx + dy*dy);
}
function AngleBetweenCoords(real x1, real y1, real x2, real y2) -> real {
return bj_RADTODEG * Atan2(y2 - y1, x2 - x1);
}
function AngleDifference(real a1, real a2) -> real {
real x;
a1 = ModuloReal(a1, 360);
a2 = ModuloReal(a2, 360);
if (a1 > a2) {
x = a1;
a1 = a2;
a2 = x;
}
x = a2 - 360;
if (a2 - a1 > a1 - x){
a2 = x;
}
return RAbsBJ(a1 - a2);
}
function Perpendicular (real Xa, real Ya, real Xb, real Yb, real Xc, real Yc) -> real {
return SquareRoot((Xa - Xc) * (Xa - Xc) + (Ya - Yc) * (Ya - Yc)) * Sin(Atan2(Yc-Ya,Xc-Xa) - Atan2(Yb-Ya,Xb-Xa));
}
// endPolar
function isUnitAlive(unit target) -> boolean{
return GetWidgetLife(target) > 0.405;
}
struct data {
unit caster, target[8190];
integer targetI, targetC, limit, level, timerID;
real xc, yc, xt, yt, angle;
effect effectCaster[2];
boolean isCancel;
static method create(unit caster, real xt, real yt) -> data {
data this = data.allocate();
item it;
integer level = GetUnitAbilityLevel(caster, AbilityID);
real xc = GetUnitX(caster);
real yc = GetUnitY(caster);
real distance = DistanceBetweenCoords(xc, yc, xt, yt);
real angle = AngleBetweenCoords(xc, yc, xt, yt);
real range = I2R(getRange(level));
unit u, utemp;
integer i, j, imin;
timer t;
integer tid;
group g = CreateGroup();
if (distance > range){
xt = GetPolarOffsetX(xc, range, angle);
yt = GetPolarOffsetY(yc, range, angle);
}
range = RMinBJ(distance, range);
it = CreateItem(PathItemID, xt, yt);
this.isCancel = false;
this.level = level;
this.limit = getTargetLimit(this.level);
this.caster = caster;
this.xc = xc;
this.yc = yc;
this.xt = GetItemX(it);
this.yt = GetItemY(it);
RemoveItem(it); it = null;
this.angle = AngleBetweenCoords(this.xc, this.yc, this.xt, this.yt);
this.effectCaster[0] = AddSpecialEffectTarget(EffectCasterHandsPath, this.caster, "hand left");
this.effectCaster[1] = AddSpecialEffectTarget(EffectCasterHandsPath, this.caster, "hand right");
GroupEnumUnitsInRange(g, xc, yc, range + DamageDistance, Filter(function() -> boolean {
return isUnitAlive(GetFilterUnit());
}));
this.targetI = -1;
this.targetC = -1;
while(true){
u = FirstOfGroup(g);
if (u == null) {break;}
if (
checkTarget(this.caster, u)
&&
R2I(RAbsBJ(Perpendicular(this.xc, this.yc, this.xt, this.yt, GetUnitX(u), GetUnitY(u)))) <= DamageDistance
&&
AngleDifference(AngleBetweenCoords(this.xc, this.yc, this.xt, this.yt), AngleBetweenCoords(this.xc, this.yc, GetUnitX(u), GetUnitY(u))) < 60
){
this.targetI = this.targetI + 1;
this.target[targetI] = u;
}
GroupRemoveUnit(g, u);
}
DestroyGroup(g); g = null; u = null;
SetUnitPathing(this.caster, false);
SetUnitTimeScale(this.caster, CasterAnimationScale);
SetUnitVertexColor(this.caster, 255, 255, 255, CasterOpacity);
UnitAddAbility(this.caster, AbilityBuffID);
if (this.targetI >= 0){
t = CreateTimer();
tid = GetHandleId(t);
i = 0;
while(i < this.targetI){
imin = i;
j = i + 1;
while(j <= this.targetI){
if (
DistanceBetweenCoords(this.xc, this.yc, GetUnitX(this.target[j]), GetUnitY(this.target[j]))
<
DistanceBetweenCoords(this.xc, this.yc, GetUnitX(this.target[imin]), GetUnitY(this.target[imin]))
){
imin = j;
}
j = j + 1;
}
utemp = this.target[i];
this.target[i] = this.target[imin];
this.target[imin] = utemp;
i = i + 1;
}
SaveInteger(HT, tid, 0, this);
TimerStart(t, TimerPeriod, true, function data.callback);
} else {
this.destroy();
}
utemp = null;
t = null;
return this;
}
method destroy(){
integer i;
SetUnitPathing(this.caster, true);
UnitRemoveAbility(this.caster, AbilityBuffID);
UnitRemoveAbility(this.caster, BuffID);
if (!this.isCancel){
SetUnitX(this.caster, this.xt);
SetUnitY(this.caster, this.yt);
DestroyEffect(AddSpecialEffectTarget(EffectBlinkCaster, this.caster, "origin"));
}
SetUnitTimeScale(this.caster, 1);
if (isUnitAlive(this.caster)){
SetUnitAnimation(this.caster, "stand");
} else {
SetUnitAnimation(this.caster, "death");
}
SetUnitVertexColor(this.caster, 255, 255, 255, 255);
for(0 <= i <= 1){
DestroyEffect(this.effectCaster[i]);
this.effectCaster[i] = null;
}
for(0 <= i <= this.targetI){
this.target[i] = null;
}
this.caster = null;
this.deallocate();
}
static method callback(){
timer t = GetExpiredTimer();
integer tid = GetHandleId(t);
data this = LoadInteger(HT, tid, 0);
integer order = GetUnitCurrentOrder(this.caster);
this.targetC = this.targetC + 1;
if ((order != 0 && order != 851983 /* attack */ && order != 851972 /* stop */) || !isUnitAlive(this.caster)){
this.isCancel = true;
}
if (this.targetC > this.targetI || this.isCancel || (this.limit >= 0 && this.targetC >= this.limit)){
this.destroy();
PauseTimer(t);
DestroyTimer(t);
FlushChildHashtable(HT, tid);
} else {
if (isUnitAlive(this.target[targetC])){
onDamage(this.caster, this.target[targetC], this.level);
SetUnitX(this.caster, GetUnitX(this.target[targetC]));
SetUnitY(this.caster, GetUnitY(this.target[targetC]));
IssueImmediateOrderById(this.caster, 851972 /* stop */);
SetUnitAnimation(this.caster, CasterAnimationName);
}
this.target[targetC] = null;
}
t = null;
}
}
function onInit(){
integer i;
trigger t1 = CreateTrigger();
trigger t2 = CreateTrigger();
for (0 <= i < bj_MAX_PLAYER_SLOTS){
TriggerRegisterPlayerUnitEvent(t1, Player(i), EVENT_PLAYER_UNIT_SPELL_CAST, null);
TriggerRegisterPlayerUnitEvent(t2, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null);
}
TriggerAddCondition(t1, Filter(function() -> boolean {
if (GetSpellAbilityId() == AbilityID && GetUnitAbilityLevel(GetTriggerUnit(), AbilityBuffID) == 0){
DestroyEffect(AddSpecialEffect(EffectBlinkTarget, GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit())));
}
return false;
}));
TriggerAddCondition(t2, Filter(function() -> boolean {
if (
GetSpellAbilityId() == AbilityID
&&
GetUnitAbilityLevel(GetTriggerUnit(), AbilityBuffID) == 0
&&
canBlink(GetTriggerUnit())
){
data.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY());
}
return false;
}));
t1 = null; t2 = null;
}
}
//! endzinc
`
ОЖИДАНИЕ РЕКЛАМЫ...
Чтобы оставить комментарий, пожалуйста, войдите на сайт.
Только не понял, почему на последней гифке здоровье героя тратится?
Столь мощно Аура Возмездия работает?
да
вот тогда уточнить хочу
это движок считает атакой или все-таки просто уроном?
если в инвентаре есть башер, может ли выпасть баш? и т. п.
Просто нужен подобный скилл, но там должно именно считаться полноценной реальной атакой на каждого врага с возможностью прока баша, молний, срабатывания триггеров "меня атаковали" и т. п. хреновин
Ну идея через мемхак и хэш пилить, в принципе должно работать, но придется чуть попотеть
ClotPh:
В том-то и дело, что нанесение урона юниту и его атака - разные, хотя часто и связанные вещи
Атаку можно имитировать, но это костыль + бд для всех триггеров, триггерящихся на атаку
Мемхак не то чтобы обожают, особенно с ятп появлением его части функций на лицухе, но без него на 1.26 очень многое нормально не сделать
////
Чтобы герой сделал дэш по линии и всех врагов на пути проатаковал обычной атакой за это время, каждого ровно 1 раз
планирую через хэш, сброс кулдауна атаки с правильной скоростью ее анимации
хз, вроде должно получиться
вот именно атака нужна, даже еще раз уточню, а не урон от атаки, чтобы прокали всякие башеры-маелштормы, могли прокнуть криты и т. д.
с уроном-то от атаки это мемхаком вообще раз плюнуть делается