• 🏆 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!

[Snippet] Timed Effect

Level 33
Joined
Apr 24, 2012
Messages
5,113
JASS:
library TimedEffect /* v1.5
*************************************************************************************
*
*    Allows one to create timed effects on 3d points or units.
*
*************************************************************************************
*
*    API
*
*    static method point takes string sfx, real x, real y, real z, real duration returns thistype
*        - starts a timed effect on a 3d point
*
*    static method target takes string sfx, widget target, string attach, real duration returns thistype
*        - starts a timed effect on a unit
*
*    method destroy takes nothing returns nothing
*        - destroys an instance
*
*    real time
*        - the time remaining of the effect's life
*        - use this if you want to adjust the time
*
*************************************************************************************
*
*    Credits
*
*       PurgeAndFire for the OTip trick.
*       Bannar
*
**************************************************************************************/
    globals
        // Timeout of the effects 
        private constant real TIMEOUT = 0.03125
        // Platform is used for creating special effects with z points
        private constant integer PLATFORM = 'OTip'
    endglobals
    
    struct TimedEffect
        private static constant timer t = CreateTimer()
        
        private static effect tempEffect
        private static destructable platform
        
        private static thistype array next
        private static thistype array prev
       
        private effect effects
        
        real time
        
        method destroy takes nothing returns nothing
            if effects != null then
                call DestroyEffect(effects)
                set effects = null
                
                set time = 0
                
                set next[prev[this]] = next[this]
                set prev[next[this]] = prev[this]
                if next[this] == 0 then
                    call PauseTimer(t)
                endif
            endif
        endmethod
        
        private static method P takes nothing returns nothing
            local thistype this = next[0]
            loop
                exitwhen 0 == this
                set time = time - TIMEOUT
                if 0 > time then
                    call destroy()
                endif
                set this = next[this]
            endloop
        endmethod
        
        private method insert takes nothing returns nothing
            set next[this] = 0
            set prev[this] = prev[0]
            set next[prev[0]] = this
            set prev[0] = this
            
            if prev[this] == 0 then
                call TimerStart(t, TIMEOUT, true, function thistype.P)
            endif
        endmethod
        
        static method point takes string sfx, real x, real y, real z, real duration returns thistype
            local thistype this
            if sfx != "" then
                if 0 < z then
                    set platform = CreateDestructableZ(PLATFORM, x, y, z, 0, 1, 0)
                endif
                set tempEffect = AddSpecialEffect(sfx, x, y)
                if null != platform then
                    call RemoveDestructable(platform)
                    set platform = null
                endif
                if duration < TIMEOUT then
                    call DestroyEffect(tempEffect)
                    set tempEffect = null
                    return 0
                endif
                set this = allocate()
                set effects = tempEffect
                set time = duration
                set tempEffect = null
                call insert()
                return this
            endif
            return 0
        endmethod
        
        static method target takes string sfx, widget target, string attach, real duration returns thistype
            local thistype this
            if sfx != "" and target != null then
                set tempEffect = AddSpecialEffectTarget(sfx, target, attach)
                if duration < TIMEOUT then
                    call DestroyEffect(tempEffect)
                    set tempEffect = null
                    return 0
                endif
                set this = allocate()
                set effects = tempEffect
                set time = duration
                set tempEffect = null
                call insert()
                return this
            endif
            return 0
        endmethod
    endstruct
endlibrary

Demo:
JASS:
struct Tester extends array
    static timer tm = CreateTimer()
    static integer i = 0
    private static method looper takes nothing returns nothing
        local real x = GetRandomReal(-1000, 1000)
        local real y = GetRandomReal(-1000, 1000)
        local real z = GetRandomReal(1, 500)
        local real dur = 3
        local TimedEffect te = TimedEffect.point( "units\\creeps\\NerubianQueen\\NerubianQueen.mdl", x, y, z, dur)
        set i = i + 1
        if i > 10 then
            set te.time = 3
        endif
    endmethod
    
    private static method onInit takes nothing returns nothing
        call FogEnable(false)
        call FogMaskEnable(false)
        call TimerStart(tm, 0.1, true, function Tester.looper)
    endmethod
endstruct
 
Last edited:
RIP Readable variable names.

Also:
startOnUnit -> createOnUnit
startOn3DPoint -> createAtPoint

And don't use the struct name directly inside the struct when you want to refer to static methods, just use thistype because the struct name could be changed in the future and that would cause problems. The code is currently less maintainable.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
just a question, why wont you just implement Alloc, you are uselessly typing the linked stack allocation over and over again in every resource

Also why are you using periodic loop and not start the timer for the duration specified?
also you could make the variables names a little more intuitive, saying private static effect se will not say much to me
 
JASS:
private static timer t = CreateTimer()
private thistype recycle
private static thistype instance = 0
private thistype next
private thistype prev
private static integer count = 0
private effect fx
private static effect tempFx
private real dur
private static destructable platform

Can you please clean this mess of globals up? :V
It's too hard to see anything. It's like one continuous block that I have to take in all at once.

