• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[General] TimeStop for Effects

Status
Not open for further replies.
Level 7
Joined
Feb 9, 2021
Messages
301
Disclaimer: I code for 1.26a

As I am trying to make time stop spell in my map, I need a system for creating effects, enumerating them and stopping them. However, I am not sure how to do this.

This is the way I see it from the interface perspective:
JASS:
library EffectSystem

    function ResumeEffect takes nothing returns nothing
        call SetEffectTimeScale(prevEffectSacle)
        call resume(timer)
    endfunction

    function PauseEffect takes nothing returns nothing
        real prevEffectSacle = GetEffectTimeScale(effect)
        call SetEffectTimeScale(effect, 0)
        call pause(timer)
    endfunction

    function DestroyEffect takes nothing returns nothing
        call ShowEffect(effect, false)
        call DestroyEffect(effect)

    endfunction

    function DestroySpeicalEffectEx takes string sfxId, real x, real y, real lifetime return effect
        set effect = AddSpecialEffect(sfxId, x, y)
        call SetEffectAnimationByIndex(effect, 1)
    
        set timer = NewTimerEx(timer)
        call TimerStart(timer, lifetime, false, function DestroyEffect)
    
    endfunction


endlibrary

What I want to understand is how to structure the data in a way that I can enumerate these effects and then pause/unpause and change effect animation speed these effects. I assume I need to use LinkedList.



and then I thought to just adapt this:

