Список изменений
Версия 1.1+
- исправлен маленький баг, когда при шансе chance выпадало число chance и способность не срабатывала (пропустил в условии <= меньше ИЛИ РАВНО);
- высота снаряда при появлении теперь также зависит от высоты кастера.
Версия 1.1
- убраны все требования, кроме JNGP;
- убрана вариативность использования урона из-за ненадобности/собственной надстройки;
- добавлена возможность блокировать способности, чтобы те не вызывали эффект;
- добавлена возможность вызывать эффект исключительно разрешенным способностям;
- добавлен шанс срабатывания (по умолчанию – 100%);
- чуть больше комментариев.
Небольшая наработка немного импровизированной способности. cJass + MUI.
Идея by A.W.K., реализация Atesla. Некоторые наработки взяты из карты NazarPunk для более углубленной настройки библиотеки.
Идея by A.W.K., реализация Atesla. Некоторые наработки взяты из карты NazarPunk для более углубленной настройки библиотеки.
Инструкция по импорту:
Примечание: у AddSpellActive() приоритет над AddSpellBlocked(), поэтому нужно использовать что-то одно.
- скопировать способность "Парад огня" и юнита "Дамми_Фаербол";
- скопировать папку со скриптами "Spell";
- настроить в библиотеке id способности и дамми, если они отличны от оригинала;
- выдать способность необходимым юнитам;
- опционально: настроить разрешенные/заблокированные способности, которые вызывают эффект "Парада огня", функции:
Примечание: у AddSpellActive() приоритет над AddSpellBlocked(), поэтому нужно использовать что-то одно.
Описание:
При использовании ЛЮБОЙ/РАЗРЕШЕННОЙ (в зависимости от настройки) способности мгновенно выпускает в случайного врага в радиусе radius (по умолчанию – 800м) огненный шар. Шар наносит урон, зависящий от потраченной на заклинание маны. Пассивная способность.
В карте для примера были заблокированы способности: Огненный столб (AHfs), Винные пары (ANdh), Леденящий крик (ANht)
В карте для примера были заблокированы способности: Огненный столб (AHfs), Винные пары (ANdh), Леденящий крик (ANht)
Код способности
#include "cj_types.j"
#include "cj_antibj_base.j"
native UnitAlive takes unit id returns boolean
library FireBall initializer Init {
/* Эти переменные не трогаем */
private timer fb_timer = CreateTimer(); // Обязательная переменная. Глобальный таймер, который проходит по массиву
private timer fb_timer_mana = CreateTimer(); // Таймер для маны
private int fb_count = 0; // Количество срабатываний способности. Для поддержки MUI
private bool fb_timer_active = false; // Активен ли таймер в данный момент
private float fb_mana[]; // Сюда заносим ману
private unit fb_unit[]; // Здесь хранятся кастеры
private int spells_blocked[]; // Сюда заносим способности, которые не срабатывают с пассивкой
private int spells_blocked_count = 0; // Кол-во заблоченных способностей
private int spells_active[]; // Сюда заносятся способности, которые срабатывают с пассивкой. Игнорирует переменную выше
private int spells_active_count = 0; // Кол-во срабатываемых способностей
/* Настраиваемые переменные */
private constant integer id = 'u000'; // Дамми-снаряд
private constant player owner = Player( PLAYER_NEUTRAL_PASSIVE ); // Владелец дамми-снаряда
private constant int pf = 'APOF'; // ID необходимой способности
private constant int chance = 100; // Шанс срабатывания способности в процентах, от 1 до 100
private constant string eff = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl"; // Эффект при взрыве шара
private constant string att = "chest"; // На каком участке тела взрывается эффект
private constant float period = .03125; // Период таймера снаряда: 1/32 секунды
private constant float speed = 800. / ( 1. / period ); // Расстояние, которое снаряд пройдёт за каждый тик таймера
private constant float radius = 800.; // Радиус применения шара
private constant float distance = 50.; // Необходимое расстояние между шаром и целью, чтобы шар взорвался и нанес урон
private constant float z = 75.; // Высота появления снаряда
private void AddSpellBlocked( int spell ) {
spells_blocked[spells_blocked_count] = spell;
spells_blocked_count++;
}
private void AddSpellActive( int spell ) {
spells_active[spells_active_count] = spell;
spells_active_count++;
}
private float DistanceBetweenCoords( float x1, float y1, float x2, float y2 ) { // Расстояние между двумя точками
float dx = x2 - x1;
float dy = y2 - y1;
return SquareRoot( dx * dx + dy * dy );
}
private void onDamage( unit hero, unit enemy, float damage ) { // Нанесение урона и вызов эффекта
UnitDamageTarget( hero, enemy, damage, false, true, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_FIRE, null );
DestroyEffect( AddSpecialEffectTarget( eff, enemy, att ) );
}
private void GetAbilityManaCost() { // Проходимся по массиву и отмеряем ману, передаем ее в стуктуру
int count = fb_count - 1;
loop {
exitwhen count < 0
fb_mana[count] = fb_mana[count] - GetUnitState( fb_unit[count], UNIT_STATE_MANA ) + 0.5;
fb.new( fb_unit[count], count );
count--;
}
if count < 0 {
fb_count = 0;
fb_timer_active = false;
PauseTimer( fb_timer_mana );
}
}
struct fb extends array{ // Структура способности
unit caster;
unit dummy;
unit target;
float mana_cost;
float x;
float y;
static thistype count = 0;
static thistype new( unit caster, int count_fb ) {
thistype this = count;
count++;
this.caster = caster;
this.x = GetUnitX( caster );
this.y = GetUnitY( caster );
this.target = null;
this.mana_cost = I2R( R2I( fb_mana[count_fb] ) );
group G = CreateGroup();
unit enemy = null;
player pc = GetOwningPlayer( caster );
GroupEnumUnitsInRange( G, x, y, radius, null );
loop{
enemy = GroupPickRandomUnit( G );
if ( UnitAlive( enemy ) && IsPlayerEnemy( GetOwningPlayer( enemy ), pc ) ){
this.target = enemy;
this.dummy = CreateUnit( owner, id, x, y, GetUnitFacing( this.caster ) );
SetUnitState( this.dummy, UNIT_STATE_MANA, GetUnitState( this.dummy, UNIT_STATE_MAX_MANA ) );
SetUnitFlyHeight( this.dummy, GetUnitFlyHeight( caster ) + z, 0. );
}
GroupRemoveUnit( G, enemy );
exitwhen ( this.target != null || enemy == null )
}
if ( this.dummy == null ) { delete(); }
GroupClear( G );
DestroyGroup( G );
G = null;
return 0;
}
bool continue( float x, float y, float xc, float yc ) { return ( DistanceBetweenCoords( x, y, xc, yc ) <= distance || !UnitAlive( target ) ) }
bool actions() {
float xd = GetUnitX( dummy );
float yd = GetUnitY( dummy );
float xt = GetUnitX( target );
float yt = GetUnitY( target );
if ( continue( xd, yd, xt, yt ) ) { return delete(); }
float zt = GetUnitFlyHeight( target ) + z;
float a = Atan2( yt - yd, xt - xd );
float d = DistanceBetweenCoords( xd, yd, xt, yt );
SetUnitX(dummy, xd + speed * Cos( a ) );
SetUnitY(dummy, yd + speed * Sin( a ) );
SetUnitFlyHeight( dummy, d + zt, 1000. );
SetUnitFacing( dummy, a );
return true;
}
bool delete() {
RemoveUnit( dummy );
if ( UnitAlive( target ) ) { onDamage( caster, target, mana_cost ); }
caster = null;
dummy = null;
target = null;
count--;
if ( ( this ) < ( count ) ){
caster = count.caster;
dummy = count.dummy;
target = count.target;
mana_cost = count.mana_cost;
x = count.x;
y = count.y;
return false;
}
return true;
}
}
private void update() {
fb i = fb.count - 1;
loop{
exitwhen i < 0
if ( i.actions() ) { i--; }
}
}
private void FireBall_Init() { // Функция, вызываемая при срабатывании
if ( GetRandomInt( 1, 100 ) <= chance ) {
fb_unit[fb_count] = GetTriggerUnit();
fb_mana[fb_count] = GetUnitState( fb_unit[fb_count], UNIT_STATE_MANA );
fb_count++;
if !fb_timer_active {
fb_timer_active = true;
TimerStart( fb_timer_mana, .010, true, function GetAbilityManaCost );
}
}
}
private bool GetFBSpells( int spell ) {
int count = 0;
bool have_spell = false;
if ( spells_active_count > 0 ) {
count = spells_active_count - 1;
loop{
exitwhen have_spell || count < 0
if ( spells_active[count] == spell ) { have_spell = true; }
count--;
}
return have_spell;
} if ( spells_blocked_count > 0 ) {
have_spell = true;
count = spells_blocked_count - 1;
loop{
exitwhen !have_spell || count < 0
if ( spells_blocked[count] == spell ) { have_spell = false; }
count--;
}
} else { have_spell = true; }
return have_spell;
}
private bool FireBall_Cond() { return ( GetUnitAbilityLevel( GetTriggerUnit(), pf ) > 0 && GetFBSpells( GetSpellAbilityId() ) ); }
private void Init() {
DoNotSaveReplay();
FogEnable( false );
FogMaskEnable( false );
Preload( eff );
AddSpellBlocked( 'AHfs' ); AddSpellBlocked( 'ANdh' ); AddSpellBlocked( 'ANht' );
trigger t = CreateTrigger();
TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT );
TriggerAddCondition( t, Condition( function FireBall_Cond ) );
TriggerAddAction( t, function FireBall_Init );
TimerStart( fb_timer, period, true, function update );
}
}
На новых патчах не сижу до Рефорджа и не вижу смысла писать об этом.
А система, чтобы не плодить другую систему. Мне лично эта вкатывает.
Здесь всё только усложнили, да и при чём она актуальна только для 1.26
Ред. Atesla
UPD: Впрочем, у кого будет идея безболезненно и без багов сделать систему, которая узнает затраты маны на спеллы, то можно писать сюда.
Кому надо будет: xgm.guru/p/wc3/153351?postid=285485
Ред. Феникс
Ред. nazarpunk