Split it into blocks. Put statics with statics and non-statics with non-statics.

JASS:
private static timer t = CreateTimer()

private static thistype instance = 0
private static integer count = 0

private static effect tempFx
private static destructable platform

private thistype recycle
private thistype next
private thistype prev

private effect fx
private real dur

^THAT looks a lot better.
Also, don't use names like "fx" and "dur", use full, meaningful variable names.
Code needs to be readable. If it's not readable, it's trash.

And put line breaks in your code to split it into blocks so it's easier to read and take in.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
is this library works correctly? I tried this code but the effect was never destroyed
JASS:
local TimedEffect sfx

set sfx = TimedEffect.createOnPoint("Abilities\\Spells\\Other\\Tornado\\Tornado_Target.mdl", x, y, 0, 2)
// I also tried this but still not destroyed after 2 s
call TimedEffect.createOnPoint("Abilities\\Spells\\Other\\Tornado\\Tornado_Target.mdl", x, y, 0, 2)
 
Level 13
Joined
May 24, 2005
Messages
609
Below is a similiar script I'm using in my maps for timed effects.
While there's a bit less functionality (e.g. no option for Z axis) , it's quite simple and straightforewarded.

JASS:
library EffectUtils initializer Init requires TimerUtils
// ====================================================================
//
//  EffectUtils by MoCo
//
//  A simple and easy to use toolset for timed effects.
//
//  use RegisterEffect(yourEffect, lifeTime) 
//   - After the lifeTime runs out, the effect is automatically destroyed and recycled.
//   - The RegisterEffect functions returns a unique effect id so you can access the effect during its lifeTime.
//   - A Lifetime of 0 makes the effect permanent, so you should manually recycle it via ReleaseEffect(effect_id) later.
// 
//  use ReleaseEffect(effect id) to destroy and recycle a registered effect
//   - You should always use this function to recycle permanent effects when you do not need them anymore.
//   - You can as well use the function for timed effects to destroy them before the end of their lifeTime.
//
//  use EFFECT_MAX to define the limit of simultanously active effects allowed
//   - Effects are  getting automatically recycled if the limit is exceeded.
//   - A lower value means faster auto-recycling of permanent effects.
//
// ====================================================================
//  Examples:
//  
//  (1)
//
//  local effect e = AddSpecialEffect("myEffect", x, y)
//  call RegisterEffect(e, lifeTime)
//
//  (2)
//
//  local integer effectId 
//  local effect e = AddSpecialEffectTarget("myEffect", u, "origin")
//  set effectId = RegisterEffect(e, 0)
//     ...
//  call ReleaseEffect(effectId)
//
// ====================================================================

globals
    private constant integer EFFECT_MAX = 256
    private integer e_pointer = 0
    private effect array effects[1]
endglobals

function ReleaseEffect takes integer id returns nothing
    if effects[id] != null then
        call DestroyEffect(effects[id])
        set effects[id] = null
    endif
endfunction

private function TimedEffectFinished takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetTimerData(t)
    call ReleaseTimer(t)
    set t = null
    call ReleaseEffect(id)
endfunction

// Effects with timed life are automatically destroyed
function RegisterEffect takes effect e, real lifespan returns integer
    local timer t
    local integer id = e_pointer
    
    // If an old effect hasn't cleaned up -> destroy
    if effects[id] != null then
        call DestroyEffect(effects[id])
        debug call BJDebugMsg("An effect hasn't been properly released!")
    endif
    
    // Store Effect and inc ID
    set effects[id] = e
    if e_pointer < EFFECT_MAX - 1 then
        set e_pointer = e_pointer + 1
    else
        set e_pointer = 0
    endif
    
    // Timed life?
    if lifespan > 0 then
        set t = NewTimer()
        call SetTimerData(t, id)
        call TimerStart(t, lifespan, false, function TimedEffectFinished)
    endif
    
    set t = null
    return id
endfunction

private function Init takes nothing returns nothing
    local integer i = 0
    loop
        exitwhen i > (EFFECT_MAX-1)
        set effects[i] = null
        set i = i + 1
    endloop
endfunction

endlibrary
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
I can not belive that after all this time this sits here (in approved resourcers) without being actually fixed.

Bugs coming from effect not being destroyed, are annoying, especially in loops and comes from author not being careful enough.
JASS:
        private static effect tempEffect
        private static destructable platform
Don't you see whats missing? Yes, you haven't nulled them at initialization.

Again, operator newDuration is rather pointless. Let time member be public. Or at least change "newDuration" to "duration".
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
You still haven't fixed a couple of things I had listed. Whatmore, I see that few new fellows joined the company.

Multiple occurances of 0.0(...) - you should provide global constant for that. Let's give user an opportunity to change this factor. Platform id could use the same suggestion.
Static 'tempEffect' and additional integer 'count' are redundant. Since you are using a linked list structure, just refer to your head (0) and see if it's pointing to empty reference.
Critical error: platform isn't initialized. This has been posted months ago - still crashes your system after single call.