JASS:
library MissileUtils requires Missiles
    /* -------------------- Missile Utils  v2.3 by Chopinski -------------------- */
    // This is a simple Utils library for the Relativistic Missiles system.
    // Credits:
    //     Sevion for the Alloc module
    //         - www.hiveworkshop.com/threads/snippet-alloc.192348/
    /* ----------------------------------- END ---------------------------------- */

    /* -------------------------------------------------------------------------- */
    /*                                   System                                   */
    /* -------------------------------------------------------------------------- */
    private module Alloc
        private static integer instanceCount = 0
        private thistype recycle

        static method allocate takes nothing returns thistype
            local thistype this

            if (thistype(0).recycle == 0) then
                debug if (instanceCount == JASS_MAX_ARRAY_SIZE) then
                    debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Alloc ERROR: Attempted to allocate too many instances!")
                    debug return 0
                debug endif
                set instanceCount = instanceCount + 1
                set this = instanceCount
            else
                set this = thistype(0).recycle
                set thistype(0).recycle = thistype(0).recycle.recycle
            endif

            debug set this.recycle = -1

            return this
        endmethod

        method deallocate takes nothing returns nothing
            debug if (this.recycle != -1) then
                debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Alloc ERROR: Attempted to deallocate an invalid instance at [" + I2S(this) + "]!")
                debug return
            debug endif

            set this.recycle = thistype(0).recycle
            set thistype(0).recycle = this
        endmethod
    endmodule

    private module LinkedList
        readonly thistype next
        readonly thistype prev

        method init takes nothing returns thistype
            set next = this
            set prev = this

            return this
        endmethod

        method pushBack takes thistype node returns thistype
            set node.prev = prev
            set node.next = this
            set prev.next = node
            set prev = node

            return node
        endmethod

        method pushFront takes thistype node returns thistype
            set node.prev = this
            set node.next = next
            set next.prev = node
            set next = node

            return node
        endmethod

        method pop takes nothing returns nothing
            set prev.next = next
            set next.prev = prev
        endmethod
    endmodule

    private struct MGroup extends array
        implement LinkedList
        implement Alloc
    
        Missiles missile
    
        method remove takes nothing returns nothing
            call pop()
            call deallocate()
        endmethod

        method insert takes Missiles m returns thistype
            local thistype node = pushBack(allocate())

            set node.missile = m

            return node
        endmethod
    
        static method create takes nothing returns thistype
            return thistype(allocate()).init()
        endmethod
    endstruct

    struct MissileGroup
        MGroup group
        integer size
    
        method destroy takes nothing returns nothing
            call group.deallocate()
            call deallocate()
        endmethod
    
        method missileAt takes integer i returns Missiles
            local MGroup node = group.next
            local integer j = 0
    
            if size > 0 and i <= size - 1 then
                loop
                    exitwhen j == i
                        set node = node.next
                    set j = j + 1
                endloop
            
                return node.missile
            else
                return 0
            endif
        endmethod
    
        method remove takes Missiles missile returns nothing
            local MGroup node = group.next
    
            loop
                exitwhen node == group
                    if node.missile == missile then
                        set size = size - 1
                        call node.remove()
                        exitwhen true
                    endif
                set node = node.next
            endloop
        endmethod
    
        method insert takes Missiles missile returns nothing
            set size = size + 1
            call group.insert(missile)
        endmethod
    
        method clear takes nothing returns nothing
            local MGroup node = group.next
        
            loop
                exitwhen node == group
                    call node.remove()
                set node = node.next
            endloop
        
            set size = 0
        endmethod
    
        method contains takes Missiles missile returns boolean
            local MGroup node = group.next
            local boolean found = false
    
            loop
                exitwhen node == group
                    if node.missile == missile then
                        set found = true
                        exitwhen true
                    endif
                set node = node.next
            endloop
        
            return found
        endmethod
    
        method addGroup takes MissileGroup source returns nothing
            local MGroup node = source.group.next
    
            loop
                exitwhen node == source.group
                    if not contains(node.missile) then
                        call insert(node.missile)
                    endif
                set node = node.next
            endloop
        endmethod
    
        method removeGroup takes MissileGroup source returns nothing
            local MGroup node = source.group.next
    
            loop
                exitwhen node == source.group
                    if contains(node.missile) then
                        call remove(node.missile)
                    endif
                set node = node.next
            endloop
        endmethod
    
        static method create takes nothing returns thistype
            local thistype this = thistype.allocate()
        
            set group = MGroup.create()
            set size = 0
        
            return this
        endmethod
    endstruct

    /* -------------------------------------------------------------------------- */
    /*                                  JASS API                                  */
    /* -------------------------------------------------------------------------- */
    function CreateMissileGroup takes nothing returns MissileGroup
        return MissileGroup.create()
    endfunction

    function DestroyMissileGroup takes MissileGroup missiles returns nothing
        if missiles != 0 then
            call missiles.clear()
            call missiles.destroy()
        endif
    endfunction

    function MissileGroupGetSize takes MissileGroup missiles returns integer
        if missiles != 0 then
            return missiles.size
        else
            return 0
        endif
    endfunction

    function GroupMissileAt takes MissileGroup missiles, integer position returns Missiles
        if missiles != 0 then
            return missiles.missileAt(position)
        else
            return 0
        endif
    endfunction

    function ClearMissileGroup takes MissileGroup missiles returns nothing
        if missiles != 0 then
            call missiles.clear()
        endif
    endfunction

    function IsMissileInGroup takes Missiles missile, MissileGroup missiles returns boolean
        if missiles != 0 and missile != 0 then
            if missiles.size > 0 then
                return missiles.contains(missile)
            else
                return false
            endif
        else
            return false
        endif
    endfunction

    function GroupRemoveMissile takes MissileGroup missiles, Missiles missile returns nothing
        if missiles != 0 and missile != 0 then
            if missiles.size > 0 then
                call missiles.remove(missile)
            endif
        endif
    endfunction

    function GroupAddMissile takes MissileGroup missiles, Missiles missile returns nothing
        if missiles != 0 and missile != 0 then
            if not missiles.contains(missile) then
                call missiles.insert(missile)
            endif
        endif
    endfunction

    function GroupPickRandomMissile takes MissileGroup missiles returns Missiles
        if missiles != 0 then
            if missiles.size > 0 then
                return missiles.missileAt(GetRandomInt(0, missiles.size - 1))
            else
                return 0
            endif
        else
            return 0
        endif
    endfunction

    function FirstOfMissileGroup takes MissileGroup missiles returns Missiles
        if missiles != 0 then
            if missiles.size > 0 then
                return missiles.group.next.missile
            else
                return 0
            endif
        else
            return 0
        endif
    endfunction

    function GroupAddMissileGroup takes MissileGroup source, MissileGroup destiny returns nothing
        if source != 0 and destiny != 0 then
            if source.size > 0 and source != destiny then
                call destiny.addGroup(source)
            endif
        endif
    endfunction

    function GroupRemoveMissileGroup takes MissileGroup source, MissileGroup destiny returns nothing
        if source != 0 and destiny != 0 then
            if source == destiny then
                call source.clear()
            elseif source.size > 0 then
                call destiny.removeGroup(source)
            endif
        endif
    endfunction

    function GroupEnumMissilesOfType takes MissileGroup missiles, integer whichType returns nothing
        local integer i
        local Missiles missile
    
        if missiles != 0 then
            if Missiles.count > -1 then
                set i = 0
            
                if missiles.size > 0 then
                    call missiles.clear()
                endif
            
                loop
                    exitwhen i > Missiles.count
                        set missile = Missiles.collection[i]
                    
                        if missile.type == whichType then
                            call missiles.insert(missile)
                        endif
                    set i = i + 1
                endloop
            endif
        endif
    endfunction

    function GroupEnumMissilesOfTypeCounted takes MissileGroup missiles, integer whichType, integer amount returns nothing
        local integer i
        local integer j = amount
        local Missiles missile
    
        if missiles != 0 then
            if Missiles.count > -1 then
                set i = 0
            
                if missiles.size > 0 then
                    call missiles.clear()
                endif
            
                loop
                    exitwhen i > Missiles.count or j == 0
                        set missile = Missiles.collection[i]
                    
                        if missile.type == whichType then
                            call missiles.insert(missile)
                        endif
                        set j = j - 1
                    set i = i + 1
                endloop
            endif
        endif
    endfunction

    function GroupEnumMissilesOfPlayer takes MissileGroup missiles, player p returns nothing
        local integer i
        local Missiles missile
    
        if missiles != 0 then
            if Missiles.count > -1 then
                set i = 0
            
                if missiles.size > 0 then
                    call missiles.clear()
                endif
            
                loop
                    exitwhen i > Missiles.count
                        set missile = Missiles.collection[i]
                    
                        if missile.owner == p then
                            call missiles.insert(missile)
                        endif
                    set i = i + 1
                endloop
            endif
        endif
    endfunction

    function GroupEnumMissilesOfPlayerCounted takes MissileGroup missiles, player p, integer amount returns nothing
        local integer i
        local integer j = amount
        local Missiles missile
    
        if missiles != 0 then
            if Missiles.count > -1 then
                set i = 0
            
                if missiles.size > 0 then
                    call missiles.clear()
                endif
            
                loop
                    exitwhen i > Missiles.count or j == 0
                        set missile = Missiles.collection[i]
                    
                        if missile.owner == p then
                            call missiles.insert(missile)
                        endif
                        set j = j - 1
                    set i = i + 1
                endloop
            endif
        endif
    endfunction

    function GroupEnumMissilesInRect takes MissileGroup missiles, rect r returns nothing
        local integer i
        local Missiles missile
    
        if missiles != 0 and r != null then
            if Missiles.count > -1 then
                set i = 0
            
                if missiles.size > 0 then
                    call missiles.clear()
                endif
            
                loop
                    exitwhen i > Missiles.count
                        set missile = Missiles.collection[i]
                    
                        if GetRectMinX(r) <= missile.x and missile.x <= GetRectMaxX(r) and GetRectMinY(r) <= missile.y and missile.y <= GetRectMaxY(r) then
                            call missiles.insert(missile)
                        endif
                    set i = i + 1
                endloop
            endif
        endif
    endfunction

    function GroupEnumMissilesInRectCounted takes MissileGroup missiles, rect r, integer amount returns nothing
        local integer i
        local integer j = amount
        local Missiles missile
    
        if missiles != 0 and r != null then
            if Missiles.count > -1 then
                set i = 0
            
                if missiles.size > 0 then
                    call missiles.clear()
                endif
            
                loop
                    exitwhen i > Missiles.count or j == 0
                        set missile = Missiles.collection[i]
                    
                        if GetRectMinX(r) <= missile.x and missile.x <= GetRectMaxX(r) and GetRectMinY(r) <= missile.y and missile.y <= GetRectMaxY(r) then
                            call missiles.insert(missile)
                        endif
                        set j = j - 1
                    set i = i + 1
                endloop
            endif
        endif
    endfunction

    function GroupEnumMissilesInRangeOfLoc takes MissileGroup missiles, location loc, real radius returns nothing
        local real dx
        local real dy
        local integer i
        local Missiles missile

        if missiles != 0 and radius > 0 and loc != null then
            if Missiles.count > -1 then
                set i = 0
            
                if missiles.size > 0 then
                    call missiles.clear()
                endif
            
                loop
                    exitwhen i > Missiles.count
                        set missile = Missiles.collection[i]
                        set dx = missile.x - GetLocationX(loc)
                        set dy = missile.y - GetLocationY(loc)
                    
                        if SquareRoot(dx*dx + dy*dy) <= radius then
                            call missiles.insert(missile)
                        endif
                    set i = i + 1
                endloop
            endif
        endif
    endfunction

    function GroupEnumMissilesInRangeOfLocCounted takes MissileGroup missiles, location loc, real radius, integer amount returns nothing
        local real dx
        local real dy
        local integer i
        local integer j = amount
        local Missiles missile

        if missiles != 0 and radius > 0 and loc != null then
            if Missiles.count > -1 then
                set i = 0
            
                if missiles.size > 0 then
                    call missiles.clear()
                endif
            
                loop
                    exitwhen i > Missiles.count or j == 0
                        set missile = Missiles.collection[i]
                        set dx = missile.x - GetLocationX(loc)
                        set dy = missile.y - GetLocationY(loc)
                    
                        if SquareRoot(dx*dx + dy*dy) <= radius then
                            call missiles.insert(missile)
                        endif
                        set j = j - 1
                    set i = i + 1
                endloop
            endif
        endif
    endfunction

    function GroupEnumMissilesInRange takes MissileGroup missiles, real x, real y, real radius returns nothing
        local real dx
        local real dy
        local integer i
        local Missiles missile

        if missiles != 0 and radius > 0 then
            if Missiles.count > -1 then
                set i = 0
            
                if missiles.size > 0 then
                    call missiles.clear()
                endif
            
                loop
                    exitwhen i > Missiles.count
                        set missile = Missiles.collection[i]
                        set dx = missile.x - x
                        set dy = missile.y - y
                    
                        if SquareRoot(dx*dx + dy*dy) <= radius then
                            call missiles.insert(missile)
                        endif
                    set i = i + 1
                endloop
            endif
        endif
    endfunction

    function GroupEnumMissilesInRangeCounted takes MissileGroup missiles, real x, real y, real radius, integer amount returns nothing
        local real dx
        local real dy
        local integer i
        local integer j = amount
        local Missiles missile

        if missiles != 0 and radius > 0 then
            if Missiles.count > -1 then
                set i = 0
            
                if missiles.size > 0 then
                    call missiles.clear()
                endif
            
                loop
                    exitwhen i > Missiles.count or j == 0
                        set missile = Missiles.collection[i]
                        set dx = missile.x - x
                        set dy = missile.y - y
                    
                        if SquareRoot(dx*dx + dy*dy) <= radius then
                            call missiles.insert(missile)
                        endif
                        set j = j - 1
                    set i = i + 1
                endloop
            endif
        endif
    endfunction
