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

[vJASS] SpecialEffect

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
With the introduction of effect manipulation natives in recent patches, the long held practice of dummy usage has become obsolete. Instead, we now rely purely on effects. But to do that, we need to be able to: 1.) have delayed effect destruction, which allows effects to continue its motion while still playing its death animation, and 2.) attach multiple models and control it as a single unit, like we were able to before by attaching multiple effects to the dummy unit.
This library allows you to easily transition into the new method of using pure effects. Its API is fairly simple and straightforward.


specialeffect.j
JASS:
library SpecialEffect /* v1.3.1 by AGD | https://www.hiveworkshop.com/threads/specialeffect.325954/ | Patches 1.31+


    */uses /*

    */LinkedList            /*  https://www.hiveworkshop.com/threads/325635/ | Should use atleast v1.3.0

    */optional Table        /*  https://www.hiveworkshop.com/threads/188084/
    */optional Alloc        /*  https://www.hiveworkshop.com/threads/324937/
    */optional ErrorMessage /*  https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j


    *///! novjass

    |-----|
    | API |
    |-----|
    /*

      */struct SpecialEffect extends array/*

          */readonly real x     /*
          */readonly real y     /*
          */readonly real z     /*  Absolute height
          */readonly real height/*  Height relative to the ground
          */readonly real yaw   /*
          */readonly real pitch /*
          */readonly real roll  /*

          */method currentHandle            takes nothing                               returns effect/*
                - Current <effect> handle pointed by the iterator
          */method resetIterator            takes nothing                               returns nothing/*
          */method moveIterator             takes nothing                               returns boolean/*
                - Use these iterator methods together with 'currentHandle()' if you need to traverse
                  each <effect> handle
                - moveIterator() returns true when it moves past the last element of the list
                  (resetIterator() is automatically called when this happens)
                - Sample Usage:
                    local SpecialEffect sfx = this.specialEffect
                    loop
                        exitwhen sfx.moveIterator()
                        call BlzSetSpecialEffectAlpha(sfx.currentHandle(), 0xAA)
                    endloop

          */method killHandle               takes effect effectHandle, real deathTime   returns nothing/*
                - Kills a specific <effect> handle, playing its death animation
          */method killModel                takes string model, real deathTime          returns nothing/*
                - Kills all <effect> handles matching the specific model, playing their death animation
          */method kill                     takes real deathTime, boolean wantDestroy   returns nothing/*
                - Kills all models, playing their death animation and destroys this SpecialEffect
                  instance afterwards if <wantDestroy> is true

          */method addModel                 takes string model                          returns effect/*
                - You can add multiple similar models
          */method removeHandle             takes effect effectHandle                   returns nothing/*
                - Removes a specific <effect> handle if found
          */method removeModel              takes string model                          returns nothing/*
                - Removes all <effect> handles matching the given model
          */method clearModels              takes nothing                               returns nothing/*
                - Removes all <effect> handles

                Note:
                    removeHandle(), removeModel(), and clearModels() instantly vanishes the attached
                    models without playing their death animation

          */method getHandle                takes string model                          returns effect/*
                - For random access (Uses linear search actually)
                - Returns the first instance of <effect> handle matching the given model

          */method move                     takes real x, real y, real z                returns nothing/*
          */method moveRelative             takes real x, real y, real height           returns nothing/*
                - Keeps the offset between the actual position of the <effect> handles and the
                  origin coordinates of this SpecialEffect instance
          */method setPosition              takes real x, real y, real z                returns nothing/*
          */method setPositionRelative      takes real x, real y, real height           returns nothing/*
                - Sets the actual position of all <effect> handles
          */method setOrientation           takes real yaw, real pitch, real roll       returns nothing/*

          */method setVisibility            takes player whichPlayer, boolean visible   returns nothing/*
                - Toggles the visibility flag locally for a player

          */static method create            takes real x, real y, real z                returns SpecialEffect/*
          */static method createRelative    takes real x, real y, real height           returns SpecialEffect/*
                - Constructors
          */method destroy                  takes nothing                               returns nothing/*
                - Calls clearModels() and destroys this SpecialEffect instance
                - Dying instances are also immediately vanished by this method


    *///! endnovjass

    globals
        /*
        *   Special effects that need to be instantly vanished are moved to this coordinates before destroyed
        */
        private constant real HIDDEN_PLACE_X        = 0.00
        private constant real HIDDEN_PLACE_Y        = 10000.00

        private location loc = Location(0.00, 0.00)
    endglobals

    /*========================================== SYSTEM CODE ==========================================*/

    static if DEBUG_MODE then
        private function AssertError takes boolean condition, string methodName, string structName, integer node, string message returns nothing
            static if LIBRARY_ErrorMessage then
                call ThrowError(condition, SCOPE_PREFIX, methodName, structName, node, message)
            else
                if condition then
                    call BJDebugMsg("[Library: " + SCOPE_PREFIX + "] [Struct: " + structName + "] [Method: " + methodName + "] [Instance: " + I2S(node) + "] : |cffff0000" + message + "|r")
                endif
            endif
        endfunction
    endif

    public function GetTerrainZ takes real x, real y returns real
        call MoveLocation(loc, x, y)
        return GetLocationZ(loc)
    endfunction

    /*
    *   Allocator for the whole lib
    */
    private struct Node extends array
        static if LIBRARY_Alloc then
            implement optional Alloc
        else
            private static thistype array stack
            debug method operator allocated takes nothing returns boolean
                debug return this > 0 and stack[this] == 0
            debug endmethod
            /*
            *   Credits to MyPad for the algorithm
            */
            static method allocate takes nothing returns thistype
                local thistype node = stack[0]
                if stack[node] == 0 then
                    debug call AssertError(node == JASS_MAX_ARRAY_SIZE - 2, "allocate()", "thistype", node, "Overflow")
                    set node = node + 1
                    set stack[0] = node
                else
                    set stack[0] = stack[node]
                    set stack[node] = 0
                endif
                return node
            endmethod
            method deallocate takes nothing returns nothing
                debug call AssertError(not this.allocated, "deallocate()", "thistype", this, "Double-free")
                set stack[this] = stack[0]
                set stack[0] = this
            endmethod
        endif
    endstruct

    static if DEBUG_MODE then
        private function AssertNodeValidity takes Node node, string methodName, string objectName returns nothing
            static if Node.allocated.exists then
                debug call AssertError(not node.allocated, methodName, objectName, node, "Invalid node")
            endif
        endfunction
    endif

    private function VanishEffect takes effect e returns nothing
        call BlzSetSpecialEffectX(e, HIDDEN_PLACE_X)
        call BlzSetSpecialEffectY(e, HIDDEN_PLACE_Y)
        call DestroyEffect(e)
    endfunction

    /*
    *   List of <effect>s
    */
    private struct EffectHandle extends array
        readonly effect effect
        SpecialEffect parent
        string model
        real dx
        real dy
        real dz

        private static method onInsert takes thistype node returns nothing
            local SpecialEffect parent = node.parent
            set node.effect = AddSpecialEffect(node.model, HIDDEN_PLACE_X, HIDDEN_PLACE_Y)
            call BlzSetSpecialEffectPosition(node.effect, parent.x, parent.y, parent.z)
            call BlzSetSpecialEffectOrientation(node.effect, parent.yaw, parent.pitch, parent.roll)
        endmethod

        private static method onRemove takes thistype node returns nothing
            call VanishEffect(node.effect)
            set node.effect = null
            set node.model = null
            call Node(node).deallocate()
        endmethod

        private static method allocate takes nothing returns thistype
            return Node.allocate()
        endmethod
        private method deallocate takes nothing returns nothing
            call Node(this).deallocate()
        endmethod

        implement InstantiatedList
        implement LinkedListEx

        method getHandleIndex takes effect e returns thistype
            local thistype node = this.next
            loop
                exitwhen node == this or node.effect == e
            endloop
            return node
        endmethod
        method getModelIndex takes string model returns thistype
            local thistype node = this.next
            loop
                exitwhen node == this or node.model == model
                set node = node.next
            endloop
            return node
        endmethod
    endstruct

    private struct DelayedCleanupList extends array
        EffectHandle effectHandle

        private static method onRemove takes thistype node returns nothing
            if EffectHandle.isLinked(node.effectHandle) then
                call EffectHandle.remove(node.effectHandle)
            endif
            call Node(node).deallocate()
        endmethod

        implement List
    endstruct

    /*
    *   SpecialEffect is a cluster of <effect>s that can easily be controlled as a single object
    */
    struct SpecialEffect extends array
        readonly real x
        readonly real y
        readonly real z
        readonly real height
        readonly real yaw
        readonly real pitch
        readonly real roll

        private boolean hidden
        private boolean wantDestroy
        private EffectHandle current
        private EffectHandle killed
        private thistype instance

        static if LIBRARY_Table then
            private static key table
        else
            private static hashtable table = InitHashtable()
        endif

        private method startTimer takes real timeout, code callback returns nothing
            local timer t = CreateTimer()

            static if LIBRARY_Table then
                set Table(table)[GetHandleId(t)] = this
            else
                call SaveInteger(table, 0, GetHandleId(t), this)
            endif

            call TimerStart(t, timeout, false, callback)
            set t = null
        endmethod

        private static method releaseTimer takes nothing returns thistype
            local timer t = GetExpiredTimer()
            local integer id = GetHandleId(t)

            static if LIBRARY_Table then
                local thistype node = Table(table)[id]
                call Table(table).remove(id)
            else
                local thistype node = LoadInteger(table, 0, id)
                call RemoveSavedInteger(table, 0, id)
            endif

            call DestroyTimer(t)
            set t = null

            return node
        endmethod

        private method operator handle takes nothing returns EffectHandle
            return this
        endmethod

        method currentHandle takes nothing returns effect
            return this.current.effect
        endmethod

        method resetIterator takes nothing returns nothing
            set this.current = this.handle
        endmethod
        method moveIterator takes nothing returns boolean
            set this.current = this.current.next
            return this.current == this.handle
        endmethod

        private static method onSetOrientation takes EffectHandle list, real yaw, real pitch, real roll returns nothing
            local EffectHandle node = list.next
            loop
                exitwhen node == list
                call BlzSetSpecialEffectOrientation(node.effect, yaw, pitch, roll)
                set node = node.next
            endloop
        endmethod
        private static method onSetPosition takes EffectHandle list, real x, real y, real z returns nothing
            local EffectHandle node = list.next
            loop
                exitwhen node == list
                call BlzSetSpecialEffectPosition(node.effect, x, y, z)
                set node = node.next
            endloop
        endmethod
        private static method onMove takes EffectHandle list, real dx, real dy, real dz returns nothing
            local EffectHandle node = list.next
            loop
                exitwhen node == list
                call BlzSetSpecialEffectPosition(node.effect, BlzGetLocalSpecialEffectX(node.effect) + dx, BlzGetLocalSpecialEffectY(node.effect) + dy, BlzGetLocalSpecialEffectZ(node.effect) + dz)
                set node = node.next
            endloop
        endmethod
        private method onSetVisibility takes EffectHandle list, player whichPlayer, boolean visible returns nothing
            local EffectHandle node = list.next
            loop
                exitwhen node == list
                if visible then
                    call BlzSetSpecialEffectPosition(node.effect, this.x + node.dx, this.y + node.dy, this.z + node.dz)
                else
                    set node.dx = BlzGetLocalSpecialEffectX(node.effect) - this.x
                    set node.dy = BlzGetLocalSpecialEffectY(node.effect) - this.y
                    set node.dz = BlzGetLocalSpecialEffectZ(node.effect) - this.z
                    call BlzSetSpecialEffectX(node.effect, HIDDEN_PLACE_X)
                    call BlzSetSpecialEffectY(node.effect, HIDDEN_PLACE_Y)
                endif
                set node = node.next
            endloop
        endmethod

        private method updatePosition takes real x, real y, real z, real height returns nothing
            set this.x = x
            set this.y = y
            set this.z = z
            set this.height = height
        endmethod

        method setOrientation takes real yaw, real pitch, real roll returns nothing
            debug call AssertNodeValidity(this, "setOrientation()", "thistype")
            call onSetOrientation(this.handle, yaw, pitch, roll)
            call onSetOrientation(this.killed, yaw, pitch, roll)
            set this.yaw = yaw
            set this.pitch = pitch
            set this.roll = roll
        endmethod

        private method setPositionEx takes real x, real y, real z, real height returns nothing
            call onSetPosition(this.handle, x, y, z)
            call onSetPosition(this.killed, x, y, z)
            call this.updatePosition(x, y, z, height)
        endmethod
        method setPosition takes real x, real y, real z returns nothing
            debug call AssertNodeValidity(this, "setPosition()", "thistype")
            call this.setPositionEx(x, y, z, z - GetTerrainZ(x, y))
        endmethod
        method setPositionRelative takes real x, real y, real height returns nothing
            debug call AssertNodeValidity(this, "setPositionRelative()", "thistype")
            call this.setPositionEx(x, y, height + GetTerrainZ(x, y), height)
        endmethod

        private method moveEx takes real x, real y, real z, real height returns nothing
            call onMove(this.handle, x - this.x, y - this.y, z - this.z)
            call onMove(this.killed, x - this.x, y - this.y, z - this.z)
            call this.updatePosition(x, y, z, height)
        endmethod
        method move takes real x, real y, real z returns nothing
            debug call AssertNodeValidity(this, "move()", "thistype")
            call this.moveEx(x, y, z, z - GetTerrainZ(x, y))
        endmethod
        method moveRelative takes real x, real y, real height returns nothing
            debug call AssertNodeValidity(this, "moveRelative()", "thistype")
            call this.moveEx(x, y, height + GetTerrainZ(x, y), height)
        endmethod

        method setVisibility takes player whichPlayer, boolean visible returns nothing
            debug call AssertNodeValidity(this, "setVisibility()", "thistype")
            if whichPlayer == GetLocalPlayer() and this.hidden == visible then
                call this.onSetVisibility(this.handle, whichPlayer, visible)
                call this.onSetVisibility(this.killed, whichPlayer, visible)
                set this.hidden = not visible
            endif
        endmethod

        method getHandle takes string model returns effect
            debug call AssertNodeValidity(this, "getHandle()", "thistype")
            return this.handle.getModelIndex(model).effect
        endmethod

        method addModel takes string model returns effect
            local EffectHandle node = Node.allocate()
            debug call AssertNodeValidity(this, "addModel()", "thistype")
            set node.parent = this.handle
            set node.model = model
            call EffectHandle.insert(this.handle.getModelIndex(model).prev, node)
            return node.effect
        endmethod
        method removeHandle takes effect e returns nothing
            local EffectHandle node = this.handle.next
            debug call AssertNodeValidity(this, "removeHandle()", "thistype")
            loop
                exitwhen node == this.handle
                if node.effect == e then
                    call EffectHandle.remove(node)
                    exitwhen true
                endif
                set node = node.next
            endloop
        endmethod
        method removeModel takes string model returns nothing
            local EffectHandle node = this.handle.getModelIndex(model)
            debug call AssertNodeValidity(this, "removeModel()", "thistype")
            if node != this.handle then
                loop
                    exitwhen node.model != model
                    call EffectHandle.remove(node)
                    set node = node.next
                endloop
            endif
        endmethod
        method clearModels takes nothing returns nothing
            debug call AssertNodeValidity(this, "clearModels()", "thistype")
            call this.killed.flush()
            call this.handle.flush()
        endmethod

        private static method createEx takes real x, real y, real z, real height returns thistype
            local thistype node     = EffectHandle.create()
            set node.killed         = EffectHandle.create()
            set node.yaw            = 0
            set node.pitch          = 0
            set node.roll           = 0
            set node.hidden         = false
            call node.updatePosition(x, y, z, height)
            call node.resetIterator()
            return node
        endmethod

        static method create takes real x, real y, real z returns thistype
            return createEx(x, y, z, z - GetTerrainZ(x, y))
        endmethod
        static method createRelative takes real x, real y, real height returns thistype
            return createEx(x, y, height + GetTerrainZ(x, y), height)
        endmethod

        method destroy takes nothing returns nothing
            debug call AssertNodeValidity(this, "destroy()", "thistype")
            call this.killed.destroy()
            call this.handle.destroy()
        endmethod

        private static method onExpireKill takes nothing returns nothing
            local thistype node = releaseTimer()
            if EffectHandle.isLinked(node.instance) then
                call EffectHandle.remove(node.instance)
            endif
            call Node(node).deallocate()
        endmethod
        private static method onExpireKillRange takes nothing returns nothing
            local DelayedCleanupList list = releaseTimer()
            local thistype node = thistype(list).instance
            set thistype(list).instance = 0
            if node.wantDestroy then
                set node.wantDestroy = false
                call node.destroy()
            endif
            call list.flush()
            call Node(list).deallocate()
        endmethod

        private method killHandleRange takes string model, real deathTime, boolean wantDestroy returns nothing
            local DelayedCleanupList list
            local DelayedCleanupList temp
            local thistype node
            local thistype next
            local boolean resetIter = false

            if not this.handle.empty then
                set this.wantDestroy = wantDestroy

                if model == null then
                    set node = this.handle.next
                else
                    set node = this.handle.getModelIndex(model)
                endif

                if node != this.handle then
                    set list = Node.allocate()
                    set thistype(list).instance = this
                    call DelayedCleanupList.makeHead(list)

                    loop
                        exitwhen node == this.handle or (node.handle.model != model and model != null)
                        set next = node.handle.next
                        if node.handle == this.current then
                            set resetIter = true
                        endif
                        set temp = Node.allocate()
                        set temp.effectHandle = node
                        call list.pushBack(temp)
                        call EffectHandle.move(this.killed.prev, node)
                        call BlzPlaySpecialEffect(node.handle.effect, ANIM_TYPE_DEATH)
                        set node = next
                    endloop

                    if resetIter then
                        set this.current = node.handle.prev
                    endif

                    call thistype(list).startTimer(deathTime, function thistype.onExpireKillRange)
                endif

            elseif wantDestroy then
                call this.destroy()
            endif
        endmethod

        method killHandle takes effect e, real deathTime returns nothing
            local thistype node
            local thistype instance
            debug call AssertNodeValidity(this, "killHandle()", "thistype")
            if not this.handle.empty then
                set instance = this.handle.getHandleIndex(e)
                if instance != this.handle then
                    set node = Node.allocate()
                    set node.instance = instance
                    if instance.handle == this.current then
                        set this.current = this.current.prev
                    endif
                    call EffectHandle.move(this.killed.prev, instance.handle)
                    call BlzPlaySpecialEffect(instance.handle.effect, ANIM_TYPE_DEATH)
                    call node.startTimer(deathTime, function thistype.onExpireKill)
                endif
            endif
        endmethod

        method killModel takes string model, real deathTime returns nothing
            debug call AssertNodeValidity(this, "killModel()", "thistype")
            call this.killHandleRange(model, deathTime, false)
        endmethod

        method kill takes real deathTime, boolean wantDestroy returns nothing
            debug call AssertNodeValidity(this, "kill()", "thistype")
            call this.killHandleRange(null, deathTime, wantDestroy)
        endmethod
    endstruct


