Добавлен , опубликован
Алгоритмы, Наработки и Способности
Способ реализации:
cJass
Тип:
Наработка
При вызове RemoveUnit() у нас остается handle который не будет удален пока на него ссылается что-то, и в таких случаях нельзя определить был ли удален юнит или нет (использовав u == null)
Как пример:
  • в случаях когда у нас есть таймер хранящий юнита, юнит может быть удален в промижуток между созданием таймера, и его срабатывании.
  • юнит был в какой-то глобальной переменной
Для таких случаев мы можем использовать несколько вариантов определения удален ли юнит:
Вариант #1 (UnitTypeId)
Если юнит был удален в том же потоке, тогда это не сработает
Код
define
	IsUnitExist(u) = (u != null and GetUnitTypeId(u) != 0)
	IsUnitExist2(u) = (GetUnitTypeId(u) != 0)
	
	IsUnitRemoved(u) = (u == null or GetUnitTypeId(u) == 0)
	IsUnitRemoved2(u) = (GetUnitTypeId(u) == 0)
enddefine
Вариант #2 (UnitUserData)
Код
define
	UNIT_REMOVED_UUD = -2147483646
	
	IsUnitExist(u) = (u != null and GetUnitUserData(u) != UNIT_REMOVED_UUD)
	IsUnitExist2(u) = (GetUnitUserData(u) != UNIT_REMOVED_UUD)
	
	IsUnitRemoved(u) = (u == null or GetUnitUserData(u) == UNIT_REMOVED_UUD)
	IsUnitRemoved2(u) = (GetUnitUserData(u) == UNIT_REMOVED_UUD)
	
	RemoveUnit(u) = {
		// Не ставим call чтобы можно было использовать "call RemoveUnit(u)"
		SetUnitUserData(u, UNIT_REMOVED_UUD)
		call Remove##Unit(u)
	}
enddefine
Вариант #3 (Способность)
Редактор обьектов
Создаем способность на основе (Предмет +1 атака) и удаляем все бонусы
Код
define
	ABILITY_UNIT_REMOVED = 'A000'
	
	IsUnitExist(u) = (u != null and GetUnitAbilityLevel(u, ABILITY_UNIT_REMOVED) == 0)
	IsUnitExist2(u) = (GetUnitAbilityLevel(u, ABILITY_UNIT_REMOVED) == 0)

	IsUnitRemoved(u) = (u == null or GetUnitAbilityLevel(u, ABILITY_UNIT_REMOVED) != 0)
	IsUnitRemoved2(u) = (GetUnitAbilityLevel(u, ABILITY_UNIT_REMOVED) != 0)

	RemoveUnit(u) = {
    	// Не ставим call чтобы можно было использовать "call RemoveUnit(u)"
    	UnitAddAbility(u, ABILITY_UNIT_REMOVED)
    	call Remove##Unit(u)
	}
enddefine
После реализации одного из способов мы можем спокойно проверить был ли удален юнит через какое-то время, даже если handle не освобожден
(ну или наоборот, использовать IsUnitExist() если нам нужна проверка существует ли юнит)
function TimerExpired takes nothing returns nothing
    local unit u = LoadUnitHandle(hash, timerId, 0)
    
    // Используем 2 вариацию, если при занесении в хэш мы уверены что там не null
    if (IsUnitRemoved2(u)) then
        // Юнит удален, чистим u (даже при использовании 1 вариации)
        set u = null
        return
    endif
    
    // ...
    set u = null