endlibrary

Edit: after some thought, I think making it through a single timer is better
 
Last edited:
Level 9
Joined
Mar 26, 2017
Messages
376
In my personal system, I store the number of stacks in a variable with unit handle as key. (I use lua, but this can be done with hashtable)

When timestop starts, loop through all units in scope and add +1 to variable. If timer ends, loop through the same units that had gained a stack and subtract1.

If the stacks were 0 and they are increased, the unit gets TimeScale to 0, and the unit is paused.
If the stacks are reduced to 0, they get back Timescale to 1, and the unit is unpaused.
 
Level 7
Joined
Feb 9, 2021
Messages
301
In my personal system, I store the number of stacks in a variable with unit handle as key. (I use lua, but this can be done with hashtable)

When timestop starts, loop through all units in scope and add +1 to variable. If timer ends, loop through the same units that had gained a stack and subtract1.

If the stacks were 0 and they are increased, the unit gets TimeScale to 0, and the unit is paused.
If the stacks are reduced to 0, they get back Timescale to 1, and the unit is unpaused.
I think I forgot to specify that I do it through effects, using MemoryHack. So, I can't enumerate the effects.
 
Level 9
Joined
Mar 26, 2017
Messages
376
I think I forgot to specify that I do it through effects, using MemoryHack. So, I can't enumerate the effects.
Ohh ok. Are you sure you want to be so thorough? I think a Time Stop spell is fine if it only stops units, and not effects. Appears to work like that in Dota too, for the time stop bubble.