endlibrary


v1.3.1
- Fixed compatility for the usage (or non-usage) of any Alloc module
- Other small fixes and changes

v1.3.0
- Reverted to using BlzSetSpecialEffectOrientation() from BlzSetSpecialEffectYaw/Pitch/Roll() due to the later's different output
- Added ability to add multiple similar models
- Added ability to remove/kill a specific effect handle
- Other changes

v1.2.0
- Added methods for manipulation using height relative to ground
- Changed the orientation natives used

v1.1.0
- Fixed a bug related to the iterator methods
- Added a new feature to toggle the visibility of a special effect locally for a player (see API documentation)

v1.0.0
- Initial release
 
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Very nice and vote for approval.

The iterator and random handle access are a bit weird though. Like why do they cycle through effects globally?

If you want to offer a list functionality by this system, you should actually provide an EffectGroup api similar to how unit groups work with actual enumeration and create/destroy of the group.
Currently, the use cases of your iterator are very limited. I feel its just pointless code bloat.
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
The iterator actually ccycles through a local list - the list of effect handles for a SpecialEffect instance, since SpecialEffect is basically a group of effects. Same with getHandle(), which searches the local list.

The iterator is provided so that users can easily loop through the group and call the corresponding natives themselves for the wanted operation. Otherwise, the system would have to provide wrapper methods for each operation like changing effect color, scaling, animation, etc. which would result to more code bloat.
 
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
The iterator actually ccycles through a local list - the list of effect handles for a SpecialEffect instance, since SpecialEffect is basically a group of effects. Same with getHandle(), which searches the local list.