Naming of createOnPoint & createOnUnit methods seems lacklucter. Moreover, you do can create effects on a widget target, rather then being restricted to unit objects only.

Instead, why not use native effect-related function naming? "create" for default, "createTarget" for widgets. 'effects' handle should be readonly and snippet lacks function api - the api that actually returns effects. Yes, one could call <struct>.<ctor method>.effect but your member is private anyway.

Below is snippet which I'm using - you can copy whole script. Me don't care <ogre :3>.
JASS:
/*****************************************************************************
*
*    DelayedEffect
*
*    Allows one to destroy an effect after specified amount of time.
*
******************************************************************************
*
*    Functions:
*
*       function DestroyEffectDelayed takes string modelName, real x, real y, real y, real delay returns effect
*          wrapper for delayed destruction of point-effect
*
*       function DestroyEffectTargetDelayed takes string modelName, widget targetWidget, string attachPointName, real delay returns effect
*          wrapper for delayed destruction of target-effect
*
******************************************************************************
*
*    struct DelayedEffect:
*
*       readonly effect effect
*          actual effect handle
*
*       public real duration
*          remaining duration of effect
*
*       static method create takes string modelName, real x, real y, real z, real delay returns thistype
*          point-ctor of delayed effect
*
*       static method createTarget takes string modelName, widget targetWidget, string attachPointName, real delay returns thistype
*          target-ctor of delayed effect
*
*       method destroy takes nothing returns nothing
*          dctor of delayed effect
*
*****************************************************************************/
library DelayedEffect

    globals
        private constant real    PERIOD       = 0.031250000
        private constant integer PLATFORM_ID  = 'OTip'
    endglobals

    globals
        private destructable dummy = null
        private timer looper = CreateTimer()
    endglobals

    struct DelayedEffect extends array
        private static integer count = 0
        private thistype recycle
        private thistype next
        private thistype prev

        readonly effect effect
        public real duration

        private method deallocate takes nothing returns nothing
            set this.recycle = thistype(0).recycle
            set thistype(0).recycle = this
            set this.next.prev = this.prev
            set this.prev.next = this.next

            if ( thistype(0).next == 0 ) then
                call PauseTimer(looper)
            endif
        endmethod

        method destroy takes nothing returns nothing
            call DestroyEffect(effect)
            set effect = null

            call deallocate()
        endmethod

        private static method onCallback takes nothing returns nothing
            local thistype this = thistype(0).next

            loop
                exitwhen this == 0
                set duration = duration - PERIOD

                if ( duration <= 0 ) then
                    call destroy()
                endif

                set this = this.next
            endloop
        endmethod

        private static method allocate takes nothing returns thistype
            local thistype this = thistype(0).recycle
            if ( thistype(0).next == 0 ) then
                call TimerStart(looper, PERIOD, true, function thistype.onCallback)
            endif

            if ( this == 0 ) then
                set count = count + 1
                set this = count
            else
                set thistype(0).recycle = this.recycle
            endif

            set this.next = 0
            set this.prev = thistype(0).prev
            set thistype(0).prev.next = this
            set thistype(0).prev = this

            return this
        endmethod

        static method create takes string modelName, real x, real y, real z, real delay returns thistype
            local thistype this = 0

            if ( z > 0 ) then
                set dummy = CreateDestructableZ(PLATFORM_ID, x, y, z, 0, 1, 0)
            endif

            if ( duration >= PERIOD ) then
                set this = allocate()
                set effect = AddSpecialEffect(modelName, x, y)
                set duration = delay
            else
                call DestroyEffect( AddSpecialEffect(modelName, x, y) )
            endif

            call RemoveDestructable(dummy)
            set dummy = null

            return this
        endmethod

        static method createTarget takes string modelName, widget targetWidget, string attachPointName, real delay returns thistype
            local thistype this = 0

            if ( duration >= PERIOD ) then
                set this = allocate()
                set effect = AddSpecialEffectTarget(modelName, targetWidget, attachPointName)
                set duration = delay
            else
                call DestroyEffect( AddSpecialEffectTarget(modelName, targetWidget, attachPointName) )
            endif

            return this
        endmethod
    endstruct

    function DestroyEffectDelayed takes string modelName, real x, real y, real z, real delay returns effect
        return DelayedEffect.create(modelName, x, y, z, delay).effect
    endfunction

    function DestroyEffectTargetDelayed takes string modelName, widget targetWidget, string attachPointName, real delay returns effect
        return DelayedEffect.createTarget(modelName, targetWidget, attachPointName, delay).effect
    endfunction

endlibrary
There is possibility for TimerUtils version to improve accurancy but worse efficiency. Most of the time accurancy loss of < 1/32 sec is irrelevant, though.
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
Any statement by the author on TriggerHappys and Bannars comments?

Personnally I would create effects attached to a dummy unit using dummy.mdl.
This allows you set scale and pitch angle.

Using a platform is also an option. :)

Well, I would recreate Nes' Particle if you want some dummy


Btw, please graveyard this. I have lost my intention of updating this
 
Top