Есть такой триггер.
Триггер
globals
    constant trigger TriggerEnterFinish = CreateTrigger()
endglobals

function Trig_EnterFinish_Actions takes nothing returns nothing
    local Minion m = GetUnitUserData(GetEnteringUnit())
    local CustomPlayer p = m.foe
    call p.leaked(m.data.livesconsume)  // отнимаются жизни у игрока, что протёк.
    call m.gainBounty(false)  // протёкшему игроку даётся награда.
    if p != m.owner and m.owner.inGame then  // если протёкший игрок не является хозяином входящего юнита, то игроку-владельцу даются жизни.
        call m.owner.leaked(m.data.livesgain)
    endif
    call CreateEffectPoint(EffectLifeConsume, GetUnitX(m.minion), GetUnitY(m.minion))
    call m.remove()  // удаление юнита.
    if p.lives == 0 then
        call FlushPlayer(p)  // удаление игрока.
        if bj_isSinglePlayer != (PlayingPlayers.top == 0) then
            call FinishGame(10)
        endif
    endif
endfunction

function InitTrig_EnterFinish takes nothing returns nothing
    call TriggerAddAction(TriggerEnterFinish, function Trig_EnterFinish_Actions)
endfunction
Он срабатывает при входе юнита в LeaveRegion. Чуть подробнее о функциях:
Функции игрока
    method leaked takes integer l returns nothing
        if l == 0 then
            return
        elseif l < -1 then
            call message(SoundWarning, Color.Warnings.hex + "Warning|r: you lost " + Color.Numbers.hex + I2S(-l) + "|r lives.")
        elseif l == -1 then
            call message(SoundWarning, Color.Warnings.hex + "Warning|r: you lost " + Color.Numbers.hex + "1|r life.")
        elseif l == 1 then
            call message(SoundReceiveLife, Color.Hints.hex + "Note|r: you received " + Color.Numbers.hex + "1|r life.")
        elseif l > 1 then
            call message(SoundReceiveLife, Color.Hints.hex + "Note|r: you received " + Color.Numbers.hex + I2S(l) + "|r lives.")
        endif
        call addLives(l)
    endmethod

    method addLives takes integer l returns nothing
        set lives = lives + l
        if lives < 0 then
            set lives = 0
        elseif lives > MaxLives then
            set lives = MaxLives
        endif
        call MultiboardSetItemValue(mblives, I2S(lives))
    endmethod

    method message takes sound snd, string s returns nothing
        if isLocalPlayer then
            if mescount == MessagesMaximum then
                set mescount = 0
                call ClearTextMessages()
            endif
            call StartSound(snd)
            call DisplayTimedTextToPlayer(user, 0., 0., MessagesDuration, s)
            set mescount = mescount + 1
        endif
        call TimerStart(t, MessagesDuration, false, function thistype.flushMessageCount)
    endmethod
Функции миньона
    method remove takes nothing returns nothing
        call RemoveUnit(minion)
        call delete()
    endmethod
    
    method delete takes nothing returns nothing
        call owner.sentminions.deleteFromArray(this)
        call FlushFieldMinion()
        call DeleteLifeTimer()
        call deallocate()
        call DebugMsg("Minion " + I2S(this) + " is deleted.")
    endmethod

    method FlushFieldMinion takes nothing returns nothing
        call foe.minions.deleteFromArray(this)
        call owner.clearFoodUsed(data.foodused)
        //call DebugMsg("Food cleared: " + R2SX(data.foodused))
        // Timers
        call buffs.destroy()
        //call FlushChildHashtable(Hash, hid)
        // Items
        call RemoveItem(hpicon)
        set hpicon = null
        call RemoveItem(shldicon)
        set shldicon = null
        call RemoveItem(physarmicon)
        set physarmicon = null
        call RemoveItem(magcarmicon)
        set magcarmicon = null
        call RemoveItem(movespeedicon)
        set movespeedicon = null
        call RemoveItem(lifetimeicon)
        set lifetimeicon = null
        // Effects
        call UnitRemoveAbility(minion, EffectShield)
        call UnitRemoveAbility(minion, EffectSpeedup)
        call UnitRemoveAbility(minion, EffectSlowdown)
        call UnitRemoveAbility(minion, EffectStun)
        call UnitRemoveAbility(minion, MagcArmorIncAbil)
        call UnitRemoveAbility(minion, MagcArmorDecAbil)
        call UnitRemoveAbility(minion, PhysArmorIncAbil)
        call UnitRemoveAbility(minion, PhysArmorDecAbil)
        //call DebugMsg("Minion " + I2S(this) + " is flushed.")
    endmethod