The iterator is provided so that users can easily loop through the group and call the corresponding natives themselves for the wanted operation. Otherwise, the system would have to provide wrapper methods for each operation like changing effect color, scaling, animation, etc. which would result to more code bloat.
Ah i see. Then I misunderstood the purpose.

Here's another suggestion:

Add the capability to create effects locally for a force of players by using local model paths.
Using your effect struct in a local block seems a bit risky even though it might work if done properly.

Alternatively, provide a showEffect(force for, boolean show) method that replaces all effect models with an empty model for the specified players).
Just setting the alpha to 0 locally might not be enough, as I think alpha doesnt hide particle emitters.
Changing the Position to the hidden spot locally might also do the trick. Not sure if it causes desync to do so. I couldn't imagine why it should, but blizzard is weird at times.
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Ah i see. Then I misunderstood the purpose.

Here's another suggestion:

Add the capability to create effects locally for a force of players by using local model paths.
Using your effect struct in a local block seems a bit risky even though it might work if done properly.

Alternatively, provide a showEffect(force for, boolean show) method that replaces all effect models with an empty model for the specified players).
Just setting the alpha to 0 locally might not be enough, as I think alpha doesnt hide particle emitters.
Changing the Position to the hidden spot locally might also do the trick. Not sure if it causes desync to do so. I couldn't imagine why it should, but blizzard is weird at times.
Btw, wouldn't BlzSetSpeciallEffectScale(effect, 0) do the trick?

