ScSortedSelector - перебор юнитов по удаленности

Добавлен , опубликован
Алгоритмы, Наработки и Способности
Способ реализации:
vJass
Тип:
Наработка
Ничего необычного, но раз требуется - почему бы и не сделать.
Наработка позволяет делать перебор всех юнитов в заданной области от центральных к крайним и наоборот.
Возможности:
  • собственно, перебирать юнитов, для чего используются операторы квадратных скобок [], как у простого массива;
  • каждый новый ScSortedSelector полностью независим от остальных;
  • сортировка_выбором - это реально быстрее, чем производить GroupEnumUnitsInRange рекурсивно;
  • полное 3D пространство, сортировка происходит по отдаленности юнитов в сфере от изначальной позиции;
  • оперирование от произвольной 2D или 3D области (без кастера);
  • оперирование от лица кастера;
  • оперирование от лица кастера, у которого способность с целью;
  • поддержка фильтров и доступ из фильтра к основным данным кастера;
  • возможность "мягко" ограничить макс. число юнитов на каждый отдельный каст (например, для уровней);
  • нормально документирован;
  • есть пара примеров с молниями swdn и просто изменением цвета;
  • богатый набор стандартных фильтров;
Интерфейс управления:
// ScSortedSelector
// by ScorpioT1000, 2013