But I can understand if you would want to go the extra mile.

I don't know memory hack very well. You mean to say the effects are not reachable by variable when created through means of memory hack? If that is the case, I don't see how you could modify effects in any way.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Ohh ok. Are you sure you want to be so thorough? I think a Time Stop spell is fine if it only stops units, and not effects. Appears to work like that in Dota too, for the time stop bubble.

But I can understand if you would want to go the extra mile.

I don't know memory hack very well. You mean to say the effects are not reachable by variable when created through means of memory hack? If that is the case, I don't see how you could modify effects in any way.
My time stop stops ranged attacks and missiles, so I want to stop effects as well. My system will create an effect, play death animation, hide effect and destroy the effect.

They are reachable by variable. Do not focus on the memory hack. It just adds similar functions that are available on 1.31 with effects. My problem is the enumeration of these effects. I want to be able to do "EnumerateEffectsInRange" and then loop through them to do "onPause" or other actions.

It looks something like this: (I did not finish yet)

JASS:
library EffectSystem /*

    */uses /*
    */MemoryHackEffectAPI 

    globals
        private real UPDATE_PERIOD = 0.05
    endglobals
    
    struct EffectSystem
        static integer index = -1
        static thistype array instance
        effect effect
        private real duration
        private real elapsed
        private static timer timer = CreateTimer()
        private integer pauseCounter
        private boolean paused
        private real prevEffectSacle
    
        method resume takes nothing returns nothing
            set this.pauseCounter = this.pauseCounter - 1
            if this.pauseCounter == 0 then
                call SetEffectTimeScale(this.effect, this.prevEffectSacle)
                set this.paused = false
            endif
        endmethod
        
        method pause takes nothing returns nothing
            if this.pauseCounter == 0 then
                set this.prevEffectSacle = GetEffectTimeScale(this.effect)
                call SetEffectTimeScale(this.effect, 0)
                set this.paused = true
            endif
            set this.pauseCounter = this.pauseCounter + 1
        endmethod
        
        method destroy takes nothing returns nothing
        
            set this.elapsed = 0
            call this.deallocate()
        endmethod
        
        private static method remove takes nothing returns nothing
            local integer i = 0
            local thistype this
            loop
                exitwhen i > thistype.index
                set this = instance[i]
                
                if not this.paused then
                    set this.elapsed = this.elapsed + UPDATE_PERIOD
                endif
                
                if (this.elapsed >= this.duration) then
                    call ShowEffect(this.effect, false)
                    call DestroyEffect(this.effect)
                    set instance[i] = instance[index]
                    set i = i - 1
                    set index = index - 1
                    call this.destroy()
                    if (index == -1) then
                        call PauseTimer(thistype.timer)
                    endif
                endif
                set i = i + 1
            endloop

        endmethod
        
        static method create takes string sfxId, real x, real y, real z, real lifetime returns thistype
            local thistype this = thistype.allocate()
            set this.effect = AddSpecialEffect(sfxId, x, y)
            call SetEffectAnimationByIndex(this.effect, 1)
            call SetEffectZEx(this.effect, z)
            
            set index = index + 1
            set instance[index] = this
            if (index == 0) then
                call TimerStart(thistype.timer, UPDATE_PERIOD, true, function thistype.remove)
            endif
            set this.duration = lifetime
            
            return this
        endmethod
    
    endstruct
    /*
    function CreateSpeicalEffectEx takes string sfxId, real x, real y, real z, real lifetime returns thistype
        return EffectSystem.create(sfxId, x, y, z, lifetime)
    endfunction
    */
endlibrary
 
Last edited:
Level 7
Joined
Feb 9, 2021
Messages
301
Can you obtain the coordinates? Then you can loop through all effects, and stop those with certain coords.
Yes, I can, but I can't loop through effects. There is no effect enumeration function.

EDIT: any feedback on the system I posted is welcome
 
Last edited:
Level 7
Joined
Feb 9, 2021
Messages
301
You can use indexing.
If you checked my code, I already did it through indexing. What I do not understand is how to make it similar to MissileUtils. He created functions for creating Groups, FirstOfGroup and etc. While I see the code, I do not understand, how to adapt it for my effect system. For example, what does it mean to "take MissileGroup source " and some other things.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,538
I imagine you enumerate over every single active missile and check if it meets the required conditions (is it in range?). If it does then you add it's index to an array which represents your Group. Then once all missiles have been checked you enumerate over this array (Group) and interact with the missiles.

for loop A from 0 to groupSize do
set mIndex = arrayGroup[A]
set this = instance[mIndex]
call this.pause()

There's probably a better way of doing it but that should be the general idea.
 
Last edited:
Status
Not open for further replies.
Top