Do you think making a special effect recycler is also important?
I mean if we can just play a specific animation to effects right?
I'm not sure recycling would be better than creating and destroying cause in recycling you need to perform resets on the properties of the effects which would remove any benefit of recycling over simple creation. Not to mention you need different stacks for effects with different models.
 
Last edited:
Level 20
Joined
Aug 13, 2013
Messages
1,696
wouldn't BlzSetSpeciallEffectScale(effect, 0) do the trick?
Probably it wouldn't. (like SetUnitScale() doesn't make particles resizable) and ubersplats. (from thunderclap and war stomp)
I'm not sure recycling would be better than creating and destroying cause in recycling you need to perform resets on the properties of the effects which would remove any benefit of recycling over simple creation. Not to mention you need different stacks for effects with different models.
Yeah, I'm not entirely sure how it would benefit also.
As DummyRecyclers were not just built to avoid create and destroy calls but
also to prevent the leaking memory each unit gives by minimizing these calls.
So if effects are proven to be leaking just like units then this would be one of
the benefits to recycling them instead. But yeah, can't claim much of this yet.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Probably it wouldn't. (like SetUnitScale() doesn't make particles resizable) and ubersplats. (from thunderclap and war stomp)

Yeah, I'm not entirely sure how it would benefit also.
As DummyRecyclers were not just built to avoid create and destroy calls but
also to prevent the leaking memory each unit gives by minimizing these calls.
So if effects are proven to be leaking just like units then this would be one of
the benefits to recycling them instead. But yeah, can't claim much of this yet.
Special effects actually did leak in the past. It was very minor and just a few bytes per effect, but I do remember it happening in one of the older versions of wc3.

Not sure this is still a thing. Would be an easy thing to test, though. Just create a quick loop creating and destroying thousands of effects for a minute and check game memory in task manager.
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
You should add a setOrientation method that uses the the BlzSetEffectOrientationYaw, Pitch and Roll instead of just this single one . It can produce wrong orientation as i already found out testing your system and will be notifying you later.
You mean they should be in order? Like set Yaw -> Pitch -> Roll so it could work properly? I'm curious as I'm using that three-in-one function too.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
You should add a setOrientation method that uses the the BlzSetEffectOrientationYaw, Pitch and Roll instead of just this single one . It can produce wrong orientation as i already found out testing your system and will be notifying you later.
the current setOrientation is what I'm using in Missile and it's working right, so far, for me.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
the current setOrientation is what I'm using in Missile and it's working right, so far, for me.
I updated to the current missile Version and there is still something funky going on with missiles on curved terrain. They still follow the terrain orientation to some extend.

Its a lot better than before but still not perfect.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I updated to the current missile Version and there is still something funky going on with missiles on curved terrain. They still follow the terrain orientation to some extend.

Its a lot better than before but still not perfect.
I also found this out yesterday. Fortunately, it's due to a wrong pitch calculation on my part (taking only the local height of effects and not the absolute z) and not due to how the orientation behaves on curved grounds, so I was able to fix it easily. Will be updating that Missile lib soon, as well as this lib.
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Updated to v1.1.0
- Fixed a bug related to the iterator methods
- Added a new feature to toggle the visibility of a special effect locally for a player (see API documentation)


Alternatively, provide a showEffect(force for, boolean show) method that replaces all effect models with an empty model for the specified players).
Just setting the alpha to 0 locally might not be enough, as I think alpha doesnt hide particle emitters.
Changing the Position to the hidden spot locally might also do the trick. Not sure if it causes desync to do so. I couldn't imagine why it should, but blizzard is weird at times.
This is the method I used for setting local visibility. Only that it takes a player argument instead of force cause I thought it'd be more versatile.
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
Isn't altering the position of a model to a very huge extent would cause its ribbons or tails to follow noticeably? (which is visually disturbing)
Have you tried using this with models with that kind of properties? For example: phoenix fire, illidan missile, faerie missile, destroyer missile...
 