FlushPlayer вызывает такой триггер:
FlushPlayer
globals
    CustomPlayer FlushingPlayer
    constant trigger TriggerFlushPlayer = CreateTrigger()
endglobals

//! textmacro Flush takes name, arrtyp, arr, cmd
function $name$ takes nothing returns nothing
    local $arrtyp$ arr = FlushingPlayer.$arr$
    local integer a = arr.top
    loop
        exitwhen a == -1
        call arr[a].$cmd$()
        set a = a - 1
    endloop
    call DebugMsg("$arr$ of player " + I2S(FlushingPlayer) + " is flushed.")
endfunction
//! endtextmacro

//! runtextmacro Flush("SellAllForces", "ForceArray", "forces", "sell")
//! runtextmacro Flush("RemoveAllSpawned", "SpawnArray", "spawned", "removeSpawned")
//! runtextmacro Flush("RemoveAllMinions", "MinionArray", "minions", "remove")
//! runtextmacro Flush("RemoveAllCorpses", "DeadArray", "dead", "removeCorpse")
//! runtextmacro Flush("RemoveAllTowers", "TowerArray", "towers", "remove")

function Trig_Flush_Actions takes nothing returns nothing
    local CustomPlayer p = FlushingPlayer
    call PlayingPlayers.deleteFromArray(p)
    call ExecuteFunc("SellAllForces")
    call ExecuteFunc("RemoveAllSpawned")
    call ExecuteFunc("RemoveAllMinions")
    call ExecuteFunc("RemoveAllCorpses")
    call ExecuteFunc("RemoveAllTowers")
    call p.flush()
    call DebugMsg("Player " + I2S(p) + " is flushed.")
endfunction

// call p.flush()
    method flush takes nothing returns nothing
        call DeleteTimer()
        call field.flush()
        if isReady then
            call DestroyGrid.execute()
            call ShadowMultiboard()
            call RemoveUnit(builder)
            call RemoveUnit(barracks)
            call RemoveUnit(altar)
            call RemoveUnit(blacksmith)
            call RemoveUnit(merccamp)
            set builder = null
            set barracks = null
            set blacksmith = null
            set altar = null
            set merccamp = null
            set blacksmith_swap_faction = null
            set blacksmith_mhu_level = null
            set blacksmith_mhu_cost = null
        endif
        set inGame = false
    endmethod

// call field.flush()
    method flush takes nothing returns nothing
        local integer a = -1
        local integer index = host.index
        local thistype f
        local CustomPlayer p
        // Vision
        loop
            set a = a + 1
            set p = Players[a]
            if p.isReady then  // Players that left before makeReady() method had called, do not have vision.
                set f = p.field
                call DestroyFogModifier(f.vision_player[index])
                set f.vision_player[index] = null
                call DestroyFogModifier(f.vision_forces[index])
                set f.vision_forces[index] = null
                call SetFogStateRect(p.user, FOG_OF_WAR_MASKED, playervision, true)
                call SetFogStateRect(p.user, FOG_OF_WAR_MASKED, forcearea, true)
            endif
            exitwhen a == Players.top
        endloop
        // Rects
        call RegionClearRect(SpawnRegion, spawnarea)
        call RegionClearRect(LeaveRegion, leavearea)
        
        call RemoveRect(playervision)
        set playervision = null
        call RemoveRect(forcearea)
        set forcearea = null
        call RemoveRect(spawnarea)
        set spawnarea = null
        call RemoveRect(leavearea)
        set leavearea = null
        call RemoveRect(towerarea)
        set towerarea = null
        // Cells
        call DestroyCells.execute()
    endmethod