endfunction
Если вы используете Jass new gen pack(JNGP) включите cJass для define
`
ОЖИДАНИЕ РЕКЛАМЫ...
22
А что насчёт?
GetUnitTypeId(u)==0

Так или иначе, этот метод с заменой стандартного RemoveUnit() на предложенные имеет право на жизнь, так как позволяет отследить состояние юнита в обработке текущего тика игры.
В теории, конечно, можно и без этого обойтись, так внутри тика мы и так знаем что произошло с юнитом, но облегчить некоторые проверки и написание кода этот подход может.
Ответы (3)
4
makkad, только что проверил, после RemoveUnit он тот-же. Ну и думаю это к лучшему, ибо в случае когда взаимодействие идет с типом юнита то все пройдет гладко и без ошибок (но это при условии что событие должно обрабатыватся даже если юнит удален)
22
nik5960nik, Да. Если в тот же тик проверять. Если ты знаешь что проверка в другом потоке, то будет выводить 0. Также как и GetUnitState(u,UNIT_STATE_MAXLIFE)
Можешь проверить это если вставить Wait между удалением и проверкой.
4
makkad, проверил в потоке и все так (странно что оно при RemoveUnit сразу не обнуляет, ну или это сделано для ситуации описаной мной выше)
Добавил этот способ тоже
21
А если проверять через set unit x y удалённого юнита, и затем сравнивать, изменились ли координаты? В теории, если юнита нет, то координаты не должны измениться, или вообще выдать 0, 0.
И ещё - когда я проверял юнитов по их имени, отсутствующий юнит выдавал в bj debug msg пустую строку. Значит, можно проверять через GetUnitName(u) == ""
Ответы (1)
4
EugeAl, способ через SetUnitX\Y + GetUnitX\Y как и GetUnitName более накладны по ресурсам чем сравнение типа юнита(если происходит не в одном потоке) или UserData которая тоже менее накладная ибо устанавливается только 1 раз при удалении юнита
49
А разве варик не учитывает сравнение юнита равному наллу?
Ответы (3)
4
N7 Molot, код ниже во всех случаях выдаст "u != null"
define PRINT_UNIT = {
    if (u == null) then
        call BJDebugMsg("null")
    else
        call BJDebugMsg("not null")
    endif
}

function Trig_asd_Actions takes nothing returns nothing
    local unit u = CreateUnit(Player(0), 'hfoo', 0, 0, 0)
    
    PRINT_UNIT
    call RemoveUnit(u)
    PRINT_UNIT
    call TriggerSleepAction(1.0)
    PRINT_UNIT
    
    set u = null
endfunction
4
N7 Molot, только LoadUnitHandle может выдать null, но если таймер работает с глобалками, например проходит по боссам или героям игроков то результат будет не тем. Поэтому одной лишь проверки на u == null недостаточно, если юнит может быть удален триггерно (не знаю насчёт удаления трупа, удаляется если есть ссылки или нет)
Код (в 4 случаях из 5 будет "u != null")
globals
    hashtable hash = InitHashtable()
    unit udg_u = null
endglobals

define PRINT_UNIT = {
    if (u == null) then
        call BJDebugMsg("null")
    else
        call BJDebugMsg("not null")
    endif
}

function TimerExpired takes nothing returns nothing
    local unit u = LoadUnitHandle(hash, GetHandleId(GetExpiredTimer()), 0)
    // LoadUnitHandle проверяет внутри удален ли юнит, если он там был, если да - возвращает null
    PRINT_UNIT
    set u = udg_u
    PRINT_UNIT
    set u = null
    call FlushChildHashtable(hash, GetHandleId(GetExpiredTimer()))
    call DestroyTimer(GetExpiredTimer())
endfunction

function Trig_test_Actions takes nothing returns nothing
    local unit u = CreateUnit(Player(0), 'hfoo', 0, 0, 0)
    local timer t = CreateTimer()
    set udg_u = u
    call SaveUnitHandle(hash, GetHandleId(t), 0, u)
    call TimerStart(t, 1.0, false, function TimerExpired)
    set t = null
    
    PRINT_UNIT
    call RemoveUnit(u)
    PRINT_UNIT
    call TriggerSleepAction(1.0)
    PRINT_UNIT
    
    set u = null
endfunction

function InitTrig_test takes nothing returns nothing
    set gg_trg_test = CreateTrigger()
    call TriggerRegisterTimerEventSingle(gg_trg_test, 1.0)
    call TriggerAddAction(gg_trg_test, function Trig_test_Actions)
endfunction
Ответы (1)
4
markonenumber, you can test on empty map, but idk reforged editor has define or not, i know exist JNGP for reforged but never use it. Without define you will have to manually write it all
32
нужно проверять на UNIT_TYPE_DEAD или на флаги в мемхаке, тогда отловите удаленных, ему флаг вручают, после чего юнит утилизируется движком - ссылки на него при этом будут не равны null, так же проверяйте на HandleId() если меньше 1, хендл некорректный.
Ответы (4)
4
quq_CCCP, UNIT_TYPE_DEAD может выдать true даже если юнит не был удален, что касаемо мемхака - не все его используют + он не работает на рефордже
32
nik5960nik, даже если не удалён но сдох - это повод ничего с юнитом не делать, вручение источника ауры дохлому = фатал, изменение уровня абилки = фатал, изменение статов - бага с бонусами от статов. Допом проверяем на GetHandleId()
4
quq_CCCP, даже если юнит мертв, это не значит что с ним нельзя что-то делать. Даже упомянутое изменение уровня способности имеет смысл, если к примеру юниту на время повышался уровень способности, или может стоит тупо повысить уровень абилки но из-за того что юнит сдох не возвращать прежний через время?
31
nik5960nik, у тебя сижасс в коде, который тоже не компилится в рефорже.
38
Просто не надо юзать RemoveUnit, надо его убивать и весь жизненный цикл вместе с разложением нормально пройдет, и все функции будут работать как надо
Ответы (2)
4
ScorpioT1000, если юнит уже был мертв, то ничего не изменится, в тоже время убив юнита вызовется событие его смерти что не всегда нужно.
38
nik5960nik, сделай обертку с проверкой
21
Ещё вариант, в теории
Функция UnitAddAbility возвращает boolean, в случае успеха выдаёт true
Если выдавать абилку удалённому юниту, функция должна будет выдать false
Можете проверить этот способ?
Аналогично выдают boolean функции SaveUnitHandle в хэш, UnitAddItem, UnitAddType
Ответы (2)
32
EugeAl, она не для этого, она проверяет по сути есть ли уже такая абилка у юнита, если есть - вернет false, иначе true...
Триггерно внезапно нельзя юниту добавить 2 одинаковых абилки, хотя с помощью спелбуков или инвентаря всегда пожалуйста, хотя они имеют собственные abilitylist.
21
quq_CCCP, ладно, с абилкой, итемом, типом, не прокатит, а SaveUnitHandle?
31
Дико взорвал с промИжутка.

Если вы используете Jass new gen pack(JNGP) включите cJass для define
Никогда не включайте сижасс!

UnitAlive возвращает корректные значения для всех видов хэндлов. Что ты собрался делать с мэртвыми, что UnitAlive тебе не подходит?
Чтобы оставить комментарий, пожалуйста, войдите на сайт.