Last edited:
Level 20
Joined
Aug 13, 2013
Messages
1,696
It looks like creating special effects without units would mostly require asynchronous (Z) natives which is a con of this.
One of the reasons why dummies for special effects can't be obsolete. But yeah, only prone with terrain deformations...

Also special effects' height can go through negatives (they can penetrate the ground) unlike with units that consider it.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Dummy units also face the same asynchronicity in z values caused by terrain deformations. But it isn't a problem since the value is used for visuals only.

Regarding z values able to be negative, I don't see much problem with it, you can just limit its min height to the ground z like before. effect.setPosition(e, x, y, RMaxBJ(z, GetTerrainZ(x, y))).

Furthermore, having negative z values relative to the ground actually is needed for a proper missile trajectory in rare cases where a Missile with low altitude goes from a low terrain to an abruptly hight terrain. Using dummy method, the special effect would be required to be above ground at all cost even though its real trajectory collides with the ground, and this would cause a abrupt change in the missile's height, different from its supposed trajectory.
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
I mean, users are obliged to get the z of the ground just to create a special effect using this. It's a problem with elevated terrains, without getting their z it would spawn underneath. I'm also surprised that they can penetrate on it. It's just a useless check as we're not always working with 3D missiles. Although, I don't think we can avoid it unless there's a native which adjusts the special effect's height considering the ground just like with a unit that's not based on z. It's not always a manner to get the z with the dummy method most of the time. If you designed this to completely replace the dummy method it's also vital to replicate the height at least which I don't think it's possible without GetLocationZ().
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
JASS:
          */method addModel         takes string model                          returns effect/*
                - You can't add an already attached model
Is there a way you can also remove this kind of limitation??? (or kinda make an implementation)
not technically a problem but prevents some visual potential;

Let's say: creation of a fireball with mini fireballs around it <- models here aren't unique but same.
With this kind of limitation, I'm forced to create a separated instances for each instead of just one.

If there's no way it's fine as it looks like you're basing the indexes with strings rather than effects.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I'm considering it but currently I can't think of a convenient way to allow this without reworking the implementation of the getHandle() method and maybe the internal data structure.

If there's no way it's fine as it looks like you're basing the indexes with strings rather than effects.
Maybe I can make getHandle(model) to return the first instance of effect with the model but then another feature that is expected to be provided would be to allow users to traverse the remaining effects matching the same model. I'll try to see if it's something I should add while considering the added overhead and complexity to the code.
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
I'm also looking forward if you're able to implement a kind of feature which holds
the first(main) effect as a real object(unit) with attachment points defined by xyz.

So If I'll set the main effect's orientation the coordinates of the sub effects from
that instance would also change its xyz (ignoring the orientation for its own axis)

Let's say a plane model (main effect) with its two wings that I'll consider them as
two attachment points by just adding two sub effects with offset, I'm about to roll
the plane so one should expect those sub effects to move its xyz coordinates to follow
these attachment points accordingly and based from the main effect's orientation.
(same case for yaw and pitch, although note that it's just an extra functionality)

I can provide an illustration of this via paint program just in case.
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
I think I can already picture out what you mean. But I think, it would be better off as another resource that utilizes this library, because for sure it would need more specialized operations and API. I think such a resource would be nice to have.
Why not just merge these extra functionalities here in this library and just put a few methods containing the magical maths for it instead of separating the related functions for another resource? except it would identify the main effect and its sub effect from the instance, setting orientation of the main effect and just setting position to all of its sub effects to their respective positions to match the facing of an orientation?

But yeah, I guess it's up to you as it would probably require you to rework the implementation just to achieve it properly.
I'll definitely wait for it, imagine the visual possibilities taking the advantage of this coolest feature. (I can think of many) ;)
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
One of the reason for separating it will be because of the added overhead. setOrientation() for example would involve additional checks and calculations. Plus, I can think of a lot more features to add to such a system that I don't want to bloat it here too much.

Btw, in the design I'm thinking it's allowed to have nested satellite attachments.
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
One of the reason for separating it will be because of the added overhead. setOrientation() for example would involve additional checks and calculations.
I've suggested that it would be an extra functionality like: setOrientationRelative() or some other way that wouldn't even bother with how setOrientation() is operated right now... but I think it will once you redo the implementation as it works currently for all handles in the list.
Plus, I can think of a lot more features to add to such a system that I don't want to bloat it here too much.
Sounds fair. Alright then.

in the design I'm thinking it's allowed to have nested satellite attachments.
You mean hierarchy? main effect (parent) <- sub effect (child of main effect but parent of) <- mini-sub effect (child of sub effect and parent of)...?

By the way, you might want to consider deprecating the use of this native: call BlzSetSpecialEffectOrientation(effect, yaw, pitch, roll)
As it's reported by chopinski that it causes troublesome. (even though not guarantee, I believe that he wouldn't complain this at all for no reason)

Aside from getting rid of the limitation for attaching models, I believe these are all I can expect for the next update. Take your time.
 
Top