Если нужны ещё функции - пишите.

А теперь к делу.
Мне всегда говорили, что код в игре выполняется последовательно, то есть, как я понимаю, сначала завершиться до конца триггер EnterFinish, и только потом он стартует заново, если новый миньон зашёл в область. То есть вот так: миньон зашёл в область, отнял жизни, их стало 0, игрок удалился, удаляя всех оставшихся миньонов.
Но это не так: если несколько миньонов заходят в область почти одновременно, триггер вызывается несколько раз, потому что код не успевает удалять миньонов, которые почти одновременно входят. Потому данный код багнут.

Вопрос: почему так происходит, если код выполняется последовательно? Когда выполнение "приостанавливается", чтобы запустить новый триггер и выполнить его?

Принятый ответ

Или сделай debounce для конкретного игрока
`
ОЖИДАНИЕ РЕКЛАМЫ...
0
28
5 лет назад
Отредактирован PT153
0
На данный момент я попытался исправить это так. Все функции не менялись, кроме указанных ниже:
раскрыть
function Trig_EnterFinish_Actions takes nothing returns nothing
    local Minion m = GetUnitUserData(GetEnteringUnit())
    local CustomPlayer p = m.foe
    if p.inGame then
        call p.leaked(m.data.livesconsume)
        call m.gainBounty(false)
        if p != m.owner and m.owner.inGame then
            call m.owner.leaked(m.data.livesgain)
        endif
        call CreateEffectPoint(EffectLifeConsume, GetUnitX(m.minion), GetUnitY(m.minion))
        if p.lives > 0 then
            call m.remove()
        elseif p.flush() then
            call Message(MessagesDuration, "Player " + p.colored_name + " has been defeated.")
            if bj_isSinglePlayer != (PlayingPlayers.top == 0) then
                call FinishGame(10)
            endif
        endif
    endif
endfunction

// p.flush()
    method flush takes nothing returns boolean
        if inGame then
            set inGame = false
            call field.flush()
            call field.stopAllUnits()
            call PlayingPlayers.deleteFromArray(this)
            set flushing = this
            call ExecuteFunc("SellAllForces")
            call ExecuteFunc("RemoveAllSpawned")
            call ExecuteFunc("RemoveAllMinions")
            call ExecuteFunc("RemoveAllCorpses")
            call ExecuteFunc("SellAllTowers")  // Почти тоже самое, что и call ExecuteFunc("RemoveAllTowers").
            call DeleteTimer()
            if isReady then
                call DestroyGrid.execute()
                call ShadowMultiboard()
                call RemoveUnit(builder)
                set builder = null
                call RemoveUnit(barracks)
                set barracks = null
                call RemoveUnit(altar)
                set altar = null
                call RemoveUnit(merccamp)
                set merccamp = null
                call RemoveUnit(blacksmith)
                set blacksmith = null
                set blacksmith_swap_faction = null
                set blacksmith_mhu_level = null
                set blacksmith_mhu_cost = null
            endif
            call DebugMsg("Player " + I2S(this) + " is flushed.")
            return true
        endif
        return false
    endmethod

// call field.flush()
    method flush takes nothing returns nothing
        // Regions
        call RegionClearRect(SpawnRegion, spawnarea)
        call RegionClearRect(LeaveRegion, leavearea)
        // Destroy
        call TimerStart(t, FieldDestroyDelay, false, DestroyCallback)
    endmethod

// call field.stopAllUnits()
    private static method StopAllUnitsFilterFunc takes nothing returns nothing
        call IssueImmediateOrderById(GetFilterUnit(), Order_stop)
    endmethod
    
    private static boolexpr StopAllUnitsFilter
    
    method stopAllUnits takes nothing returns nothing
        call GroupEnumUnitsInRect(bj_lastCreatedGroup, playerfield, StopAllUnitsFilter)
    endmethod
Достаточно ли этого, чтобы избежать бага, что описан в вопросе? Будет ли лучше поставить в Trig_EnterFinish_Actions вместо if p.inGame then вот это if p.lives > 0 then, или без разницы с реализацией выше? Количество жизней становится меньше 0 раньше, чем флаг inGame становится false.

На данный момент последовательность такая: жизни становятся равным нулю; установка inGame на false; удаление области из региона, на который зарегистрирован триггер EnterFinish; всем юнитам отдаётся приказ "Стоп"; удаление миньонов.
0
32
5 лет назад
0
Наверное ты вызываешь какое либо событие. Действия в варе идут строго линейно, вызов триггера или запуск события подразумевают что сначала выполнятся все действия в этих триггерах, а только потом продолжится выполнение потока который их вызвал. Проще говоря нанося урон, движок вара сначала выполнит все действия реагирующие на урон, а только потом продолжит выполнять код после нанесения урона.
0
27
5 лет назад
Отредактирован MpW
0
где-то потоки изучал, вот исследования мои (текстовик, не судить строго. один юзер этого сайта сделал карту и как показал, что есть лимиты, своего рода счетчики loop executefunc call TimerStart. я нуб этого не знал).
дебагом просто посмотри (в начале функции пишем название функции или триггера, в конец функции пишем end_name func и будет понятно кто за кем следует)
Загруженные файлы
0
28
5 лет назад
0
(в начале функции пишем название функции или триггера, в конец функции пишем end_name func и будет понятно кто за кем следует)
Да вот и проблема в том, что это происходит нечасто, но происходит, это сложно просимулировать.
0
27
5 лет назад
0
что не так? счетчики на жизнь не так отнимаются?
0
28
5 лет назад
Отредактирован PT153
0
Но это не так: если несколько миньонов заходят в область почти одновременно, триггер вызывается несколько раз, потому что код не успевает удалять миньонов, которые почти одновременно входят. Потому данный код багнут.
Удаление игрока вызывается несколько раз.

19:28, жёлтый проигрывает, удаляясь 3 раза, из-за чего у зелёного становится -1 потребление пищи, а 2 игрока вылетают из пула играющих.
В момент удаления у оранжевого создаются эффекты (у него точка (0, 0)), так как эффекты создаются для юнитов, что уже удалены.
Загруженные файлы
0
27
5 лет назад
0
без карты реплей не могу посмотреть
0
28
5 лет назад
Отредактирован PT153
0
без карты реплей не могу посмотреть
Забыл, xD.
Путь у карты такой: Maps\Download\WispTD v3.-1.2 ndopt.w3x
Обязательно замени _ на пробелы в названии карты.
Загруженные файлы
0
16
5 лет назад
0
ну нет же понятия "одновременный", всегда кто-то первый, кто-то второй. другое дело, как в движке это обрабатывается. Например, при создании юнита через CreateUnit() триггеры, реагирующие на ВХОД юнита в область, сработают только в следующем кадре, но не этом. А при вызове иллюзии через палку и наличии события UNIT_SUMMONED иллюзия будет поймана сразу же, поток прервется на выполнение кода триггера с вызовом.
Полагаю, удаление тоже не моментально их удаляет из трекинга. Попробуй отдавать "стоп" перед удалением, например,
0
28
5 лет назад
0
Попробуй отдавать "стоп" перед удалением, например,
Я так и начал делать, но сейчас это бесполезно, мне кажется, потому что сейчас рект из региона удаляется первее юнитов.
PT153:
На данный момент последовательность такая: жизни становятся равным нулю; установка inGame на false; удаление области из региона, на который зарегистрирован триггер EnterFinish; всем юнитам отдаётся приказ "Стоп"; удаление миньонов.
Раз уж никто точно не знает, то хватит ли реализации из первого комментария, чтобы избежать данной ошибки?
Вообще, надо систему логов сделать, вместо вывода в сообщений в игру, ибо последние отключены в ndopt версиях.
0
37
5 лет назад
0
Или сделай debounce для конкретного игрока
Принятый ответ
0
28
5 лет назад
0
Или сделай debounce для конкретного игрока
Вот это?
0
37
5 лет назад
Отредактирован ScorpioT1000
0
Просто не выполнять триггер для игрока x, если он уже выполнялся для этого игрока 0.5 сек назад
Чтобы оставить комментарий, пожалуйста, войдите на сайт.