library ScSortedSelector
{
// Available standard filters // Доступные стандартные фильтры
boolexpr SC_FILTER_ANY;                                    
boolexpr SC_FILTER_ANY_NOT_CASTER;                         
boolexpr SC_FILTER_NOT_DEAD_NOT_CASTER;                    
boolexpr SC_FILTER_ALLY_NOT_DEAD_NOT_CASTER;               
boolexpr SC_FILTER_ALLY_DEAD_NOT_CASTER;                   
boolexpr SC_FILTER_ENEMY_NOT_DEAD_NOT_CASTER;              
boolexpr SC_FILTER_ENEMY_DEAD_NOT_CASTER;                  
boolexpr SC_FILTER_SAMEPLAYER_NOT_DEAD_NOT_CASTER;         
boolexpr SC_FILTER_SAMEPLAYER_DEAD_NOT_CASTER;             
boolexpr SC_FILTER_SAMEPLAYER_OR_ALLY_NOT_DEAD_NOT_CASTER; 
boolexpr SC_FILTER_SAMEPLAYER_OR_ALLY_NOT_DEAD;            
boolexpr SC_FILTER_ALLY_NOT_DEAD;                          
boolexpr SC_FILTER_DEAD_NOT_CASTER;                        

// Returns current filter ScSelector caster. Works only with NewFromUnit method!
// Возвращает заклинателя текущего фильтра ScSelector. Работает только с методом NewFromUnit!
#define ScGetFilterCaster()  = ScSelector.getfiltercaster
// Returns current filter ScSelector start coords // Возвращает начальные координаты текущего фильтра ScSelector
#define ScGetFilterX()       = ScSelector.getfilterx
#define ScGetFilterY()       = ScSelector.getfiltery
#define ScGetFilterZ()       = ScSelector.getfilterz
// Returns current filter ScSelector target; already defined in Common.j // Возвращает цель текущего фильтра ScSelector
//     GetEnumUnit           = GetEnumUnit



struct ScSelector
{    
    // Creates a new ScSelector from the specified unit coords (for no-target spells)
    // Создает новый ScSelector из указанных координат юнита (для способностей без цели)
    static thistype NewFromUnit(unit caster, \
                                real radius, \        // = 500.0
                                boolexpr filter, \    // = SC_FILTER_ENEMY_NOT_DEAD_NOT_CASTER 
                                bool reverseSort, \   // = false 
                                int maxUnitsAffected) // = -1
    {
        return thistype.doCreate(GetUnitX(caster),GetUnitY(caster),GetUnitZ(caster),radius,filter,caster,reverseSort,maxUnitsAffected);
    }
    
    // Creates a new ScSelector in 3D from the specified custom coords and caster unit (for targeted spells)
    // Создает новый ScSelector в 3D из указанных нестандартных координат и юнита-заклинателя (для способностей с целью)
    static thistype NewFromTarget(unit caster, \
                                  real x, \
                                  real y, \
                                  real radius, \        // = 500.0
                                  boolexpr filter, \    // = SC_FILTER_ENEMY_NOT_DEAD_NOT_CASTER 
                                  bool reverseSort, \   // = false 
                                  int maxUnitsAffected) // = -1
    {
        return thistype.doCreate(x,y,thistype.GetZ(x,y),radius,filter,caster,reverseSort,maxUnitsAffected);
    }
    
    // Creates a new ScSelector in 2D, doesn't match z coord this time
    // Создает новый ScSelector в 2D, не учитывает z координату на этот раз
    static thistype New(real x, \
                        real y, \
                        real radius, \        // = 500.0
                        boolexpr filter, \    // = SC_FILTER_ENEMY_NOT_DEAD_NOT_CASTER 
                        bool reverseSort, \   // = false 
                        int maxUnitsAffected) // = -1
    { 
        return thistype.doCreate(x,y,-110000,radius,filter,null,reverseSort,maxUnitsAffected);
    }
    
    // Creates a new ScSelector in 3D // Создает новый ScSelector в 3D
    static thistype New3D(real x, \
                          real y, \
                          real z, \
                          real radius, \        // = 500.0
                          boolexpr filter, \    // = SC_FILTER_ENEMY_NOT_DEAD_NOT_CASTER 
                          bool reverseSort, \   // = false
                          int maxUnitsAffected) // = -1 
    { 
        return thistype.doCreate(x,y,z,radius,filter,null,reverseSort,maxUnitsAffected); 
    }
    
    // Deletes existing ScSelector
    void Delete() 
    {     
        .Clear();
        .destroy(); 
    }
    
    // You can access units using [] operator
    unit operator[](int index) {
        if(index > .c) { return null; }
        return .u[index];
    }
    // You can change units using []= operator
    void operator[]=(int index, unit value) {
        if(index > .c) { return; }
        .u[index] = value;
    }
    // Returns size of array
    int Size() { return .c; }
    
    // void Sort(bool reverse);
    // void Select(boolexpr filter);
    // void Clear();
    // void Print();
Full Source
#include "cj_types.j"

// ScSortedSelector
// by ScorpioT1000, 2013

library ScSortedSelector
{
// Available standard filters // Доступные стандартные фильтры
boolexpr SC_FILTER_ANY;                                    
boolexpr SC_FILTER_ANY_NOT_CASTER;                         
boolexpr SC_FILTER_NOT_DEAD_NOT_CASTER;                    
boolexpr SC_FILTER_ALLY_NOT_DEAD_NOT_CASTER;               
boolexpr SC_FILTER_ALLY_DEAD_NOT_CASTER;                   
boolexpr SC_FILTER_ENEMY_NOT_DEAD_NOT_CASTER;              
boolexpr SC_FILTER_ENEMY_DEAD_NOT_CASTER;                  
boolexpr SC_FILTER_SAMEPLAYER_NOT_DEAD_NOT_CASTER;         
boolexpr SC_FILTER_SAMEPLAYER_DEAD_NOT_CASTER;             
boolexpr SC_FILTER_SAMEPLAYER_OR_ALLY_NOT_DEAD_NOT_CASTER; 
boolexpr SC_FILTER_SAMEPLAYER_OR_ALLY_NOT_DEAD;            
boolexpr SC_FILTER_ALLY_NOT_DEAD;                          
boolexpr SC_FILTER_DEAD_NOT_CASTER;                        

// Returns current filter ScSelector caster. Works only with NewFromUnit method!
// Возвращает заклинателя текущего фильтра ScSelector. Работает только с методом NewFromUnit!
#define ScGetFilterCaster()  = ScSelector.getfiltercaster
// Returns current filter ScSelector start coords // Возвращает начальные координаты текущего фильтра ScSelector
#define ScGetFilterX()       = ScSelector.getfilterx
#define ScGetFilterY()       = ScSelector.getfiltery
#define ScGetFilterZ()       = ScSelector.getfilterz
// Returns current filter ScSelector target; already defined in Common.j // Возвращает цель текущего фильтра ScSelector
//     GetEnumUnit           = GetEnumUnit



struct ScSelector
{    
    // Creates a new ScSelector from the specified unit coords (for no-target spells)
    // Создает новый ScSelector из указанных координат юнита (для способностей без цели)
    static thistype NewFromUnit(unit caster, \
                                real radius, \        // = 500.0
                                boolexpr filter, \    // = SC_FILTER_ENEMY_NOT_DEAD_NOT_CASTER 
                                bool reverseSort, \   // = false 
                                int maxUnitsAffected) // = -1
    {
        return thistype.doCreate(GetUnitX(caster),GetUnitY(caster),GetUnitZ(caster),radius,filter,caster,reverseSort,maxUnitsAffected);
    }
    
    // Creates a new ScSelector in 3D from the specified custom coords and caster unit (for targeted spells)
    // Создает новый ScSelector в 3D из указанных нестандартных координат и юнита-заклинателя (для способностей с целью)
    static thistype NewFromTarget(unit caster, \
                                  real x, \
                                  real y, \
                                  real radius, \        // = 500.0
                                  boolexpr filter, \    // = SC_FILTER_ENEMY_NOT_DEAD_NOT_CASTER 
                                  bool reverseSort, \   // = false 
                                  int maxUnitsAffected) // = -1
    {
        return thistype.doCreate(x,y,thistype.GetZ(x,y),radius,filter,caster,reverseSort,maxUnitsAffected);
    }
    
    // Creates a new ScSelector in 2D, doesn't match z coord this time
    // Создает новый ScSelector в 2D, не учитывает z координату на этот раз
    static thistype New(real x, \
                        real y, \
                        real radius, \        // = 500.0
                        boolexpr filter, \    // = SC_FILTER_ENEMY_NOT_DEAD_NOT_CASTER 
                        bool reverseSort, \   // = false 
                        int maxUnitsAffected) // = -1
    { 
        return thistype.doCreate(x,y,-110000,radius,filter,null,reverseSort,maxUnitsAffected);
    }
    
    // Creates a new ScSelector in 3D // Создает новый ScSelector в 3D
    static thistype New3D(real x, \
                          real y, \
                          real z, \
                          real radius, \        // = 500.0
                          boolexpr filter, \    // = SC_FILTER_ENEMY_NOT_DEAD_NOT_CASTER 
                          bool reverseSort, \   // = false
                          int maxUnitsAffected) // = -1 
    { 
        return thistype.doCreate(x,y,z,radius,filter,null,reverseSort,maxUnitsAffected); 
    }
    
    // Deletes existing ScSelector
    void Delete() 
    {     
        .Clear();
        .destroy(); 
    }
    
    // You can access units using [] operator
    unit operator[](int index) {
        if(index > .c) { return null; }
        return .u[index];
    }
    // You can change units using []= operator
    void operator[]=(int index, unit value) {
        if(index > .c) { return; }
        .u[index] = value;
    }
    // Returns size of array
    int Size() { return .c; }
    
    // void Sort(bool reverse);
    // void Select(boolexpr filter);
    // void Clear();
    // void Print();
    
/** =============================================================== */
/** ======================= PRIVATE SECTION ======================= */

    #define SC_SELECTOR_MAX_UNITS = 96
    #define is3d = (.startZ < (-100000))
    #define private swap(A,B,T) = { T T##tmp = A; A = B; B = T##tmp; }
    
    private unit u[SC_SELECTOR_MAX_UNITS];
    private real d[SC_SELECTOR_MAX_UNITS];
    private int  c;
    private real startRadius;
    private real startX;
    private real startY;
    private real startZ;
    private unit me;
    private int  softLimit;
    
    private static location GL = Location(0,0)
    private static real GetUnitZ(unit u) {
        MoveLocation(thistype.GL , GetUnitX(u) , GetUnitY(u));
        return GetUnitFlyHeight(u) + GetLocationZ(thistype.GL);
    }
    private static real GetZ(real x, real y) {
        MoveLocation(thistype.GL , x , y);
        return GetLocationZ(thistype.GL);
    }
    
    private static thistype doCreate(real x, real y, real z, real radius, boolexpr filter, unit caster, bool reverseSort, int maxUnitsAffected) {
        thistype this = thistype.allocate();
        .c = 0;
        .startRadius = radius;
        .startX = x;
        .startY = y;
        .startZ = z;
        if(maxUnitsAffected <= 0) { maxUnitsAffected = SC_SELECTOR_MAX_UNITS; }
        .softLimit = maxUnitsAffected;
        .me = caster;
        .Select(filter);
        .Sort(reverseSort);
        return this;
    }

    void Print() {
        int i=0;
        BJDebugMsg("ScSelector["+I2S(this)+"] = { ("+I2S(c)+")," \
                                                    +R2SW(.startRadius,0,2)+"," \
                                                    +R2SW(.startX,0,2)+"," \
                                                    +R2SW(.startY,0,2)+"," \
                                                    +R2SW(.startZ,0,2));
        whilenot(i >= c) {
            BJDebugMsg("    "+R2SW(GetUnitX(u[i]),0,2)+"," \
                             +R2SW(GetUnitY(u[i]),0,2)+"," \
                             +R2SW(thistype.GetUnitZ(u[i]),0,2) \
                             +": "+R2SW(d[i],0,2)+"," \
                             +" id="+I2S(i)+",");    
            ++i;
        }
        BJDebugMsg("}");
    }
    
    void Select(boolexpr filter) {
        thistype.mgetfiltercaster = .me;
        thistype.mgetfilterx = .startX;
        thistype.mgetfiltery = .startY;
        thistype.mgetfilterz = .startZ;
        real x,y,z;
        group g = CreateGroup();
        .Clear();
        GroupEnumUnitsInRange(g,.startX,.startY,.startRadius,filter);
        unit w = FirstOfGroup(g);        
        whilenot((w == null) || (.c >= SC_SELECTOR_MAX_UNITS)) {
            x = GetUnitX(w) - .startX;
            y = GetUnitY(w) - .startY;
            if(is3d) {
                z = thistype.GetUnitZ(w) - .startZ;
                .d[.c] = SquareRoot(x*x + y*y + z*z)
                if(.d[.c] <= .startRadius) {
                    .u[.c] = w;
                }
            } else {
                .d[.c] = SquareRoot(x*x + y*y)
                .u[.c] = w;
            }
            .c++;
            GroupRemoveUnit(g,w);
            w = FirstOfGroup(g);
        }
        GroupClear(g);
        DestroyGroup(g);
        g = null;
        w = null;
    }
    
    // Selection sorting
    void Sort(bool reverse) {
        int i,j,iMin;
        for (j = 0; j < (.c-1); ++j) {
            iMin = j;
            for ( i = j+1; i < .c; ++i) {
                if(reverse) { 
                    if (.d[i] > .d[iMin]) {
                        iMin = i;
                    }
                } else { 
                    if (.d[i] < .d[iMin]) {
                        iMin = i;
                    }
                }
            }
            if ( iMin != j ) {
                swap(.d[j], .d[iMin], real);
                swap(.u[j], .u[iMin], unit);
            }
        }
    }
    
    void Clear() {
        int i=0;
        whilenot(i >= c) { 
            u[i] = null;
            ++i;
        }
    }
    
    static unit mgetfiltercaster = null;
    static real mgetfilterx = 0;
    static real mgetfiltery = 0;
    static real mgetfilterz = 0;
    static unit getfiltercaster() { return mgetfiltercaster; }
    static real getfilterx() { return mgetfilterx; }
    static real getfiltery() { return mgetfilterx; }
    static real getfilterz() { return mgetfilterx; }
}

#define private NOT_CASTER = { (ScGetFilterCaster() != GetEnumUnit()) }
#define private NOT_DEAD = { ((GetUnitTypeId( GetFilterUnit()) > 0) && !IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD)) }
#define private DEAD = { ((GetUnitTypeId( GetFilterUnit()) > 0) && IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD)) }
#define private ENEMY = { IsPlayerEnemy(GetOwningPlayer(ScGetFilterCaster()),GetOwningPlayer(GetFilterUnit())) }
#define private ALLY = { IsPlayerAlly(GetOwningPlayer(ScGetFilterCaster()),GetOwningPlayer(GetFilterUnit())) }
#define private SAME_PLAYER = { (GetOwningPlayer(ScGetFilterCaster()) == GetOwningPlayer(GetFilterUnit())) }

bool ScFilterAny() { return true; }
bool ScFilterAnyNotMe() { return NOT_CASTER; }
bool ScFilterNotDeadNotCaster() { return NOT_DEAD && NOT_CASTER; }
bool ScFilterAllyNotDeadNotCaster() { return ALLY && NOT_DEAD && NOT_CASTER; }
bool ScFilterAllyDeadNotCaster() { return ALLY && DEAD && NOT_CASTER; }
bool ScFilterEnemyNotDeadNotCaster() { return ENEMY && NOT_DEAD && NOT_CASTER; }
bool ScFilterEnemyDeadNotCaster() { return ENEMY && DEAD && NOT_CASTER; }
bool ScFilterSamePlayerNotDeadNotCaster() { return SAME_PLAYER && NOT_DEAD && NOT_CASTER; }
bool ScFilterSamePlayerDeadNotCaster() { return SAME_PLAYER && DEAD && NOT_CASTER; }
bool ScFilterSamePlayerOrAllyNotDeadNotCaster() { return (SAME_PLAYER || ALLY) && NOT_DEAD && NOT_CASTER; }
bool ScFilterSamePlayerOrAllyNotDead() { return (SAME_PLAYER || ALLY) && NOT_DEAD; }
bool ScFilterAllyNotDead() { return ALLY && NOT_DEAD; }
bool ScFilterDeadNotCaster() { return DEAD && NOT_CASTER; }

callback onInit() {
    SC_FILTER_ANY                                    = Condition(function ScFilterAny)
    SC_FILTER_ANY_NOT_CASTER                         = Condition(function ScFilterAnyNotMe)
    SC_FILTER_NOT_DEAD_NOT_CASTER                    = Condition(function ScFilterNotDeadNotCaster)
    SC_FILTER_ALLY_NOT_DEAD_NOT_CASTER               = Condition(function ScFilterAllyNotDeadNotCaster)
    SC_FILTER_ALLY_DEAD_NOT_CASTER                   = Condition(function ScFilterAllyDeadNotCaster)
    SC_FILTER_ENEMY_NOT_DEAD_NOT_CASTER              = Condition(function ScFilterEnemyNotDeadNotCaster)
    SC_FILTER_ENEMY_DEAD_NOT_CASTER                  = Condition(function ScFilterEnemyDeadNotCaster)
    SC_FILTER_SAMEPLAYER_NOT_DEAD_NOT_CASTER         = Condition(function ScFilterSamePlayerNotDeadNotCaster)
    SC_FILTER_SAMEPLAYER_DEAD_NOT_CASTER             = Condition(function ScFilterSamePlayerDeadNotCaster)
    SC_FILTER_SAMEPLAYER_OR_ALLY_NOT_DEAD_NOT_CASTER = Condition(function ScFilterSamePlayerOrAllyNotDeadNotCaster)
    SC_FILTER_SAMEPLAYER_OR_ALLY_NOT_DEAD            = Condition(function ScFilterSamePlayerOrAllyNotDead)
    SC_FILTER_ALLY_NOT_DEAD                          = Condition(function ScFilterAllyNotDead)
    SC_FILTER_DEAD_NOT_CASTER                        = Condition(function ScFilterDeadNotCaster)
}

}

// ScorpioT1000 (c) 2013

Как это выглядит

Пример 1:
    if(GetSpellAbilityId() == 'A000') {
        // Создадим новый селектор, зададим параметры. По умолчанию см. в заголовках функиций
        ScSelector s = ScSelector.NewFromTarget(GetTriggerUnit(),GetLocationX(l), GetLocationY(l), 410, SC_FILTER_ENEMY_NOT_DEAD_NOT_CASTER, true, -1);
        // Начнем цикл от 0 до размера массива селектора
        for(int i = 0; i < s.Size(); ++i) {
            // ваш код
            // вы можете свободно обращаться скобками
            unit u = s[i];
            s[i] = u;
            TriggerSleepAction(0.08);
        }
        
        s.Delete();
    }
Пример с лечением:
    if(GetSpellAbilityId() == 'A002') {
        ScSelector s = ScSelector.NewFromTarget(GetTriggerUnit(),GetLocationX(l), GetLocationY(l), 300, SC_FILTER_SAMEPLAYER_OR_ALLY_NOT_DEAD, false, -1);
        
        for(int i = 0; i < s.Size(); ++i) {
            XLS_CreateOnUnits(XLS_HEALING_WAVE_2,GetTriggerUnit(),s[i],1.9);
            TriggerSleepAction(0.05);
        }
        s.Delete();
    }
Конечно, это примеры на скорую руку, желательно обходиться без вейтов.

Что за фильтры

При создании фильтры контроллируют, какие единицы могут быть затронуты селектором.
Все фильтры перечислены в самом начале модуля ScSortedSelector. Расшифрую, что означает каждая приписка:
ANY - любой юнит; NOT - отрицание, т.е. следующее за ним слово исключается при выборе; OR - логическое "или", т.е. может быть выполнено что-то одно (то, что слева или то что справа); CASTER - тот, кто вызвал заклинание (работает только если юнит был передан); DEAD - мертвые, но еще не удаленные; ALLY - союзники заклинателя; ENEMY - противники заклинателя; SAMEPLAYER - юниты, принадлежащие тому же игроку;
Тем самым, например, фильтр SC_FILTER_SAMEPLAYER_OR_ALLY_NOT_DEAD_NOT_CASTER означает, что в выборку будут добавлены юниты самого игрока, юниты его союзников, при этом они все не могут быть мертвыми и в выборку не добавится сам заклинатель.

Могут быть фаталы

Из-за немного кривой либы молний у меня были фаталы, но я сменил период и все пофиксилось O_o
Ещё могут быть фаталы, если обратиться к юниту, которого нет. Например, если вы создали селектор без заклинателя, а фильтр выбрали с проверкой на союз.

Где дамаг?

Эта библиотека выбора, т.е. ее главная задача - отобрать для вас юнитов в правильном порядке, а что с ними делать - решает уже модмейкер.
`
ОЖИДАНИЕ РЕКЛАМЫ...
1
30
11 лет назад
1
[12.02.2013 02:37:07] ScorpioT1000: Клампа в треды xgm.ru/p/wc3/sc-sorted-selector
Вау, круто! Побольше бы таких юзейбл библиотек от тебя, Скорпи!
5
15
11 лет назад
5
беда пичаль, кидает в менюшку
1
2
11 лет назад
1
Запускаю мап, сохраняю - unknown block и последняя строка в окошке method sort....
Вероятно всего for не видит, как быть?
0
37
11 лет назад
Отредактирован ScorpioT1000
0
GREAT_MAN, замени
for(раз; два; три)
{

}
на
раз
whilenot(not (два))
{

три
}
или скачай уже последний jngp
Clamp, я имел ввиду холивары с бугерменами, но ладно.
youtube, я тестил на варе 1.21b и на 1.26
0
17
11 лет назад
0
Скорп, :) зачетная штука. У меня появилась идея использовать для распространения страха. (у меня есть абилка когда юниты разбегаются в ужасе.) Очень просто и красиво написано.
0
37
11 лет назад
0
vlad_C0M, попробуй)
Думаю, может еще одну сделать, "вирусная логика", которая будет позволять простыми телодвижениями переключаться между юнитами по логике распространения вируса?
0
30
11 лет назад
0
ScorpioT1000, круто, полезная вещь, Одобрям!
0
30
11 лет назад
0
Clamp, я имел ввиду холивары с бугерменами, но ладно.
бугермен не в состоянии критиковать тебя, ибо сливается даже против моих ответов, а я на порядок ниже тебя в качестве программиста =)
0
37
11 лет назад
0
В споре рождаются новые идеи =)
0
30
11 лет назад
0
Я могу тут поспорить только том, что она малоюзабельна, не более =)
0
30
11 лет назад
0
Кто такой этот ваш бугермен?
0
21
6 лет назад
0
Перебор "по удаленности"? Скрины ни о чем не говорят, (по крайней мере о переборе). Интересует пример где действительно необходимо именно перебирать юнитов.
Просто расскажи как это работает и для какой задачи писалось. Если не лень.
0
28
6 лет назад
Отредактирован PT153
0
Гхм, а зачем использовать Selection Sort, когда есть Insertion Sort, которая лучше?
2
37
6 лет назад
2
Raised, типа новы, кого дамагнуть первым, если надо наносить урон не сразу, а дать возможность убежать от волны.
PT153, ну так вот решил тогда, не вспомню уже, можно супер квик сорт уже юзать
Чтобы оставить комментарий, пожалуйста, войдите на сайт.