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

[Solved] Invisibility (Pyke W) [vJass]

Status
Not open for further replies.
Level 7
Joined
Feb 9, 2021
Messages
301
I am trying to make a spell similar to Pyke's invisibility from LoL: Hero cast a spell and becomes invisible; if there is an enemy hero in the radius x or the caster got damaged, it becomes visible for y second; if the caster casted a spell or attacked, the spell finishes; if the timer is finished, the spell finishes. My question is how to efficiently select all enemy heroes who are not already in the group around the caster? I tried to do it with "GroupEnumUnitsInRange", but here I can't put conditions that units are enemies and units are not in the group.
 
Level 7
Joined
Feb 9, 2021
Messages
301
You can use IsUnitEnemy(unit, player) and IsUnitInGroup(group, unit) inside group enumeration.
I understand that. However, I thought there might be a more efficient way to exclude them before selecting the group or in the group selection. In this way, the trigger will loop through fewer units. Otherwise, I will have to select all heroes first and then loop through all heroes do actions based on these conditions.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
There're no natives for readily enumerating enemies or heroes only, nor for other very specific cases of enumeration. You can put a filterfunc as the last argument of the GroupEnum... if that's what you want based on your question above:
JASS:
GroupEnumUnitsInRange(group, unit, x, y, radius, Condition(function EnemyAndHeroFilter))
But it wouldn't be any more efficient nor convenient compared to just doing the filtering in the loop, especially if the condition is just a simple check.
 
Level 7
Joined
Feb 9, 2021
Messages
301
There're no natives for readily enumerating enemies or heroes only, nor for other very specific cases of enumeration. You can put a filterfunc as the last argument of the GroupEnum... if that's what you want based on your question above:
JASS:
GroupEnumUnitsInRange(group, unit, x, y, radius, Condition(function EnemyAndHeroFilter))
But it wouldn't be any more efficient nor convenient compared to just doing the filtering in the loop, especially if the condition is just a simple check.
So, here is my attempt to make it. The selection of the group and events that cancel the invis(attack or spell cast) do not work for some reason. I also not sure how to better start timeout count if the unit got the damage. (I use DDS)

JASS:
scope Invisibility initializer init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
    globals
        private constant integer SPELL_ID = 'A00U' //the rawcode of the spell
        private constant integer BUFF_ID = 'B00D' //the rawcode of the buff
        private constant integer INVIS_ID = 'A00S'//the rawcode of the invis
        private constant integer DUMMY_ID = 'h001'//the rawcode of the dummy
        private constant integer DUMMY_SPELL_ID = 'A00T'//the rawcode of ability added to dummy to apply buff
        
    endglobals
    
    
    private function Range takes nothing returns real
    //returns the range the spell will affect
        return 500.
    endfunction
    
    private function Duration takes integer level returns real
    //returns the range the spell will affect
        return level * 2.
    endfunction
    
    private function BackInvis takes nothing returns real
    //returns the range the spell will affect
        return 1.
    endfunction


    private function Targets takes unit target returns boolean
        return GetWidgetLife(target) > 0.405 and IsUnitType(target, UNIT_TYPE_HERO)
    endfunction

//===========================================================================
//=============================SETUP END=====================================
//=========================================================================== 
    globals
        private integer dindex = -1
        private constant real FPS = 0.031250000
        private timer period
        private group eHeroes
        private group eHeroesInRange
        private boolexpr b
    endglobals
//=========================================================================== 
    private struct tempDat
        unit caster
        unit target
        unit dummy
        player p
        real range
        real backInvis
        real BIcounter
        real counter
        real cX
        real cY
        integer level
        integer hCounter
      
        real duration
        boolean seen
        boolean timeout
        
        method destroy takes nothing returns nothing
            //Wash leaks
            set this.caster = null
            set this.target = null
            set this.dummy = null
            if dindex == -1 then
                call PauseTimer(period)
            endif
            call this.deallocate()
        endmethod
        
        
    endstruct
//===========================================================================   
    globals
        private tempDat array data
    endglobals
//=========================================================================== 
    
    private function Pick takes nothing returns boolean
        return Targets(GetFilterUnit())
    endfunction
    
//=========================================================================== 
    private function periodic takes nothing returns nothing
        local integer i = 0
        local tempDat this
        loop
            exitwhen i > dindex
            local integer TempInt
            set this = data[i]
            
                //if counter finished remove the buff
            if this.counter > this.duration then
                call BJDebugMsg("counter finished")
                call UnitRemoveBuffBJ(BUFF_ID, this.caster)
            endif
                // if buffed is remove, delete invis (use this condition vs counter because of other events that remove the buff 
            if not UnitHasBuffBJ(this.caster, BUFF_ID) then
                call BJDebugMsg("end")
                if GetUnitAbilityLevel(this.caster , INVIS_ID) == 1 then
                    call UnitRemoveAbility(this.caster, INVIS_ID)
                endif
                call DisableTrigger( gg_trg_InvisSpell )
                call DisableTrigger( gg_trg_InvisAttack )
                //call DisableTrigger( gg_trg_InvisAttacked )
                set data[i] = data[dindex]
                set i = i - 1
                set dindex = dindex - 1
                call this.destroy()
            else
                set this.counter = this.counter + FPS
            endif
            
            // select all enemy heroes and add them the group in order to know if there is an enemy in range
            call GroupEnumUnitsInRange(eHeroes, this.cX, this.cY, Range, b)
            loop
                set this.target = FirstOfGroup(eHeroes)
                exitwhen this.target == null
                if IsUnitEnemy(this.target, this.p) and not IsUnitInGroup(this.target, eHeroesInRange) then
                    set this.hCounter = this.hCounter + 1
                    call GroupAddUnit(eHeroesInRange, this.target)
                endif
                call GroupRemoveUnit(eHeroes, this.target)
            endloop
            
            // if a hero in range goes out of the range, take him out of the group
            loop
                set this.target = FirstOfGroup(eHeroesInRange)
                exitwhen TempInt == this.hCounter
                if GetDistanceBetweenCoordinates(GetUnitX(this.caster), GetUnitX(this.target), GetUnitY(this.caster), GetUnitY(this.target)) > Range then
                    set this.hCounter = this.hCounter - 1
                    // timeout responsbile for the timer after which the unit becomes invis again
                    if this.hCounter == 0 then
                        set this.timeout = true
                    endif
                    
                endif
                set TempInt = TempInt + 1
            endloop
            
            // when heroes in range, remove invis and reset timeout
            if this.hCounter > 0 then
                //remove the ability
                if not this.seen then
                    set this.seen = true
                    call BJDebugMsg("seen")
                    call UnitRemoveAbility(this.caster, INVIS_ID)
                endif
                
                //reset timeout
                if this.timeout then 
                    set this.timeout = false
                    set this.BIcounter = 0
                endif
            endif
         
            
            if this.timeout then 
                set this.BIcounter = this.BIcounter + FPS
                if this.BIcounter >= BackInvis then
                    call BJDebugMsg("timeout finish")
                    set this.seen = false
                    call UnitAddAbility(this.caster, INVIS_ID)
                    set this.timeout = false
                endif
            endif
            
            
            set i = i + 1
        endloop
    endfunction
        
//===========================================================================   
    private function Conditions takes nothing returns boolean
        local tempDat this
        if GetSpellAbilityId() == SPELL_ID then
            set this = tempDat.create()
            set this.caster = GetTriggerUnit()
            set this.cX = GetUnitX(this.caster)
            set this.cY = GetUnitY(this.caster)
            set this.p = GetOwningPlayer(this.caster)
            set this.seen = false
            set this.timeout = false
           
            set this.level = GetUnitAbilityLevel(this.caster, SPELL_ID)
           
            set this.duration = Duration(this.level)
        
            set this.counter = 0
            call BJDebugMsg("Invis")
            // add buff
            set this.dummy = CreateUnit(this.p, DUMMY_ID, this.cX, this.cY, 270)
            call UnitAddAbility(this.dummy, DUMMY_SPELL_ID)
            call IssueTargetOrder(this.dummy, "acidbomb", this.caster)
            //add inivis
            call UnitAddAbility(this.caster, INVIS_ID)
            //
            set dindex = dindex + 1
            set data[dindex] = this
            if dindex == 0 then
                call EnableTrigger( gg_trg_InvisSpell )
                call EnableTrigger( gg_trg_InvisAttack )
                //call EnableTrigger( gg_trg_InvisAttacked )
                call TimerStart(period, FPS, true, function periodic)
            endif
            
        endif
        return false
    endfunction


    
//===========================================================================
    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t , EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition(t , Condition( function Conditions ) )
        
        //setting globals
        set eHeroes = CreateGroup()
        set eHeroesInRange = CreateGroup()
        set period = CreateTimer()
        set b = Condition(function Pick)
        set t = null
        
        //preloading effects
    endfunction
endscope

JASS:
struct InvisSpell extends array
    private static constant integer BUFF_ID = 'B00D'


    private static method run takes nothing returns nothing
        local unit caster = GetTriggerUnit()
        call UnitRemoveBuffBJ(BUFF_ID, caster)
        call BJDebugMsg("casted abil")
        
    endmethod

    private static method check takes nothing returns boolean
        if UnitHasBuffBJ(GetTriggerUnit(), BUFF_ID) then
            call thistype.run()
        endif
        return false
    endmethod

    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call DisableTrigger(t)
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function thistype.check))
        set t = null
    endmethod
endstruct

JASS:
struct InvisAttack extends array
    private static constant integer BUFF_ID = 'B00D'


    private static method run takes nothing returns nothing
        local unit caster = GetTriggerUnit()
        call UnitRemoveBuffBJ(BUFF_ID, caster)
        call BJDebugMsg("casted abil")
        
    endmethod

    private static method check takes nothing returns boolean
        if UnitHasBuffBJ(GetTriggerUnit(), BUFF_ID) /*and udg_DamageEventSource == UnitHasBuffBJ(GetTriggerUnit(), BUFF_ID)*/ then
            call thistype.run()
        endif
        return false
    endmethod

    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call DisableTrigger(t)
        call TriggerRegisterVariableEvent( t, "udg_DamageEvent", EQUAL, 1.00 )
        call TriggerAddCondition(t, Condition(function thistype.check))
        set t = null
    endmethod
endstruct
 
Level 7
Joined
Feb 9, 2021
Messages
301
Does the "casted abil" messages display?
Also, the eHeroesInRange group should be instance member since the hero units persist in that group until the duration of the invi spell is over.
It doesn't display.

I also made the group instance member, but the group selection still doesn't work. I attached the map for you.
 
Last edited:
Level 7
Joined
Feb 9, 2021
Messages
301
Does the "casted abil" messages display?
Also, the eHeroesInRange group should be instance member since the hero units persist in that group until the duration of the invi spell is over.
So, invis works now. However, I can't make work other things such as "the caster got damaged, it becomes visible for y second; if the caster casted a spell or attacked, the spell finishes"

Update: The only things I can't do is "the caster got damaged, it becomes visible for y second". I updated the new map. I also would really appreciate if you can check my code and give me some feedback. May be you could also recommend some systems to use to improve efficiency.
 
Last edited:
Level 7
Joined
Feb 9, 2021
Messages
301
I currently don't have access to WE. I'll see what I can do, or maybe you could post the latest codes?
JASS:
scope Invisibility initializer init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
    globals
        private constant integer SPELL_ID = 'A00U' //the rawcode of the spell
        private constant integer BUFF_ID = 'B00D' //the rawcode of the buff
        private constant integer INVIS_ID = 'A00S'//the rawcode of the invis
        private constant integer DUMMY_ID = 'h001'//the rawcode of the dummy
        private constant integer DUMMY_SPELL_ID = 'A00T'//the rawcode of ability added to dummy to apply buff
     
    endglobals
 
 
    private function Range takes nothing returns real
    //returns the range the spell will affect
        return 500.
    endfunction
 
    private function Duration takes integer level returns real
    //returns the range the spell will affect
        return level * 8.
    endfunction
 
    private function BackInvis takes nothing returns real
    //returns the range the spell will affect
        return 3.
    endfunction


    private function Targets takes unit target returns boolean
        return GetWidgetLife(target) > 0.405 and IsUnitType(target, UNIT_TYPE_HERO)
    endfunction

//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
    globals
        private integer dindex = -1
        private constant real FPS = 0.031250000
        private timer period
        private group eHeroes
        private boolexpr b
    endglobals
//===========================================================================
    private struct tempDat
        unit caster
        unit target
        unit dummy
        player p
        real range
        real backInvis
        real BIcounter
        real counter
        real cX
        real cY
        integer level
        integer hCounter
     
        real duration
        boolean seen
        boolean timeout
     
        group eHInRange
     
        method destroy takes nothing returns nothing
            //Wash leaks
            set this.caster = null
            set this.target = null
            set this.dummy = null
            if dindex == -1 then
                call PauseTimer(period)
            endif
            call this.deallocate()
        endmethod
     
     
    endstruct
//===========================================================================
    globals
        private tempDat array data
    endglobals
//===========================================================================
 
    private function Pick takes nothing returns boolean
        return Targets(GetFilterUnit())
    endfunction
 
//===========================================================================
    private function periodic takes nothing returns nothing
        local integer i = 0
        local tempDat this
        loop
            exitwhen i > dindex
            local integer TempInt = 0
            set this = data[i]
            set this.cX = GetUnitX(this.caster)
            set this.cY = GetUnitY(this.caster)
                //if counter finished remove the buff
            if this.counter > this.duration then
                call BJDebugMsg("counter finished")
                call UnitRemoveBuffBJ(BUFF_ID, this.caster)
            endif
                // if buffed is remove, delete invis (use this condition vs counter because of other events that remove the buff
            if not UnitHasBuffBJ(this.caster, BUFF_ID) then
                call BJDebugMsg("end")
                call SetUnitVertexColor(this.caster, 255, 255, 255, 255)
                if GetUnitAbilityLevel(this.caster , INVIS_ID) == 1 then
                    call UnitRemoveAbility(this.caster, INVIS_ID)
                endif
             
                call GroupClear(this.eHInRange)
                call DisableTrigger( gg_trg_InvisSpell )
                call DisableTrigger( gg_trg_InvisAttack )
                //call DisableTrigger( gg_trg_InvisAttacked )
                set data[i] = data[dindex]
                set i = i - 1
                set dindex = dindex - 1
                call this.destroy()
            else
                set this.counter = this.counter + FPS
            endif
         
            // select all enemy heroes and add them the group in order to know if there is an enemy in range
            call GroupEnumUnitsInRange(eHeroes, this.cX, this.cY, Range(), b)
            loop
                set this.target = FirstOfGroup(eHeroes)
                exitwhen this.target == null
                if IsUnitEnemy(this.target, this.p) and not IsUnitInGroup(this.target, this.eHInRange) then
                    call BJDebugMsg("add enemy")
                    set this.hCounter = this.hCounter + 1
                    call GroupAddUnit(this.eHInRange, this.target)
           
                endif
                call GroupRemoveUnit(eHeroes, this.target)
             
            endloop
         
            // if a hero in range goes out of the range, take him out of the group
            loop
                set this.target = FirstOfGroup(this.eHInRange)
                exitwhen TempInt >= this.hCounter
                if GetDistanceBetweenCoordinates(this.cX, GetUnitX(this.target), this.cY, GetUnitY(this.target)) > Range() then
                    set this.hCounter = this.hCounter - 1
                    call BJDebugMsg("-1 in range")
                    // timeout responsbile for the timer after which the unit becomes invis again
                    if this.hCounter == 0 then
                        set this.timeout = true
                    endif
                call GroupRemoveUnit(this.eHInRange, this.target)
                endif
                set TempInt = TempInt + 1
            endloop
         
            // when heroes in range, remove invis and reset timeout
            if this.hCounter > 0 then
         
                //remove the ability
                if not this.seen then
                    set this.seen = true
                    call BJDebugMsg("seen")
                    call UnitRemoveAbility(this.caster, INVIS_ID)
                    //call SetUnitVertexColor(this.caster, 100, 100, 100, 80)
                endif
             
                //reset timeout
                if this.timeout then
                    set this.timeout = false
                    set this.BIcounter = 0
                endif
            endif
      
         
            if this.timeout then
                set this.BIcounter = this.BIcounter + FPS
                if this.BIcounter >= BackInvis() then
                    call BJDebugMsg("timeout finish")
                    set this.seen = false
                    //call SetUnitVertexColor(this.caster, 100, 100, 100, 110)
                    call UnitAddAbility(this.caster, INVIS_ID)
                    set this.timeout = false
                endif
            endif
         
         
            set i = i + 1
        endloop
    endfunction
     
//===========================================================================
    private function Conditions takes nothing returns boolean
        local tempDat this
        if GetSpellAbilityId() == SPELL_ID then
            set this = tempDat.create()
            set this.caster = GetTriggerUnit()
            set this.cX = GetUnitX(this.caster)
            set this.cY = GetUnitY(this.caster)
            set this.p = GetOwningPlayer(this.caster)
            set this.seen = false
            set this.timeout = false
            set this.eHInRange = CreateGroup()
            set this.level = GetUnitAbilityLevel(this.caster, SPELL_ID)
        
            set this.duration = Duration(this.level)
     
            set this.counter = 0
            call BJDebugMsg("Invis")
            // add buff
            set this.dummy = CreateUnit(this.p, DUMMY_ID, this.cX, this.cY, 270)
            call UnitAddAbility(this.dummy, DUMMY_SPELL_ID)
            call IssueTargetOrder(this.dummy, "acidbomb", this.caster)
            //add effects
         
         
            //add inivis
            call UnitAddAbility(this.caster, INVIS_ID)
         
            //
            set dindex = dindex + 1
            set data[dindex] = this
            if dindex == 0 then
                call EnableTrigger( gg_trg_InvisSpell )
                call EnableTrigger( gg_trg_InvisAttack )
                //call EnableTrigger( gg_trg_InvisAttacked )
                call TimerStart(period, FPS, true, function periodic)
            endif
         
        endif
        return false
    endfunction


 
//===========================================================================
    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t , EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition(t , Condition( function Conditions ) )
     
        //setting globals
        set eHeroes = CreateGroup()
        set period = CreateTimer()
        set b = Condition(function Pick)
        set t = null
     
        //preloading effects
    endfunction
endscope

JASS:
struct InvisSpell extends array
    private static constant integer BUFF_ID = 'B00D'


    private static method run takes nothing returns nothing
        local unit caster = GetTriggerUnit()
        call UnitRemoveBuffBJ(BUFF_ID, caster)
        call BJDebugMsg("casted abil")
     
    endmethod

    private static method check takes nothing returns boolean
        if UnitHasBuffBJ(GetTriggerUnit(), BUFF_ID) then
            call thistype.run()
        endif
        return false
    endmethod

    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        //call DisableTrigger(t)
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function thistype.check))
        set t = null
    endmethod
endstruct

JASS:
struct InvisAttack extends array
    private static constant integer BUFF_ID = 'B00D'


    private static method run takes nothing returns nothing
        call BJDebugMsg("attack")
        call UnitRemoveBuffBJ(BUFF_ID, udg_DamageEventSource)

    endmethod

    private static method check takes nothing returns boolean
        if UnitHasBuffBJ(udg_DamageEventSource, BUFF_ID) /*and not udg_IsDamageSpell*/ then
            call thistype.run()
        endif
        return false
    endmethod

    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        //call DisableTrigger(t)
        call TriggerRegisterVariableEvent( t, "udg_DamageEvent", EQUAL, 1.00 )
        call TriggerAddCondition(t, Condition(function thistype.check))
        set t = null
    endmethod
endstruct

Everything works, except for "the caster got damaged, it becomes visible for y second". So, there should иу another trigger with an event activated when the caster got attacked and it should do set this.timeout = true. However, I do not know how to connect these two triggers.

Also, are there ways I can improve my code?
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I think here
JASS:
    private static method check takes nothing returns boolean
        if UnitHasBuffBJ(udg_DamageEventSource, BUFF_ID) /*and not udg_IsDamageSpell*/ then
            call thistype.run()
        endif
        return false
    endmethod
it should use udg_DamageEventTarget instead of damage source for buff checking.

So, there should иу another trigger with an event activated when the caster got attacked and it should do set this.timeout = true
For this I suggest putting all these structs into one scope so that they have shared access to their private data, since they are very related to begin with. You can either use a hashtable to attach spell data to the unit which you will then access from the damage event function. Or better yet, if you are already using a unit indexer you can directly use the index of the unit instead of doing tempDat.create(), since invisibility instance should be limited to 1 per unit anyway.

Btw, did you get the SpellFramework working? If you did I can translate your spell to use that library so you can have a reference. It will greatly simplify your future spellmaking process. You no longer would need to handle dynamic indexing, the periodic function, spell event registration, as well as spell instance allocation yourself.
 
Level 7
Joined
Feb 9, 2021
Messages
301
I think here
JASS:
    private static method check takes nothing returns boolean
        if UnitHasBuffBJ(udg_DamageEventSource, BUFF_ID) /*and not udg_IsDamageSpell*/ then
            call thistype.run()
        endif
        return false
    endmethod
it should use udg_DamageEventTarget instead of damage source for buff checking.


For this I suggest putting all these structs into one scope so that they have shared access to their private data, since they are very related to begin with. You can either use a hashtable to attach spell data to the unit which you will then access from the damage event function. Or better yet, if you are already using a unit indexer you can directly use the index of the unit instead of doing tempDat.create(), since invisibility instance should be limited to 1 per unit anyway.

Btw, did you get the SpellFramework working? If you did I can translate your spell to use that library so you can have a reference. It will greatly simplify your future spellmaking process. You no longer would need to handle dynamic indexing, the periodic function, spell event registration, as well as spell instance allocation yourself.
It uses DameEvenSource because the job of this trigger is to remove Invis Buff from the caster if he attacked anyone.

Thank you for your recommendations, I will try to do it.

It would be great if you could do this with your system. However, I am still solving novjass problem (I texted a guy who might know how to fix it without deleting novjass, as it is very troublesome). Let me get back to you on this one.

Some questions/ideas on your system:
1. On timers, does it use something similar to CTL?
2. Your system has "Two-phase spell event handlers". Does it mean it can simplify, for example, in this spell two events for one spell (unit casted spell -> activate invis, unit attacked while in invis -> delete the buff)?
3. it would be great if you added buff, disable, dummy, dds, dynamic indexing, linked list and recycler systems to your system and organised them together. This would be all in one package for creating spells.
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
It uses DameEvenSource because the job of this trigger is to remove Invis Buff from the caster if he attacked anyone.
Oh, I see. I think you can also merge the "the caster got damaged, it becomes visible for y second" part here too by making the condition like:
JASS:
    private static method run takes unit u returns nothing
        call BJDebugMsg("attack")
        call UnitRemoveBuffBJ(BUFF_ID, u)
    endmethod

    private static method check takes nothing returns boolean
        if UnitHasBuffBJ(udg_DamageEventSource, BUFF_ID) /*and not udg_IsDamageSpell*/ then
            call thistype.run(udg_DamageEventSource)
        end
        if UnitHasBuffBJ(udg_DamageEventTarget, BUFF_ID) then
            call thistype.run(udg_DamageEventTarget)
        endif
        return false
    endmethod

It would be great if you could do this with your system. However, I am still solving novjass problem (I texted a guy who might know how to fix it without deleting novjass, as it is very troublesome). Let me get back to you on this one.
Try replacing the novjass parts with a multiline vjass comment and see if it compiles
JASS:
//! novjass
<documentation here>
//! endnovjass
-->
JASS:
/*
<documentation here>
*/

1. On timers, does it use something similar to CTL?
Not exactly similar but a bit similar on purpose, however in SpellFramework you don't need to do anything like implementing modules or starting & stopping the timer yourself. It's all handled internally, you just need to define the periodic timeout. This is true for the main periodic function of the spell at least (If you have other or additional periodic functions, then you have to setup a timer for them yourself).

2. Your system has "Two-phase spell event handlers". Does it mean it can simplify, for example, in this spell two events for one spell (unit casted spell -> activate invis, unit attacked while in invis -> delete the buff)?
No the two-phase event means something different. The first phase are the global handlers - handlers functions that activate for all custom spells. This is useful for example if you want to modify existing custom spells like make them multicast, redirect them from their original targets, etc. The second phase are the specific spell handlers - handlers that only activate for a specific spell id. This is where you would put the usual code for your custom spells.

3. it would be great if you added buff, disable, dummy, dds, dynamic indexing, linked list and recycler systems to your system and organised them together. This would be all in one package for creating spells.
It is usually preferable to not bundle too many different systems into one submission, it would be harder to review and test. It would also be burden to those users who only want the core functionalities.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Oh, I see. I think you can also merge the "the caster got damaged, it becomes visible for y second" part here too by making the condition like:
JASS:
    private static method run takes unit u returns nothing
        call BJDebugMsg("attack")
        call UnitRemoveBuffBJ(BUFF_ID, u)
    endmethod

    private static method check takes nothing returns boolean
        if UnitHasBuffBJ(udg_DamageEventSource, BUFF_ID) /*and not udg_IsDamageSpell*/ then
            call thistype.run(udg_DamageEventSource)
        end
        if UnitHasBuffBJ(udg_DamageEventTarget, BUFF_ID) then
            call thistype.run(udg_DamageEventTarget)
        endif
        return false
    endmethod


Try replacing the novjass parts with a multiline vjass comment and see if it compiles
JASS:
//! novjass
<documentation here>
//! endnovjass
-->
JASS:
/*
<documentation here>
*/


Not exactly similar but a bit similar on purpose, however in SpellFramework you don't need to do anything like implementing modules or starting & stopping the timer yourself. It's all handled internally, you just need to define the periodic timeout. This is true for the main periodic function of the spell at least (If you have other or additional periodic functions, then you have to setup a timer for them yourself).


No the two-phase event means something different. The first phase are the global handlers - handlers functions that activate for all custom spells. This is useful for example if you want to modify existing custom spells like make them multicast, redirect them from their original targets, etc. The second phase are the specific spell handlers - handlers that only activate for a specific spell id. This is where you would put the usual code for your custom spells.


It is usually preferable to not bundle too many different systems into one submission, it would be harder to review and test. It would also be burden to those users who only want the core functionalities.
Hmm, I have another error (i tried Jaina spellpack made with your system) :
1617197179757.png
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
The error's weird cause module declarations are not allowed inside structs in vjass. And looking at the Jaina spellpack, I don't see any module being declared inside a struct. Maybe some it's another bug on adichelper since cjass is known long ago for having some bugs and the latest update to cjass was many years ago. Btw, are you really using cjass code in your map? If not I suggest disabling adichelper and enable only the jasshelper.
 
Level 7
Joined
Feb 9, 2021
Messages
301
The error's weird cause module declarations are not allowed inside structs in vjass. And looking at the Jaina spellpack, I don't see any module being declared inside a struct. Maybe some it's another bug on adichelper since cjass is known long ago for having some bugs and the latest update to cjass was many years ago. Btw, are you really using cjass code in your map? If not I suggest disabling adichelper and enable only the jasshelper.
Okay, I figured out the problem. Sorry for all the trouble. I had to download another NewGen Pack... Can you show me how to do this spell on your system/ please? Ideally, I would appreciate it if you can use Unit Indexer, so I can see how you use it to transfer the caster to another trigger. What UnitIndexer would you recommend? Here is my latest version of the spell and the description of it (Camouflage): Stealth :
Note: By the way, after reading the description again, I realised that I need to change some things, so better rely on this description.

Edit: Also, why you didn't implement CTL in your system? At least from what I read, it improves performance.
JASS:
scope Invisibility initializer init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
    globals
        private constant integer SPELL_ID = 'A00U' //the rawcode of the spell
        private constant integer BUFF_ID = 'B00D' //the rawcode of the buff
        private constant integer INVIS_ID = 'A00S'//the rawcode of the invis
        private constant integer DUMMY_ID = 'h001'//the rawcode of the dummy
        private constant integer DUMMY_SPELL_ID = 'A00T'//the rawcode of ability added to dummy to apply buff
     
        private constant integer TRANSPERENCY = 110
    endglobals
 
 
    private function Range takes nothing returns real
    //returns the range the spell will affect
        return 500.
    endfunction
 
    private function Duration takes integer level returns real
    //returns the range the spell will affect
        return level * 10.
    endfunction
 
    private function BackInvis takes nothing returns real
    //returns the range the spell will affect
        return 1.
    endfunction


    private function Targets takes unit target returns boolean
        return GetWidgetLife(target) > 0.405 and IsUnitType(target, UNIT_TYPE_HERO)
    endfunction

//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
    globals
        private integer dindex = -1
        private constant real FPS = 0.031250000
        private timer period
        private group eHeroes
        private boolexpr b
    endglobals
//===========================================================================
    private struct tempDat
        unit caster
        unit target
        unit dummy
        player p
        real range
        real backInvis
        real BIcounter
        real counter
        real cX
        real cY
        integer level
        integer hCounter
     
        real duration
        boolean seen
        boolean timeout
     
        group eHInRange
     
        method destroy takes nothing returns nothing
            //Wash leaks
            set this.caster = null
            set this.target = null
            set this.dummy = null
            if dindex == -1 then
                call PauseTimer(period)
            endif
            call this.deallocate()
        endmethod
     
     
    endstruct
//===========================================================================
    globals
        private tempDat array data
    endglobals
//===========================================================================
 
    private function Pick takes nothing returns boolean
        return Targets(GetFilterUnit())
    endfunction
 
//===========================================================================
    private function periodic takes nothing returns nothing
        local integer i = 0
        local tempDat this
        loop
            exitwhen i > dindex
            local integer TempInt = 0
            set this = data[i]
            set this.cX = GetUnitX(this.caster)
            set this.cY = GetUnitY(this.caster)
                //if counter finished remove the buff
            if this.counter > this.duration then
                call BJDebugMsg("counter finished")
                call UnitRemoveBuffBJ(BUFF_ID, this.caster)
            endif
          
            // select all enemy heroes and add them the group in order to know if there is an enemy in range
            call GroupEnumUnitsInRange(eHeroes, this.cX, this.cY, Range(), b)
            loop
                set this.target = FirstOfGroup(eHeroes)
                exitwhen this.target == null
                if IsUnitEnemy(this.target, this.p) and not IsUnitInGroup(this.target, this.eHInRange) then
                    call BJDebugMsg("add enemy")
                    set this.hCounter = this.hCounter + 1
                    call GroupAddUnit(this.eHInRange, this.target)
           
                endif
                call GroupRemoveUnit(eHeroes, this.target)
             
            endloop
         
            // if a hero in range goes out of the range, take him out of the group
            loop
                set this.target = FirstOfGroup(this.eHInRange)
                exitwhen TempInt >= this.hCounter
                if GetDistanceBetweenCoordinates(this.cX, GetUnitX(this.target), this.cY, GetUnitY(this.target)) > Range() then
                    set this.hCounter = this.hCounter - 1
                    call BJDebugMsg("-1 in range")
                    // timeout responsbile for the timer after which the unit becomes invis again
                    if this.hCounter == 0 then
                        set this.timeout = true
                    endif
                call GroupRemoveUnit(this.eHInRange, this.target)
                endif
                set TempInt = TempInt + 1
            endloop
         
            // when heroes in range, remove invis and reset timeout
            if this.hCounter > 0 then
         
                //remove the ability
                if not this.seen then
                    set this.seen = true
                    call BJDebugMsg("seen")
                    call UnitRemoveAbility(this.caster, INVIS_ID)
                    call SetUnitVertexColor(this.caster, 255, 255, 255, TRANSPERENCY)
                endif
             
                //reset timeout
                set this.BIcounter = 0
             
                if this.timeout then
                    set this.timeout = false
                endif
            endif
      
         
            if this.timeout then
                set this.BIcounter = this.BIcounter + FPS
                call BJDebugMsg(R2S(this.BIcounter))
                if this.BIcounter >= BackInvis() then
                    call BJDebugMsg("timeout finish")
                    set this.seen = false
                    call SetUnitVertexColor(this.caster, 255, 255, 255, 255)
                    call UnitAddAbility(this.caster, INVIS_ID)
                    set this.timeout = false
                endif
            endif
          
          
          
             // if buffed is remove, delete invis (use this condition vs counter because of other events that remove the buff
            if not UnitHasBuffBJ(this.caster, BUFF_ID) then
                call BJDebugMsg("end")
                call SetUnitVertexColor(this.caster, 255, 255, 255, 255)
                if GetUnitAbilityLevel(this.caster , INVIS_ID) == 1 then
                    call UnitRemoveAbility(this.caster, INVIS_ID)
                endif
             
                call GroupClear(this.eHInRange)
                set data[i] = data[dindex]
                set i = i - 1
                set dindex = dindex - 1
                call this.destroy()
            else
                set this.counter = this.counter + FPS
            endif
         
         
         
            set i = i + 1
        endloop
    endfunction
     
//===========================================================================
    private function Conditions takes nothing returns boolean
        local tempDat this
        if GetSpellAbilityId() == SPELL_ID then
            set this = tempDat.create()
            set this.caster = GetTriggerUnit()
            set this.cX = GetUnitX(this.caster)
            set this.cY = GetUnitY(this.caster)
            set this.p = GetOwningPlayer(this.caster)
            set this.seen = false
            set this.timeout = false
            set this.eHInRange = CreateGroup()
            set this.level = GetUnitAbilityLevel(this.caster, SPELL_ID)
        
            set this.duration = Duration(this.level)
     
            set this.counter = 0
            call BJDebugMsg("Invis")
            // add buff
            set this.dummy = CreateUnit(this.p, DUMMY_ID, this.cX, this.cY, 270)
            call UnitAddAbility(this.dummy, DUMMY_SPELL_ID)
            call IssueTargetOrder(this.dummy, "acidbomb", this.caster)
            //add effects
         
         
            //add inivis
            call UnitAddAbility(this.caster, INVIS_ID)
         
            //
            set dindex = dindex + 1
            set data[dindex] = this
            if dindex == 0 then
                call TimerStart(period, FPS, true, function periodic)
            endif
         
        endif
        return false
    endfunction



//===========================================================================
    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t , EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition(t , Condition( function Conditions ) )
     
        //setting globals
        set eHeroes = CreateGroup()
        set period = CreateTimer()
        set b = Condition(function Pick)
        set t = null
     
        //preloading effects
    endfunction
 

    //remove the buff if the unit casted the spell
     
    struct InvisSpell extends array
        private static constant integer BUFF_ID = 'B00D'


        private static method run takes nothing returns nothing
            local unit caster = GetTriggerUnit()
            call UnitRemoveBuffBJ(BUFF_ID, caster)
            call BJDebugMsg("casted abil")
         
        endmethod

        private static method check takes nothing returns boolean
            if UnitHasBuffBJ(GetTriggerUnit(), BUFF_ID) then
                call thistype.run()
            endif
            return false
        endmethod

        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.check))
            set t = null
        endmethod
    endstruct

    //remove the buff if the unit attacked and turn on timeout if the unit got damage

    struct InvisAttack extends array
        private static constant integer BUFF_ID = 'B00D'


        private static method run takes nothing returns nothing
            call BJDebugMsg("attack")
            call UnitRemoveBuffBJ(BUFF_ID, udg_DamageEventSource)
   
        endmethod

        private static method check takes nothing returns boolean
            if UnitHasBuffBJ(udg_DamageEventSource, BUFF_ID) and not udg_IsDamageSpell then
                call thistype.run()
            endif
            return false
        endmethod

        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterVariableEvent( t, "udg_DamageEvent", EQUAL, 1.00 )
            call TriggerAddCondition(t, Condition(function thistype.check))
            set t = null
        endmethod
    endstruct


endscope
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Sure I'll try port this to use my system.

For UnitIndexer I use UnitDex at the jass section by TriggerHappy. It's the easiest one to import and it already have almost all the things you could ever need from a UnitIndexer.
The reason I didn't use CTL is because it only supports one timeout (32 FPS) while SpellFramework is supposed to let users define their timeout for each spell. Another thing is that its API is not fully compatible for SpellFramework's public API. There's no way I can abstract the module implementations from CTL away from the user of SpellFramework. A better candidate to use would be TimerTools but its author Nestharus himself said there's a bug in the system that breaks native timers for some unknown reason. Anyway my system only uses one timer for each spell struct, so it should still be relatively efficient.


EDIT:

Here's a quick writeup:

Some other changes:
  • Now uses only 1 dummy caster. To make sure this works, be sure to properly configure your dummy unit. Ensure that you check 'Is a building' so that your dummy caster don't need to rotate to cast. Also, the cast range of the dummy's spell should be maximum, etc.
  • Uses UnitDex by TriggerHappy (link provided at the code). Make sure to follow importing instructions written in the UnitDex thread.
  • Added RegisterPlayerUnitEvent library

I haven't tested nor even compiled this so there most likely will be errors. But I suggest that you already try this now, and I'll just fix this the next day.
JASS:
library Camouflage /*


    */uses /*

    */SpellFramework             /*    https://www.hiveworkshop.com/threads/325448/
    */UnitDex                    /*    https://www.hiveworkshop.com/threads/248209/
    */RegisterPlayerUnitEvent    /*    https://www.hiveworkshop.com/threads/250266/
    */

//===========================================================================
//=============================SETUP START===================================
//===========================================================================

    private module StealthConfiguration

        static constant integer SPELL_ID            = 'A00U' //the rawcode of the spell
        static constant integer SPELL_EVENT_TYPE    = EVENT_SPELL_EFFECT
        static constant real SPELL_PERIOD           = 1./32.

        static constant integer BUFF_ID             = 'B00D' //the rawcode of the buff
        static constant integer INVIS_ID            = 'A00S' //the rawcode of the invis
        static constant integer DUMMY_ID            = 'h001' //the rawcode of the dummy
        static constant integer DUMMY_SPELL_ID      = 'A00T' //the rawcode of ability added to dummy to apply buff
        static constant player DUMMY_PLAYER         = Player(PLAYER_NEUTRAL_PASSIVE) //owner of dummy caster

        static constant integer TRANSPARENCY        = 110

        static constant method Duration takes integer level returns real
            return 0. + 10.*level
        endmethod
        static constant method HeroDetectionRange takes integer level returns real
            return 500. + 0.*level // returns the range the spell will affect
        endmethod
        static constant method BackInvis takes integer level returns real
            return 1. + 0.*level
        endmethod
        static constant method ShimmerDuration takes integer level returns real
            return 1. + 0.*level
        endmethod

        static method Targets takes unit target, unit caster returns boolean
            return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(caster)) and IsUnitType(target, UNIT_TYPE_HERO)
        endmethod

        static method OnShimmerStart takes unit caster returns nothing
            /*
            *    What happens when the caster shimmers
            *    For example, you could create additional special effects for the caster here
            */
        endmethod
        static method OnShimmerEnd takes unit caster returns nothing
            /*
            *    What happens when the caster becomes invisible again after shimmering
            */
        endmethod

    endmodule

//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
    native UnitAlive takes unit u returns boolean

    globals
        private group enumGroup = CreateGroup()
    endglobals
//===========================================================================
 
    private struct Camouflage extends array

        implement StealthConfiguration

        private static unit dummy
        private static unit enumUnit

        private player p
        private real duration
        private real range
        private real backInvis
        private integer level
        private boolean seen

        private method onSpellStart takes nothing returns thistype
            set this = GetUnitId(Spell.triggerUnit)

            set this.level          = Spell.level
            set this.p              = Spell.triggerPlayer
            set this.backInvis      = BackInvis(Spell.level)
            set this.range          = HeroDetectionRange(Spell.level)
            set this.duration       = Duration(Spell.level)
            set this.seen           = false

            if IssueTargetOrder(dummy, "acidbomb", Spell.triggerUnit) then // add buff
                call UnitAddAbility(Spell.triggerUnit, INVIS_ID) // add invis

                return this // add 'this' instance to periodic timer
            endif

            return 0 // [ERROR] issued dummy order failed, don't run periodic operations
        endmethod

        private method onSpellPeriodic takes nothing returns boolean
            local unit caster = GetUnitById(this)
            local real i = 0
            local real cX = GetUnitX(caster)
            local real cY = GetUnitY(caster)
            local real dx
            local real dy
            local boolean seen = false
            /*
            *    If there is at least 1 hero within detection range, mark the caster as 'seen'
            */
            call GroupEnumUnitsInRange(enumGroup, cX, cY, this.range, null)
            loop
                set enumUnit = FirstOfGroup(enumGroup)
                exitwhen enumUnit == null
                if Targets(enumUnit, caster) then
                    set seen = true
                endif
                call GroupRemoveUnit(enumGroup, enumUnit)
            endloop
            /*
            *    Keep track of the moment when the 'seen' status of the caster changes,
            *     i.e., the moment when the caster becomes seen, or unseen
            */
            if seen != this.seen then
                set this.seen = seen
                if seen then
                    /*
                    *    caster has just been seen by at least 1 enemy hero
                    */
                    call UnitRemoveAbility(caster, INVIS_ID)
                    call SetUnitVertexColor(caster, 255, 255, 255, TRANSPARENCY)
                else
                    /*
                    *    caster has just been unseen by ALL enemy heroes
                    */
                    call SetUnitVertexColor(caster, 255, 255, 255, 255)
                    call UnitAddAbility(caster, INVIS_ID)
                    set this.backInvis = BackInvis(this.level)
                endif
            endif
            /*
            *    update spell duration
            */
            set this.duration = this.duration - SPELL_PERIOD
            if this.duration < 0.00 then
                call UnitRemoveAbility(caster, BUFF_ID)
                return true // duration ended: end periodic operations
            endif

            if this.backInvis > 0. then
                /*
                *    caster is shimmering
                */
                set this.backInvis = this.backInvis - SPELL_PERIOD
                if this.backInvis <= 0. then
                    /*
                    *    shimmering ended: add buff again
                    */
                    call IssueTargetOrder(dummy, "acidbomb", caster)

                    static if thistype.OnShimmerEnd.exists then
                        call OnShimmerEnd(caster)
                    endif

                    return false // do not end periodic operations
                endif
            endif
            /*
            *    if the caster no longer has the buff and is not just shimmering, end periodic operations
            */
            return GetUnitAbilityLevel(GetUnitById(this), BUFF_ID) == 0 and this.backInvis < 0.
        endmethod

        private method onSpellEnd takes nothing returns nothing
            local unit caster = GetUnitById(this)

            call SetUnitVertexColor(caster, 255, 255, 255, 255)
            if GetUnitAbilityLevel(caster , INVIS_ID) > 0 then
                call UnitRemoveAbility(caster, INVIS_ID)
            endif

            // Wash leaks
            set caster = null
        endmethod

        implement SpellEvent // IMPORTANT so that the above methods automatically run when the correct spell is cast

        //===========================================================================

        private static method OnOtherSpellCast takes nothing returns boolean
            return GetSpellAbilityId() != SPELL_ID and UnitRemoveAbility(GetTriggerUnit(), BUFF_ID)
        endmethod

        private static method OnCasterAttacks takes nothing returns boolean
            return UnitRemoveAbility(GetAttacker(), BUFF_ID)
        endmethod

        private static method OnCasterDamaged takes nothing returns boolean
            local thistype this

            if GetUnitAbilityLevel(udg_DamageEventTarget, BUFF_ID) > 0 then
                call UnitRemoveAbility(udg_DamageEventTarget, BUFF_ID)
                set this = GetUnitId(udg_DamageEventTarget)
                set this.backInvis = ShimmerDuration(this.level)
    
                static if thistype.OnShimmerStart.exists then
                    call OnShimmerStart(udg_DamageEventTarget)
                endif
            endif

            return false
        endmethod

        //===========================================================================
        private static method onInit takes nothing returns nothing
            // other events
            local trigger t = CreateTrigger()
            call TriggerRegisterVariableEvent(t, "udg_DamageEvent", EQUAL, 1.00)
            call TriggerAddCondition(t, Condition(function thistype.OnCasterDamaged))

            call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.OnOtherSpellCast)
            call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED, function thistype.OnCasterAttacks)

            // setting globals
            set dummy = CreateUnit(DUMMY_PLAYER, DUMMY_ID, 0., 0., 270.) // Only need to create dummy caster once
            call UnitAddAbility(dummy, DUMMY_SPELL_ID)

            set t = null

            // preloading effects
        endmethod

    endstruct


endlibrary
 
Last edited:
Level 7
Joined
Feb 9, 2021
Messages
301
Sure I'll try port this to use my system.

For UnitIndexer I use UnitDex at the jass section by TriggerHappy. It's the easiest one to import and it already have almost all the things you could ever need from a UnitIndexer.
The reason I didn't use CTL is because it only supports one timeout (32 FPS) while SpellFramework is supposed to let users define their timeout for each spell. Another thing is that its API is not fully compatible for SpellFramework's public API. There's no way I can abstract the module implementations from CTL away from the user of SpellFramework. A better candidate to use would be TimerTools but its author Nestharus himself said there's a bug in the system that breaks native timers for some unknown reason. Anyway my system only uses one timer for each spell struct, so it should still be relatively efficient.


EDIT:

Here's a quick writeup:

Some other changes:
  • Now uses only 1 dummy caster. To make sure this works, be sure to properly configure your dummy unit. Ensure that you check 'Is a building' so that your dummy caster don't need to rotate to cast. Also, the cast range of the dummy's spell should be maximum, etc.
  • Uses UnitDex by TriggerHappy (link provided at the code). Make sure to follow importing instructions written in the UnitDex thread.
  • Added RegisterPlayerUnitEvent library

I haven't tested nor even compiled this so there most likely will be errors. But I suggest that you already try this now, and I'll just fix this the next day.
JASS:
library Camouflage /*


    */uses /*

    */SpellFramework             /*    https://www.hiveworkshop.com/threads/325448/
    */UnitDex                    /*    https://www.hiveworkshop.com/threads/248209/
    */RegisterPlayerUnitEvent    /*    https://www.hiveworkshop.com/threads/250266/
    */

//===========================================================================
//=============================SETUP START===================================
//===========================================================================

    private module StealthConfiguration

        static constant integer SPELL_ID            = 'A00U' //the rawcode of the spell
        static constant integer SPELL_EVENT_TYPE    = EVENT_SPELL_EFFECT
        static constant real SPELL_PERIOD           = 1./32.

        static constant integer BUFF_ID             = 'B00D' //the rawcode of the buff
        static constant integer INVIS_ID            = 'A00S' //the rawcode of the invis
        static constant integer DUMMY_ID            = 'h001' //the rawcode of the dummy
        static constant integer DUMMY_SPELL_ID      = 'A00T' //the rawcode of ability added to dummy to apply buff
        static constant player DUMMY_PLAYER         = Player(PLAYER_NEUTRAL_PASSIVE) //owner of dummy caster

        static constant integer TRANSPARENCY        = 110

        static constant method Duration takes integer level returns real
            return 0. + 10.*level
        endmethod
        static constant method HeroDetectionRange takes integer level returns real
            return 500. + 0.*level // returns the range the spell will affect
        endmethod
        static constant method BackInvis takes integer level returns real
            return 1. + 0.*level
        endmethod
        static constant method ShimmerDuration takes integer level returns real
            return 1. + 0.*level
        endmethod

        static method Targets takes unit target, unit caster returns boolean
            return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(caster)) and IsUnitType(target, UNIT_TYPE_HERO)
        endmethod

        static method OnShimmerStart takes unit caster returns nothing
            /*
            *    What happens when the caster shimmers
            *    For example, you could create additional special effects for the caster here
            */
        endmethod
        static method OnShimmerEnd takes unit caster returns nothing
            /*
            *    What happens when the caster becomes invisible again after shimmering
            */
        endmethod

    endmodule

//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
    native UnitAlive takes unit u returns boolean

    globals
        private group enumGroup = CreateGroup()
    endglobals
//===========================================================================

    private struct Camouflage extends array

        implement StealthConfiguration

        private static unit dummy
        private static unit enumUnit

        private player p
        private real duration
        private real range
        private real backInvis
        private integer level
        private boolean seen

        private method onSpellStart takes nothing returns thistype
            set this = GetUnitId(Spell.triggerUnit)

            set this.level          = Spell.level
            set this.p              = Spell.triggerPlayer
            set this.backInvis      = BackInvis(Spell.level)
            set this.range          = HeroDetectionRange(Spell.level)
            set this.duration       = Duration(Spell.level)
            set this.seen           = false

            if IssueTargetOrder(dummy, "acidbomb", Spell.triggerUnit) then // add buff
                call UnitAddAbility(Spell.triggerUnit, INVIS_ID) // add invis

                return this // add 'this' instance to periodic timer
            endif

            return 0 // [ERROR] issued dummy order failed, don't run periodic operations
        endmethod

        private method onSpellPeriodic takes nothing returns boolean
            local unit caster = GetUnitById(this)
            local real i = 0
            local real cX = GetUnitX(caster)
            local real cY = GetUnitY(caster)
            local real dx
            local real dy
            local boolean seen = false
            /*
            *    If there is at least 1 hero within detection range, mark the caster as 'seen'
            */
            call GroupEnumUnitsInRange(enumGroup, cX, cY, this.range, null)
            loop
                set enumUnit = FirstOfGroup(enumGroup)
                exitwhen enumUnit == null
                if Targets(enumUnit, caster) then
                    set seen = true
                endif
                call GroupRemoveUnit(enumGroup, enumUnit)
            endloop
            /*
            *    Keep track of the moment when the 'seen' status of the caster changes,
            *     i.e., the moment when the caster becomes seen, or unseen
            */
            if seen != this.seen then
                set this.seen = seen
                if seen then
                    /*
                    *    caster has just been seen by at least 1 enemy hero
                    */
                    call UnitRemoveAbility(caster, INVIS_ID)
                    call SetUnitVertexColor(caster, 255, 255, 255, TRANSPARENCY)
                else
                    /*
                    *    caster has just been unseen by ALL enemy heroes
                    */
                    call SetUnitVertexColor(caster, 255, 255, 255, 255)
                    call UnitAddAbility(caster, INVIS_ID)
                    set this.backInvis = BackInvis(this.level)
                endif
            endif
            /*
            *    update spell duration
            */
            set this.duration = this.duration - SPELL_PERIOD
            if this.duration < 0.00 then
                call UnitRemoveAbility(caster, BUFF_ID)
                return true // duration ended: end periodic operations
            endif

            if this.backInvis > 0. then
                /*
                *    caster is shimmering
                */
                set this.backInvis = this.backInvis - SPELL_PERIOD
                if this.backInvis <= 0. then
                    /*
                    *    shimmering ended: add buff again
                    */
                    call IssueTargetOrder(dummy, "acidbomb", caster)

                    static if thistype.OnShimmerEnd.exists then
                        call OnShimmerEnd(caster)
                    endif

                    return false // do not end periodic operations
                endif
            endif
            /*
            *    if the caster no longer has the buff and is not just shimmering, end periodic operations
            */
            return GetUnitAbilityLevel(GetUnitById(this), BUFF_ID) == 0 and this.backInvis < 0.
        endmethod

        private method onSpellEnd takes nothing returns nothing
            local unit caster = GetUnitById(this)

            call SetUnitVertexColor(caster, 255, 255, 255, 255)
            if GetUnitAbilityLevel(caster , INVIS_ID) > 0 then
                call UnitRemoveAbility(caster, INVIS_ID)
            endif

            // Wash leaks
            set caster = null
        endmethod

        implement SpellEvent // IMPORTANT so that the above methods automatically run when the correct spell is cast

        //===========================================================================

        private static method OnOtherSpellCast takes nothing returns boolean
            return GetSpellAbilityId() != SPELL_ID and UnitRemoveAbility(GetTriggerUnit(), BUFF_ID)
        endmethod

        private static method OnCasterAttacks takes nothing returns boolean
            return UnitRemoveAbility(GetAttacker(), BUFF_ID)
        endmethod

        private static method OnCasterDamaged takes nothing returns boolean
            local thistype this

            if GetUnitAbilityLevel(udg_DamageEventTarget, BUFF_ID) > 0 then
                call UnitRemoveAbility(udg_DamageEventTarget, BUFF_ID)
                set this = GetUnitId(udg_DamageEventTarget)
                set this.backInvis = ShimmerDuration(this.level)

                static if thistype.OnShimmerStart.exists then
                    call OnShimmerStart(udg_DamageEventTarget)
                endif
            endif

            return false
        endmethod

        //===========================================================================
        private static method onInit takes nothing returns nothing
            // other events
            local trigger t = CreateTrigger()
            call TriggerRegisterVariableEvent(t, "udg_DamageEvent", EQUAL, 1.00)
            call TriggerAddCondition(t, Condition(function thistype.OnCasterDamaged))

            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.OnOtherSpellCast)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED, function thistype.OnCasterAttacks)

            // setting globals
            set dummy = CreateUnit(DUMMY_PLAYER, DUMMY_ID, 0., 0., 270.) // Only need to create dummy caster once
            call UnitAddAbility(dummy, DUMMY_SPELL_ID)

            set t = null

            // preloading effects
        endmethod

    endstruct


endlibrary
Thank you, I will test it and check more closely after I get back from work. On the first sight, your code, similar to mine, does not distinguish between revelation and shimmering. When the unit is near enemies, it will be revealed, it can be targeted and you can see his hp bar. However, "Camouflaged units will briefly shimmer whenever they take damage. A unit cannot shimmer more frequently than once per second, and shimmering does not make a unit targetable nor remove the stealth effect, but grants positional information to the enemy". Therefore, these are two separate functions.

Edit: Another part that I just realised is "Camouflaged units are revealed while being inside the line of sight (i.e not in the fog of war or behind terrain)". I think there was a library for this.
Edit2: Also, I missed the fact that when the caster uses an item, invis disappears.

Your code seems to be much more efficient than mine. Still, a lot to learn. You do through one group, while I had two (I thought this would be more efficient)

Since you are knowledgeable about coding, I wanted to ask a couple of more questions about systems. This weekend, I am finalising all system I will be using for my MOBA map. I was planning to use CTL for periodic and TimerUtils for single timers. However, it seems like I will not need CTL with your system. I like your system. It is very easy to use. However, will I lose much on performance?

I also want to implement Dummy System and Unit Recycler system. Which ones would you recommend? I am kinda stuck between yours, this one [vJASS] - Safe Recycler System + Dummy System and Dummy Recycler v1.25. Maybe there are other better ones? I am looking to make it as efficient as possible, as I am building for users with weak PCs.

Edit: Do you use this [System] GroupUtils

Edit: I found this:
1617367388962.png
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Thank you, I will test it and check more closely after I get back from work. On the first sight, your code, similar to mine, does not distinguish between revelation and shimmering. When the unit is near enemies, it will be revealed, it can be targeted and you can see his hp bar. However, "Camouflaged units will briefly shimmer whenever they take damage. A unit cannot shimmer more frequently than once per second, and shimmering does not make a unit targetable nor remove the stealth effect, but grants positional information to the enemy". Therefore, these are two separate functions.

Edit: Another part that I just realised is "Camouflaged units are revealed while being inside the line of sight (i.e not in the fog of war or behind terrain)". I think there was a library for this.
Edit2: Also, I missed the fact that when the caster uses an item, invis disappears.
Yeah I realized I've got to change these. I haven't picked up on all the spell details by speed reading the spell description.

For unit recycling I suggest using mine since it also considers unit facing angle, while I'm not sure if that other lib does. Then for dummies that you use for special effects I also suggest using Flux's DummyRecycler that you linked. Even tho my UnitRecycler is general purpose and can be used for dummies too, I still suggest using a specialized dummy recyer alongside it since they are more efficient at handling dummies as they are made for that particular purpose.
You won't lose any noticeable performance if you use mine periodic spells compared to using CTL. Actually there are scenarios where only using a single timer like CTL does could lead to lesser performance but there are also scenarios where it's the othrr way around. I could provide you with more explanation and illustration regarding this but its hard just typing on my phone : D.
The difference between recycling and pausing timers would in practical cases be totally insignificant since this is just a single (or two) operation and happens very very seldom compared to let's say the periodic operations. The gain or lose solely from this recycling or pausing would be like the difference between adding a spoon vs a cup of water in a waterpool. In isolation the spoon is many times lesser than the cup but relative to the waterpool the difference wont be very significant.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Yeah I realized I've got to change these. I haven't picked up on all the spell details by speed reading the spell description.

For unit recycling I suggest using mine since it also considers unit facing angle, while I'm not sure if that other lib does. Then for dummies that you use for special effects I also suggest using Flux's DummyRecycler that you linked. Even tho my UnitRecycler is general purpose and can be used for dummies too, I still suggest using a specialized dummy recyer alongside it since they are more efficient at handling dummies as they are made for that particular purpose.
You won't lose any noticeable performance if you use mine periodic spells compared to using CTL. Actually there are scenarios where only using a single timer like CTL does could lead to lesser performance but there are also scenarios where it's the othrr way around. I could provide you with more explanation and illustration regarding this but its hard just typing on my phone : D.
The difference between recycling and pausing timers would in practical cases be totally insignificant since this is just a single (or two) operation and happens very very seldom compared to let's say the periodic operations. The gain or lose solely from this recycling or pausing would be like the difference between adding a spoon vs a cup of water in a waterpool. In isolation the spoon is many times lesser than the cup but relative to the waterpool the difference wont be very significant.
That makes sense, thank you. One library less to learn : )

I wanted to get your opinion on some other libraries (Sorry for so many questions, but I don't really have anyone to ask : ( ) :
1. [System] GroupUtils
2. I noticed in Jaina Spellpack, JAKEZINC uses his Pause Library and Dispel Library. Why he does not just stun units? In his case, units are frozen, so it makes sense that they will have a buff "frozen". Also, I don't see a need for Dispel Library, since Buff library already allows to remove buffs.


Edit:

Is it impossible to call on function outside of the struct? For Jass, I have been using some cJass to make my life easier. For example, I had these functions and just called them in all triggers:
JASS:
function Condition_Base takes player p,unit e returns boolean
   return IsUnitEnemy(e,p) and not IsUnitType(e,UNIT_TYPE_DEAD) and not IsUnitType(e,UNIT_TYPE_STRUCTURE)
endfunction

function BaseBool takes nothing returns boolean
   return GetUnitAbilityLevel(GetFilterUnit(),'Aloc') == 0 and not IsUnitType(GetFilterUnit(),UNIT_TYPE_DEAD) and not IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE)
endfunction
 


function GetAngleBetweenPoints takes real x1, real x2, real y1, real y2 returns real
    local real result = bj_RADTODEG * Atan2(y2 - y1, x2 - x1)
    return result
endfunction


function GetXWithOffset takes real x, real distance, real angle returns real
    local real xWithOffset = x + distance * Cos(angle * bj_DEGTORAD)
    return xWithOffset
endfunction


function GetYWithOffset takes real y, real distance, real angle returns real
    local real yWithOffset = y + distance * Sin(angle * bj_DEGTORAD)
    return yWithOffset
endfunction


function GetDistanceBetweenCoordinates takes real x1, real x2, real y1, real y2 returns real
    local real dx = x2 - x1
    local real dy = y2 - y1
    return SquareRoot(dx * dx + dy * dy)
endfunction


function setUnitPosition takes unit u, real x, real y returns nothing
    call SetUnitX(u, x)
    call SetUnitY(u, y)
    set u = null
endfunction
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
1. The group recycling feature isn't a big gain (if there even is a gain) imo and my reasoning for this is similar to what I've given above for timer recycling. It's convenient to use if you want to take into account unit collision sizes when enumerating but this can easily be done manually too. Example:
JASS:
call GroupUnitsInArea(g, x, y, r)
loop
    set u = FirstOfGroup(g)
    exitwhen u == null
    call GroupRemoveUnit(g, u)
    // <actions>
endloop
->
JASS:
call GroupEnumUnitsInRange(g, x, y, r + MAX_UNIT_COLLISION_SIZE, null)
loop
    set u = FirstOfGroup(g)
    exitwhen u == null
    call GroupRemoveUnit(g, u)
    if IsUnitInRangeXY(u, x, y, r) then
        // <actions>
    endif
endloop
Personally I wouldn't use it because of how little there is to gain in terms of convenience in exchange for the overhead it adds when enumerating units. (See the library code to know exactly how it makes enumeration slower)

2. I'm not fully sure but I think he uses pause instead of stun to still allow frozen units to cast spells. I maybe wrong. I'm not sure regarding the DispelLibrary either since when creating spells, I usually do not use any buffs whatsoever.

3. It's possible. Just make sure these function declarations appear before the part of your code that calls them.
 
Level 7
Joined
Feb 9, 2021
Messages
301
1. The group recycling feature isn't a big gain (if there even is a gain) imo and my reasoning for this is similar to what I've given above for timer recycling. It's convenient to use if you want to take into account unit collision sizes when enumerating but this can easily be done manually too. Example:
JASS:
call GroupUnitsInArea(g, x, y, r)
loop
    set u = FirstOfGroup(g)
    exitwhen u == null
    call GroupRemoveUnit(g, u)
    // <actions>
endloop
->
JASS:
call GroupEnumUnitsInRange(g, x, y, r + MAX_UNIT_COLLISION_SIZE, null)
loop
    set u = FirstOfGroup(g)
    exitwhen u == null
    call GroupRemoveUnit(g, u)
    if IsUnitInRangeXY(u, x, y, r) then
        // <actions>
    endif
endloop
Personally I wouldn't use it because of how little there is to gain in terms of convenience in exchange for the overhead it adds when enumerating units. (See the library code to know exactly how it makes enumeration slower)

2. I'm not fully sure but I think he uses pause instead of stun to still allow frozen units to cast spells. I maybe wrong. I'm not sure regarding the DispelLibrary either since when creating spells, I usually do not use any buffs whatsoever.

3. It's possible. Just make sure these function declarations appear before the part of your code that calls them.
Thanks.

On 3, then I have to define these in each function every time in every new spell. Before, I just had it here:

1617453725710.png


Is there a way to do a similar thing to call them from the libraries?


Edit: How do you stuns, roots, attack speed slow and etc without buffs?
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Is there way to do similar thing to call them for the libraries?
You can put them inside a library. Functions inside libraries will be put on the top of map script after compilation. And to ensure proper library order:
JASS:
library Foo
    function bar
    endfunction
endlibrary

...

library MyLib uses A
    function OnInit
        call bar()
    endfunction
endlibrary
If you're not calling it from another library, you can call them directly since it's ensured those functions are compiled first so long as you put those functions inside a library. For more info, see WC3 Modding Information Center - VJASS Documentation for the vjass manual.

Edit: How do you stuns, roots, attack speed slow and etc without buffs?
I use dummy spells as there is no other way. They put buffs on the target ofcouse, what I mean by my above statement is I don't use buffs for the custom coded aspects of the spell.
 
Level 7
Joined
Feb 9, 2021
Messages
301
You can put them inside a library. Functions inside libraries will be put on the top of map script after compilation. And to ensure proper library order:
JASS:
library Foo
    function bar
    endfunction
endlibrary

...

library MyLib uses A
    function OnInit
        call bar()
    endfunction
endlibrary
If you're not calling it from another library, you can call them directly since it's ensured those functions are compiled first so long as you put those functions inside a library. For more info, see WC3 Modding Information Center - VJASS Documentation for the vjass manual.


I use dummy spells as there is no other way. They put buffs on the target ofcouse, what I mean by my above statement is I don't use buffs for the custom coded aspects of the spell.
So, just to make sure I got it correctly. I used private structs and functions before for building spells and had no problems with these cJass functions. However, your system has spells inside of libraries to work with your system. It is already obvious that I can't call these cJass functions from inside the library. Therefore, I have to make my cJass functions libraries:

JASS:
library Base_Functions

function GetAngleBetweenPoints takes real x1, real x2, real y1, real y2 returns real
    local real result = bj_RADTODEG * Atan2(y2 - y1, x2 - x1)
    return result
endfunction


function GetXWithOffset takes real x, real distance, real angle returns real
    local real xWithOffset = x + distance * Cos(angle * bj_DEGTORAD)
    return xWithOffset
endfunction

function GetYWithOffset takes real y, real distance, real angle returns real
    local real yWithOffset = y + distance * Sin(angle * bj_DEGTORAD)
    return yWithOffset
endfunction

endlibrary


Then, I just built a spell using your system


JASS:
library Hammer /*


    */uses /*

    */SpellFramework    /*

    */

    /*****************************************************************
    *                       SPELL CONFIGURATION                      *
    *****************************************************************/
    private module SpellConfiguration

        static constant integer SPELL_ABILITY_ID            = 'A006'

        static constant integer SPELL_EVENT_TYPE            = EVENT_SPELL_EFFECT
       
        static constant integer FROZEN_BUFF                 = 'B001'

        static constant real SPELL_PERIOD                   = FPS

        static constant real SFX_DEATH_TIME                 = 1.50
       
        static constant real AOE                            = 70
       
        static constant real BASE_SPEED                     = 1200 * FPS
       
        static constant real ACCEL                          = 1.1 // speed acceleration
       
        static constant real DIST                           = 500
       
        static constant integer MAX_TARGETS                  = 1
       
       
        //Fly SFX
        static constant string FLY_SFX              = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
        static constant string FLY_SFX_ATTACH       = "origin"
        //Knockback SFX
        static constant string KNOCKBACK_SFX        = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"
        static constant string KNOCKBACK_SFX_ATTACH = "origin"

        //                                      The attack type of inflicting damage
        static constant attacktype ATTACK_TYPE      = ATTACK_TYPE_MAGIC
        //                                      The damage type of inflicting damage
        static constant damagetype DAMAGE_TYPE      = DAMAGE_TYPE_MAGIC
        //                                      The weapon type of inflicting damage
        static constant weapontype WEAPON_TYPE      = WEAPON_TYPE_WHOKNOWS
       
       
       
        static method DMG takes integer level, unit caster, real curDist returns real
            return 10.*level + 0.1 * GetHeroStr(caster, true) + curDist * 10
        endmethod
       
        static constant method Frozen_DMG_Perc_Hp takes integer level returns real
            return 0.2 + 0.*level
        endmethod
       
        static method Knock_Range takes real curDist returns real
            return curDist/4
        endmethod
       
       
        static method Knock_Speed takes real curSpeed returns real
            return curSpeed * 0.5
        endmethod
       
       
        static method Frozen_Cond takes unit target returns boolean
            return GetWidgetLife(target) <= ( GetUnitStateSwap(UNIT_STATE_MAX_LIFE, target) * Frozen_DMG_Perc_Hp  )
        endmethod
       
        static method Targets takes unit target, unit caster returns boolean
            return GetWidgetLife(target) > 0.405 and IsUnitEnemy(target, GetOwningPlayer(caster)) and not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
        endmethod
       

    endmodule



    /*****************************************************************
    *                   END OF SPELL CONFIGURATION                   *
    *****************************************************************/

    /*========================= SPELL CODE =========================*/
   
   
    globals
        private group enumGroup = CreateGroup()
       
    endglobals

    public struct Hammer extends array

        implement SpellConfiguration

        private static unit enumU

       
        private real dmg
        private real xC
        private real yC
        private real xT
        private real yT
        private real angle
        private real curDist
        private real speed
       
       
        private integer level
       

        private method onSpellStart takes nothing returns thistype
            set this = GetUnitId(Spell.triggerUnit)
           
            set this.level          = Spell.level
            set this.dmg            = 0
            set this.xC             = GetUnitX(Spell.triggerUnit)
            set this.yC             = GetUnitY(Spell.triggerUnit)
            set this.xT             = GetSpellTargetX()
            set this.yT             = GetSpellTargetY()
            set this.angle          = 270//GetAngleBetweenPoints(this.xC, this.xT, this.yC, this.yT)
            set this.speed          = BASE_SPEED
            set this.curDist        = 0
           
            call SetUnitPathing(Spell.triggerUnit, false)
           
            return this
        endmethod

        private method onSpellPeriodic takes nothing returns boolean
            local unit caster = GetUnitById(this)
           
            local integer enem_counter = 0
           
            local real cX = GetUnitX(caster)
            local real cY = GetUnitY(caster)
           
            local real xWithOffset = 0//GetXWithOffset(cX, this.speed, this.angle)
            local real yWithOffset = 0//GetYWithOffset(cY, this.speed, this.angle)
           
            local integer targets
           
            set this.dmg = DMG(this.level, caster, this.curDist)
           
            call SetUnitAnimationByIndex(caster, 0)

            //Select Enemies in the unit's range and deal to the first in front of the hero
            call GroupEnumUnitsInRange(enumGroup, xWithOffset, yWithOffset, AOE, null)
            loop
                set enumU = FirstOfGroup(enumGroup)
                exitwhen enem_counter == MAX_TARGETS or enumU == null
                if Targets(enumU, caster) then
                    if UnitHasBuffBJ(enumU, FROZEN_BUFF) and Frozen_Cond(enumU) then
                        call UnitDamageTarget(caster, enumU, 999999, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE )
                        call BJDebugMsg("DiedFrozen")
                    else
                        call UnitDamageTarget(caster,enumU, this.dmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE )
                        set udg_KB3D_Unit = enumU
                        set udg_KB3D_Range = Knock_Range(this.curDist)
                        set udg_KB3D_Speed = Knock_Speed(this.curDist)
                        set udg_KB3D_Accel = -20.
                        set udg_KB3D_Angle = this.angle
                        set udg_KB3D_Fx = KNOCKBACK_SFX
                        set udg_KB3D_iKB = true
                        set udg_KB3D_DisableUnit = true
                        set udg_KB3D_EndOnObstacle = true
                        call ExecuteFunc( "KB3D_Registration" )
                   
                    endif

                    set enem_counter = enem_counter + 1
     
                endif
                call GroupRemoveUnit(enumGroup, enumU)
            endloop
           
                //turn the trigger off if
            if enem_counter == 1 or this.curDist > DIST or not IsTerrainWalkable(xWithOffset, yWithOffset) then
               
                call SetUnitAnimationByIndex(caster, 2)
                call SetUnitPathing(caster, true)
               
                return true // Return true to end the periodic process and proceed to onSpellEnd()
            else
                set this.curDist = this.curDist + this.speed
                //Move the Unit
                call SetUnitX(caster, xWithOffset)
                call SetUnitY(caster, yWithOffset)
                set this.speed = this.speed * ACCEL
               
                //Create SFX in thе caster's Location
               
           
            endif
               
            return false   
        endmethod

        private method onSpellEnd takes nothing returns nothing
        //


        endmethod

        implement SpellEvent

    endstruct


endlibrary



Do I have to change it to "library Hammer requires Base_Functions" and then commands such as "GetXWithOffset(cX, this.speed, this.angle)" will work work?
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Here is (hopefully) the fixed version of Camouflage:
JASS:
library Camouflage /*


    */uses /*

    */SpellFramework             /*    https://www.hiveworkshop.com/threads/325448/
    */UnitDex                    /*    https://www.hiveworkshop.com/threads/248209/
    */RegisterPlayerUnitEvent    /*    https://www.hiveworkshop.com/threads/250266/
    */

//===========================================================================
//=============================SETUP START===================================
//===========================================================================

    private module StealthConfiguration

        static constant integer SPELL_ABILITY_ID    = 'A00U' //the rawcode of the spell
        static constant integer SPELL_EVENT_TYPE    = EVENT_SPELL_EFFECT
        static constant real SPELL_PERIOD           = 1./32.

        static constant integer BUFF_ID             = 'B00D' //the rawcode of the buff
        static constant integer INVIS_ID            = 'A00S' //the rawcode of the invis
        static constant integer DUMMY_ID            = 'h001' //the rawcode of the dummy
        static constant integer DUMMY_SPELL_ID      = 'A00T' //the rawcode of ability added to dummy to apply buff
        static constant player DUMMY_PLAYER         = Player(PLAYER_NEUTRAL_PASSIVE) //owner of dummy caster

        static constant string SHIMMER_SFX_MODEL    = ""

        static constant integer TRANSPARENCY        = 110

        static constant method Duration takes integer level returns real
            return 0. + 10.*level
        endmethod
        static constant method HeroDetectionRange takes integer level returns real
            return 500. + 0.*level // returns the range the spell will affect
        endmethod
        static constant method BackInvis takes integer level returns real
            return 1. + 0.*level
        endmethod
        static constant method ShimmerCooldown takes integer level returns real
            return 1. + 0.*level
        endmethod

        static method Targets takes unit target, unit caster returns boolean
            return UnitAlive(target)                                and/*
                */ IsUnitEnemy(target, GetOwningPlayer(caster))     and/*
                */ IsUnitType(target, UNIT_TYPE_HERO)               and/*
                */ IsUnitVisible(target, GetOwningPlayer(caster)) // is caster in enemy line of sight
        endmethod

        static method OnCasterShimmer takes unit caster returns nothing
            /*
            *   <What shimmer does>
            */
            call DestroyEffect(AddSpecialEffect(SHIMMER_SFX_MODEL, GetUnitX(caster), GetUnitY(caster)))
        endmethod

        static method OnCasterRevealStart takes unit caster returns nothing
            /*
            *    What happens when the caster is revealed
            *    For example, you could create additional special effects for the caster here
            */
        endmethod
        static method OnCasterRevealEnd takes unit caster returns nothing
            /*
            *    What happens when the caster becomes invisible again after being revealed
            */
        endmethod

    endmodule

//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
    native UnitAlive takes unit u returns boolean

    globals
        private group enumGroup = CreateGroup()
    endglobals
//===========================================================================
 
    private struct Camouflage extends array

        implement StealthConfiguration

        private static unit dummy
        private static unit enumUnit
        private static unit caster

        private player p
        private real duration
        private real range
        private real shimmerCooldown
        private real backInvis
        private integer level
        private boolean seen

        private method onSpellStart takes nothing returns thistype
            set this = GetUnitId(Spell.triggerUnit)

            set this.level              = Spell.level
            set this.p                  = Spell.triggerPlayer
            set this.range              = HeroDetectionRange(Spell.level)
            set this.duration           = Duration(Spell.level)
            set this.seen               = false
            set this.shimmerCooldown    = 0.
            set this.backInvis          = 0.

            if IssueTargetOrder(dummy, "acidbomb", Spell.triggerUnit) then // add buff
                call UnitAddAbility(Spell.triggerUnit, INVIS_ID) // add invis

                return this // add 'this' instance to periodic timer
            endif

            return 0 // [ERROR] issued dummy order failed, don't run periodic operations
        endmethod

        private method onSpellPeriodic takes nothing returns boolean
            local real i = 0
            local real cX = GetUnitX(caster)
            local real cY = GetUnitY(caster)
            local real dx
            local real dy
            local boolean seen = false

            set caster = GetUnitById(this)
            /*
            *    If there is at least 1 hero within detection range, mark the caster as 'seen'
            */
            call GroupEnumUnitsInRange(enumGroup, cX, cY, this.range, null)
            loop
                set enumUnit = FirstOfGroup(enumGroup)
                exitwhen enumUnit == null
                if Targets(enumUnit, caster) then
                    set seen = true
                endif
                call GroupRemoveUnit(enumGroup, enumUnit)
            endloop
            /*
            *    Keep track of the moment when the 'seen' status of the caster changes,
            *     i.e., the moment when the caster becomes seen, or unseen
            */
            if seen != this.seen then
                set this.seen = seen
                if seen then
                    /*
                    *    caster has just been seen by at least 1 enemy hero
                    */
                    call UnitRemoveAbility(caster, INVIS_ID)
                    call SetUnitVertexColor(caster, 255, 255, 255, TRANSPARENCY)
                else
                    /*
                    *    caster has just been unseen by ALL enemy heroes
                    */
                    call SetUnitVertexColor(caster, 255, 255, 255, 255)
                    call UnitAddAbility(caster, INVIS_ID)
                    set this.backInvis = BackInvis(this.level)

                    static if thistype.OnCasterRevealStart.exists then
                        call OnCasterRevealStart(caster)
                    endif
                endif
            endif
            /*
            *    update spell duration
            */
            set this.duration = this.duration - SPELL_PERIOD
            if this.duration < 0.00 then
                call UnitRemoveAbility(caster, BUFF_ID)
                return true // duration ended: end periodic operations
            endif
            /*
            *   update shimmer cooldown
            */
            if this.shimmerCooldown > 0. then
                set this.shimmerCooldown = this.shimmerCooldown - SPELL_PERIOD
            endif

            if this.backInvis > 0. then
                /*
                *    caster is revealed
                */
                set this.backInvis = this.backInvis - SPELL_PERIOD
                if this.backInvis <= 0. then
                    /*
                    *    reveal ended: add buff again
                    */
                    call IssueTargetOrder(dummy, "acidbomb", caster)

                    static if thistype.OnCasterRevealEnd.exists then
                        call OnCasterRevealEnd(caster)
                    endif

                    return false // do not end periodic operations
                endif
            endif
            /*
            *    if the caster no longer has the buff even when not revealed, end periodic operations
            */
            return GetUnitAbilityLevel(GetUnitById(this), BUFF_ID) == 0 and this.backInvis <= 0.
        endmethod

        private method onSpellEnd takes nothing returns nothing
            local unit caster = GetUnitById(this)

            call SetUnitVertexColor(caster, 255, 255, 255, 255)
            if GetUnitAbilityLevel(caster , INVIS_ID) > 0 then
                call UnitRemoveAbility(caster, INVIS_ID)
            endif

            // Wash leaks
            set caster = null
        endmethod

        implement SpellEvent // IMPORTANT so that the above methods automatically run when the correct spell is cast

        //===========================================================================

        private static method OnOtherSpellCast takes nothing returns boolean
            return GetSpellAbilityId() != SPELL_ABILITY_ID and UnitRemoveAbility(GetTriggerUnit(), BUFF_ID)
        endmethod

        private static method OnCasterItemUse takes nothing returns boolean
            return UnitRemoveAbility(GetTriggerUnit(), BUFF_ID)
        endmethod

        private static method OnCasterAttacks takes nothing returns boolean
            return UnitRemoveAbility(GetAttacker(), BUFF_ID)
        endmethod

        private static method OnCasterDamaged takes nothing returns boolean
            local thistype this

            if GetUnitAbilityLevel(udg_DamageEventTarget, BUFF_ID) > 0 then
                call UnitRemoveAbility(udg_DamageEventTarget, BUFF_ID)

                set this = GetUnitId(udg_DamageEventTarget)
                if this.shimmerCooldown <= 0. then
                    set this.shimmerCooldown = ShimmerCooldown(this.level)
                    call OnCasterShimmer(udg_DamageEventTarget)
                endif
            endif

            return false
        endmethod

        //===========================================================================
        private static method onInit takes nothing returns nothing
            // other events
            local trigger t = CreateTrigger()
            call TriggerRegisterVariableEvent(t, "udg_DamageEvent", EQUAL, 1.00)
            call TriggerAddCondition(t, Condition(function thistype.OnCasterDamaged))

            call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.OnOtherSpellCast)
            call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_USE_ITEM, function thistype.OnCasterItemUse)
            call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED, function thistype.OnCasterAttacks)

            // setting globals
            set dummy = CreateUnit(DUMMY_PLAYER, DUMMY_ID, 0., 0., 270.) // Only need to create dummy caster once
            call UnitAddAbility(dummy, DUMMY_SPELL_ID)

            set t = null

            // preloading effects
        endmethod

    endstruct


endlibrary

So, just to make sure I got it correctly. I used private structs and functions before for building spells and had no problems with these cJass functions. However, your system has spells inside of libraries to work with your system. It is already obvious that I can't call these cJass functions from inside the library. Therefore, I have to make my cJass functions libraries:
Yup either that, or you could make your spells NOT inside a library, just structs. This way, your spell struct can both call functions from my system as well as your cjass functions.

Do I have to change it to "library Hammer requires Base_Functions" and then commands such as "GetXWithOffset(cX, this.speed, this.angle)" will work work?
Yup
 
Level 7
Joined
Feb 9, 2021
Messages
301
Here is (hopefully) the fixed version of Camouflage:
JASS:
library Camouflage /*


    */uses /*

    */SpellFramework             /*    https://www.hiveworkshop.com/threads/325448/
    */UnitDex                    /*    https://www.hiveworkshop.com/threads/248209/
    */RegisterPlayerUnitEvent    /*    https://www.hiveworkshop.com/threads/250266/
    */

//===========================================================================
//=============================SETUP START===================================
//===========================================================================

    private module StealthConfiguration

        static constant integer SPELL_ABILITY_ID    = 'A00U' //the rawcode of the spell
        static constant integer SPELL_EVENT_TYPE    = EVENT_SPELL_EFFECT
        static constant real SPELL_PERIOD           = 1./32.

        static constant integer BUFF_ID             = 'B00D' //the rawcode of the buff
        static constant integer INVIS_ID            = 'A00S' //the rawcode of the invis
        static constant integer DUMMY_ID            = 'h001' //the rawcode of the dummy
        static constant integer DUMMY_SPELL_ID      = 'A00T' //the rawcode of ability added to dummy to apply buff
        static constant player DUMMY_PLAYER         = Player(PLAYER_NEUTRAL_PASSIVE) //owner of dummy caster

        static constant string SHIMMER_SFX_MODEL    = ""

        static constant integer TRANSPARENCY        = 110

        static constant method Duration takes integer level returns real
            return 0. + 10.*level
        endmethod
        static constant method HeroDetectionRange takes integer level returns real
            return 500. + 0.*level // returns the range the spell will affect
        endmethod
        static constant method BackInvis takes integer level returns real
            return 1. + 0.*level
        endmethod
        static constant method ShimmerCooldown takes integer level returns real
            return 1. + 0.*level
        endmethod

        static method Targets takes unit target, unit caster returns boolean
            return UnitAlive(target)                                and/*
                */ IsUnitEnemy(target, GetOwningPlayer(caster))     and/*
                */ IsUnitType(target, UNIT_TYPE_HERO)               and/*
                */ IsUnitVisible(target, GetOwningPlayer(caster)) // is caster in enemy line of sight
        endmethod

        static method OnCasterShimmer takes unit caster returns nothing
            /*
            *   <What shimmer does>
            */
            call DestroyEffect(AddSpecialEffect(SHIMMER_SFX_MODEL, GetUnitX(caster), GetUnitY(caster)))
        endmethod

        static method OnCasterRevealStart takes unit caster returns nothing
            /*
            *    What happens when the caster is revealed
            *    For example, you could create additional special effects for the caster here
            */
        endmethod
        static method OnCasterRevealEnd takes unit caster returns nothing
            /*
            *    What happens when the caster becomes invisible again after being revealed
            */
        endmethod

    endmodule

//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
    native UnitAlive takes unit u returns boolean

    globals
        private group enumGroup = CreateGroup()
    endglobals
//===========================================================================

    private struct Camouflage extends array

        implement StealthConfiguration

        private static unit dummy
        private static unit enumUnit
        private static unit caster

        private player p
        private real duration
        private real range
        private real shimmerCooldown
        private real backInvis
        private integer level
        private boolean seen

        private method onSpellStart takes nothing returns thistype
            set this = GetUnitId(Spell.triggerUnit)

            set this.level              = Spell.level
            set this.p                  = Spell.triggerPlayer
            set this.range              = HeroDetectionRange(Spell.level)
            set this.duration           = Duration(Spell.level)
            set this.seen               = false
            set this.shimmerCooldown    = 0.
            set this.backInvis          = 0.

            if IssueTargetOrder(dummy, "acidbomb", Spell.triggerUnit) then // add buff
                call UnitAddAbility(Spell.triggerUnit, INVIS_ID) // add invis

                return this // add 'this' instance to periodic timer
            endif

            return 0 // [ERROR] issued dummy order failed, don't run periodic operations
        endmethod

        private method onSpellPeriodic takes nothing returns boolean
            local real i = 0
            local real cX = GetUnitX(caster)
            local real cY = GetUnitY(caster)
            local real dx
            local real dy
            local boolean seen = false

            set caster = GetUnitById(this)
            /*
            *    If there is at least 1 hero within detection range, mark the caster as 'seen'
            */
            call GroupEnumUnitsInRange(enumGroup, cX, cY, this.range, null)
            loop
                set enumUnit = FirstOfGroup(enumGroup)
                exitwhen enumUnit == null
                if Targets(enumUnit, caster) then
                    set seen = true
                endif
                call GroupRemoveUnit(enumGroup, enumUnit)
            endloop
            /*
            *    Keep track of the moment when the 'seen' status of the caster changes,
            *     i.e., the moment when the caster becomes seen, or unseen
            */
            if seen != this.seen then
                set this.seen = seen
                if seen then
                    /*
                    *    caster has just been seen by at least 1 enemy hero
                    */
                    call UnitRemoveAbility(caster, INVIS_ID)
                    call SetUnitVertexColor(caster, 255, 255, 255, TRANSPARENCY)
                else
                    /*
                    *    caster has just been unseen by ALL enemy heroes
                    */
                    call SetUnitVertexColor(caster, 255, 255, 255, 255)
                    call UnitAddAbility(caster, INVIS_ID)
                    set this.backInvis = BackInvis(this.level)

                    static if thistype.OnCasterRevealStart.exists then
                        call OnCasterRevealStart(caster)
                    endif
                endif
            endif
            /*
            *    update spell duration
            */
            set this.duration = this.duration - SPELL_PERIOD
            if this.duration < 0.00 then
                call UnitRemoveAbility(caster, BUFF_ID)
                return true // duration ended: end periodic operations
            endif
            /*
            *   update shimmer cooldown
            */
            if this.shimmerCooldown > 0. then
                set this.shimmerCooldown = this.shimmerCooldown - SPELL_PERIOD
            endif

            if this.backInvis > 0. then
                /*
                *    caster is revealed
                */
                set this.backInvis = this.backInvis - SPELL_PERIOD
                if this.backInvis <= 0. then
                    /*
                    *    reveal ended: add buff again
                    */
                    call IssueTargetOrder(dummy, "acidbomb", caster)

                    static if thistype.OnCasterRevealEnd.exists then
                        call OnCasterRevealEnd(caster)
                    endif

                    return false // do not end periodic operations
                endif
            endif
            /*
            *    if the caster no longer has the buff even when not revealed, end periodic operations
            */
            return GetUnitAbilityLevel(GetUnitById(this), BUFF_ID) == 0 and this.backInvis <= 0.
        endmethod

        private method onSpellEnd takes nothing returns nothing
            local unit caster = GetUnitById(this)

            call SetUnitVertexColor(caster, 255, 255, 255, 255)
            if GetUnitAbilityLevel(caster , INVIS_ID) > 0 then
                call UnitRemoveAbility(caster, INVIS_ID)
            endif

            // Wash leaks
            set caster = null
        endmethod

        implement SpellEvent // IMPORTANT so that the above methods automatically run when the correct spell is cast

        //===========================================================================

        private static method OnOtherSpellCast takes nothing returns boolean
            return GetSpellAbilityId() != SPELL_ABILITY_ID and UnitRemoveAbility(GetTriggerUnit(), BUFF_ID)
        endmethod

        private static method OnCasterItemUse takes nothing returns boolean
            return UnitRemoveAbility(GetTriggerUnit(), BUFF_ID)
        endmethod

        private static method OnCasterAttacks takes nothing returns boolean
            return UnitRemoveAbility(GetAttacker(), BUFF_ID)
        endmethod

        private static method OnCasterDamaged takes nothing returns boolean
            local thistype this

            if GetUnitAbilityLevel(udg_DamageEventTarget, BUFF_ID) > 0 then
                call UnitRemoveAbility(udg_DamageEventTarget, BUFF_ID)

                set this = GetUnitId(udg_DamageEventTarget)
                if this.shimmerCooldown <= 0. then
                    set this.shimmerCooldown = ShimmerCooldown(this.level)
                    call OnCasterShimmer(udg_DamageEventTarget)
                endif
            endif

            return false
        endmethod

        //===========================================================================
        private static method onInit takes nothing returns nothing
            // other events
            local trigger t = CreateTrigger()
            call TriggerRegisterVariableEvent(t, "udg_DamageEvent", EQUAL, 1.00)
            call TriggerAddCondition(t, Condition(function thistype.OnCasterDamaged))

            call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.OnOtherSpellCast)
            call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_USE_ITEM, function thistype.OnCasterItemUse)
            call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED, function thistype.OnCasterAttacks)

            // setting globals
            set dummy = CreateUnit(DUMMY_PLAYER, DUMMY_ID, 0., 0., 270.) // Only need to create dummy caster once
            call UnitAddAbility(dummy, DUMMY_SPELL_ID)

            set t = null

            // preloading effects
        endmethod

    endstruct


endlibrary


Yup either that, or you could make your spells NOT inside a library, just structs. This way, your spell struct can both call functions from my system as well as your cjass functions.


Yup
I ended up with this error
1617459360791.png
 
Level 7
Joined
Feb 9, 2021
Messages
301
It should be
JASS:
library Hammer requires BaseFunctions SpellFramework
//or
library Hammer uses BaseFunctions SpellFramework
With your current declaration jasshelper thinks 'uses' is one of your library requirements which it cannot find.
Thanks, it works now.

On Camouflage, it doesn't work: the unit does not become visible when near the enemy, the unit does not lose invisibility on the attack, the unit does not lose invisibility after the spell is finished. I also get an error if I use the spell twice.

Edit: this type of error.
1617463994322.png
 
Last edited:
Level 7
Joined
Feb 9, 2021
Messages
301
Can you send the test map? I'm too lazy to recreate a test map ;D
Sure )

Edit: Try using Hammer while it is casted. Your system gives an error, similar to invis. I thought your system is MUI, since it does dynamic indexing on the back-end?
 

Attachments

  • Mammon.w3x
    354.5 KB · Views: 33
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Sure )

Edit: Try using Hammer while it is casted. Your system gives an error, similar to invis. I thought your system is MUI, since it does dynamic indexing on the back-end?
Sorry for the late response, I'm into it now.

Is it better to do effects as a unit with effect's model or as a dummy unit with a special effect attached (using Dummy system)
It's better to use dummies + sfx since you can simply recycle the dummies while creating/destroying effects unlike using units directly where you don't have any other choice but to keep creating/removing units regularly. Remember that units require more resources in both memory and cpu compared to effects, which can easily become significant depending on how often you spam them. Not to mention the dummy + sfx method is more flexible since you can attach multiple sfx to one dummy.


EDIT: Weird, the caster becomes permanently invisible. Removing the buff does nothing too.
EDIT: It's because you set the duration of the passive invisibility to 0. Just set it to a really high number instead.

Fixed one attached below:
 

Attachments

  • Mammon.w3x
    353.8 KB · Views: 32
Last edited:
Level 7
Joined
Feb 9, 2021
Messages
301
Sorry for the late response, I'm into it now.


It's better to use dummies + sfx since you can simply recycle the dummies while creating/destroying effects unlike using units directly where you don't have any other choice but to keep creating/removing units regularly. Remember that units require more resources in both memory and cpu compared to effects, which can easily become significant depending on how often you spam them. Not to mention the dummy + sfx method is more flexible since you can attach multiple sfx to one dummy.


EDIT: Weird, the caster becomes permanently invisible. Removing the buff does nothing too.
EDIT: It's because you set the duration of the passive invisibility to 0. Just set it to a really high number instead.

Fixed one attached below:
I did some testing:
1. Caster does not become invisible upon cast
2. If caster cast an ability while being invisible, it can't cast invisibility anymore, and it also can't move for some time?
3. On receiving damage, it does not shimmer
"Camouflaged units will briefly shimmer whenever they take damage. A unit cannot shimmer more frequently than once per second, and shimmering does not make a unit targetable nor remove the stealth effect, but grants positional information to the enemy."
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Regarding the shimmer part, that's because I purposely made the shimmer actions to be empty as I don't know what's your desired implementation of shimmer. Instead I made a OnCasterShimmer() function on the configuration part where you can implement your shimmer actions, like briefly create an sfx for example. Or do you mean the OnCasterShimmer function itself is not running?

Regarding the other two, I'll check it out.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Regarding the shimmer part, that's because I purposely made the shimmer actions to be empty as I don't know what's your desired implementation of shimmer. Instead I made a OnCasterShimmer() function on the configuration part where you can implement your shimmer actions, like briefly create an sfx for example. Or do you mean the OnCasterShimmer function itself is not running?

Regarding the other two, I'll check it out.
I did this and had no response:
1617619744509.png
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I didn't realize ther're two unit indexers in the map, which is probably the reason why Damage Engine was broken. I removed the UnitDex so that only the GUI Unit Indexer remained and created a fake UnitDex library which wraps the GUI Unit Indexer. After that the shimmering worked. I also fixed issue no. 1 above. The only thing I can't confirm is no. 2.

Here is the latest map:
 

Attachments

  • Mammon.w3x
    349.8 KB · Views: 21
Level 7
Joined
Feb 9, 2021
Messages
301
I see, that's weird.
I have a question. I need to create variables with arrays inside 'public struct ... extends array' in order to have a unique value for each selected unit. For example, I need to do "set this.chilledStacks(tempUnitId) = 0" and "set this.frozen(tempUnitId)" = false. However, it gives me an error.

Btw, thank you for explaining to me about the effects.
JASS:
library Freeze /*


    [I]/uses /[/I]

    [I]/SpellFramework    /[/I]
    [I]/UnitDex           /[/I]

    */

    /*****************************************************************
    *                       SPELL CONFIGURATION                      *
    *****************************************************************/
    private module SpellConfiguration

        static constant integer SPELL_ABILITY_ID    = 'A00Y'
     
        static constant integer DUMMY_STORM_ID      = 'h006'
     
        static constant integer CASTER_SFX_ID       = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
     
        static constant integer SPELL_SFX_ID        = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"

        static constant player DUMMY_PLAYER         = Player(PLAYER_NEUTRAL_PASSIVE)

        static constant real RANGE                  = 500
     
        static constant integer STACKS_GAIN_PER_FPS = 1
     
        static constant integer STACKS_LOSE_PER_FPS = 2
     
        //                                      The attack type of inflicting damage
        static constant attacktype ATTACK_TYPE      = ATTACK_TYPE_MAGIC
        //                                      The damage type of inflicting damage
        static constant damagetype DAMAGE_TYPE      = DAMAGE_TYPE_MAGIC
        //                                      The weapon type of inflicting damage
        static constant weapontype WEAPON_TYPE      = WEAPON_TYPE_WHOKNOWS
     
     
        /*
        *   Any other event combination will make the spell non-channeling
        */
        static constant integer SPELL_EVENT_TYPE    = EVENT_SPELL_CHANNEL + EVENT_SPELL_ENDCAST

        static constant real SPELL_PERIOD           = FPS
     


     
    endmodule

    /*
    *   Note: For seamless manipulation of channeling ability, I suggest using
    *   an ability based on CHANNEL then be sure to set its 'Follow through
    *   time' field into a really high value such as <99999> and set the
    *   'Disable other abilities' to false.
    *   This allows you to configure the channeling (follow through time) duration
    *   in the code instead of using the Object Editor.
    */
 
    private constant function BaseDmg takes integer level, unit caster returns real
        return 10.*level + 0.1 * GetHeroStr(caster, true)
    endfunction
 
    private constant function FrozenDmg takes integer level returns real
        return 2.00 + 0.00*level
    endfunction
 
 
    private constant function FollowThroughTime takes integer level returns real
        return 10.00 + 0.00*level
    endfunction
 
    private constant function TimeToFreeze takes integer level returns real
        return 2.00 + 0.00*level
    endfunction
 
    private constant function FreezeDuration takes integer level returns real
        return 2.00 + 0.00*level
    endfunction

    private function TargetFilter takes unit target, unit caster returns boolean
        return GetWidgetLife(target) > 0.405 and IsUnitEnemy(target, GetOwningPlayer(caster)) and not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
    endfunction
    ...
    /*****************************************************************
    *                   END OF SPELL CONFIGURATION                   *
    *****************************************************************/

    /[I]========================= SPELL CODE =========================[/I]/

    private constant function IsSpellChanneling takes nothing returns boolean
        return SPELL_EVENT_TYPE == EVENT_SPELL_CHANNEL + EVENT_SPELL_ENDCAST
    endfunction
 
 
    globals
         private group enumGroup = CreateGroup()
 
    endglobals

    public struct Freeze extends array

        implement SpellConfiguration
        private static unit enumU
        private static unit caster
     
        unit dummy
        effect casterSfx
        effect spellSfx
        real followThroughTime
        real timeToFreeze
        real freezeDuration
        real baseDmg
        real frozenDmg
        integer unitId
        group chilled
        group chilledCopy
     
        integer requiredStacks
        integer AsMsReduction
        integer level
     
        unit array stormT
        integer array chilledStacks
        boolean array frozen
     
     
     
        real cX
        real cY


        private static thistype array channelingInstance

        private method onSpellStart takes nothing returns thistype
            local integer unitId
         


            if IsSpellChanneling() then
                set unitId = GetUnitId(Spell.triggerUnit)
                /*
                *   We do this to limit the channeling instance to only one per unit
                */
                if channelingInstance[unitId] == 0 then
                    set channelingInstance[unitId] = this
                    set this.unitId = unitId
                else
                    set this = channelingInstance[unitId]
                endif

                if Spell.eventType == EVENT_SPELL_ENDCAST then
                    /*
                    *   This block executes when a channeling spell ends, whether because
                    *   it was finished or was interrupted
                    *
                    *   Returning a negative value will remove the positive equivalent node
                    *   and call onSpellEnd() for that node if it exists in the list
                    */
                 
                    return -this
                endif
            endif
         
            set this.level              = Spell.level
            set this.cX                 = GetUnitX(Spell.triggerUnit)
            set this.cY                 = GetUnitY(Spell.triggerUnit)
            set this.dummy              = GetRecycledDummyAnyAngle(this.cX, this.cY, GetUnitFlyHeight(Spell.triggerUnit))
            set this.spellSfx           = AddSpecialEffectTarget(SPELL_SFX_ID, this.dummy, origin)
            set this.casterSfx          = AddSpecialEffectTarget(CASTER_SFX_ID, Spell.triggerUnit, origin)
            set this.followThroughTime  = FollowThroughTime(Spell.level)
            set this.timeToFreeze       = TimeToFreeze(Spell.level)
            set this.freezeDuration     = FreezeDuration(Spell.level)
            set this.chilled            = CreateGroup()
            set this.chilledCopy        = CreateGroup()
            set this.requiredStacks     = this.freezeDuration/FPS
            set this.AsMsReduction      = 1/this.requiredStacks
            set this.baseDmg            = BaseDmg(this.level, Spell.triggerUnit)
            set this.frozenDmg          = FrozenDmg(this.level, Spell.triggerUnit)
            return this
        endmethod

        /*
        *   Notice that onSpellPeriodic() stops executing when the caster stops channeling,
        *   either when the channeling is finished or is interrupted
        */
        private method onSpellPeriodic takes nothing returns boolean
            local integer tempUnitId

            if IsSpellChanneling() then
            /*
            *   Spell is channeling
            */
                set caster = GetUnitById(this)
                call SetUnitAnimationByIndex(caster, 3)
             
                //add every enemy in the group
                call GroupEnumUnitsInRange(enumGroup, this.cX, this.cY, RANGE, null)
                loop
                    set enumU = FirstOfGroup(enumGroup)
                    exitwhen enumU == null
                    if Targets(enumU, caster) and not IsUnitInGroup(enumU, this.chilled) then
                        call GroupAddUnit(this.chilled, enumU)
                        // add buff for chilled stacks
                     
                        // set the target's stun boolean = false
                        set tempUnitId = GetUnitId(enumU)
                        set this.chilledStacks(tempUnitId) = 0
                        set this.frozen(tempUnitId) = false
                     
                    endif
                    call GroupRemoveUnit(enumGroup, numU)
                 
                endloop
             
                // if a hero is in range -> add chilled stacks, if a hero is out of range -> remove chilled stacks;
                // if chilled stacks = enough = freeze; if no chilled stacks = remove from the group
                // Every chilled stack decrases ms and as proprtioanlly to required time to freeze the unit ->
                // 100% as /ms decrease = frozen
                set this.chilledCopy = CopyGroup(this.chilled)
                loop
                    set enumU = FirstOfGroup(this.chilledCopy)
                    exitwhen enumU == null
                    set tempUnitId = GetUnitId(enumU)
                    if GetDistanceBetweenCoordinates(this.cX, GetUnitX(enumU), this.cY, GetUnitY(enumU)) =< RANGE then
                        if this.chilledStacks(tempUnitId) => this.requiredStacks and not this.frozen(tempUnitId) then
                            //stun the unit with non stack type stun
                            //Remove chilled stacks
                            set this.chilledStacks(tempUnitId) = 0 // Should I zero them here?
                            set this.frozen(tempUnitId) = true
                        elseif this.chilledStacks(tempUnitId) < this.requiredStacks and not this.frozen(tempUnitId) then
                            //add more chilled stacks
                            set this.chilledStacks(tempUnitId) = this.chilledStacks(tempUnitId) + STACKS_GAIN_PER_FPS
                            //decrease the unit's as/ms based on the number of stacks
                        endif
                     
                        if not this.frozen(tempUnitId) then
                            //cause base damage
                            call UnitDamageTarget(caster, enumU, this.baseDmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE )
                         
                        else
                            //cause more dmg
                            call UnitDamageTarget(caster, enumU, this.frozenDmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE )
                        endif
                     
                    else
                        if this.chilledStacks(tempUnitId) <= 0 then
                            //remove chilled buff
                            //remove the unit from the gropup
                            set this.chilledStacks(tempUnitId) = 0
                            call GroupRemoveUnit(this.chilled, enumU)
                            //clear his variables -> is not necessary?
                            if this.frozen(tempUnitId) then
                                set this.frozen(tempUnitId) = false
                                //stun unit for required duraiton
                            endif
                        elseif this.chilledStacks(tempUnitId) > 0 and this.chilledStacks(tempUnitId) < this.requiredStacks then
                            //reduce chilled stacks on the target
                            set this.chilledStacks(tempUnitId) = this.chilledStacks(tempUnitId) - STACKS_LOSE_PER_FPS
                         
                            //adjust its speed
                        elseif this.frozen(tempUnitId) then
                            //set stun for the set duration
                            //remove chilled buff
                            //return his speed back to normal
                            call GroupRemoveUnit(this.chilled, enumU)
                     
                        endif
                 
                 
                    endif
                 
     
                 
                    call GroupRemoveUnit(this.chilledCopy, enumU)

                endloop
             
         
                set this.followThroughTime = this.followThroughTime - SPELL_PERIOD
                if not (this.followThroughTime > 0.00 and <ContinueChannelingCondition>) then
                /*
                *   If it is time to end the spell, instead of manually returning true, we
                *   order the caster to "stop" to trigger onSpellStart() which will in turn
                *   stop the periodic proccess after detecting that the event is EVENT_SPELL_ENDCAST
                */
                    call IssueImmediateOrderById(GetUnitById(this.unitId), STOP_ORDER_ID)
                endif

                return false
            endif
            /*
            *   Spell is non-channeling
            */
            return true // Return true to end the periodic process and proceed to onSpellEnd()
        endmethod

        private method onSpellEnd takes nothing returns nothing
            local boolean finished = IsSpellChanneling() and this.followThroughTime <= 0.00
            local boolean leftover = false
            if finished then
            /*
            *   The channel was finished
            */

            else
            /*
            *   The channel was interrupted
            */

            endif
            //reseting animination
            call SetUnitAnimationByIndex(caster, 2)
         
            set this.chilledCopy = CopyGroup(this.chilled)
            loop
                set enumU = FirstOfGroup(this.chilledCopy)
                exitwhen enumU == null
                if this.frozen(tempUnitId) then
                    //set x stun on the stunned units
                    //remove from the group
                    call GroupRemoveUnit(this.chilled, enumU)
                    //clear their variables -> do i need to?
                else
                    if not leftover then
                        set leftover = false
                    //on units who are outside of range continue to reduce stacks in the same temp
                        //start a separate timer, loop through the remaning units in the group, decrease stacks
                        //when the stacks = 0, stop the timer, clear the rest
                 
                    endif
                 
             
                endif
                call GroupRemoveUnit(this.chilledCopy, enumU)
            endloop
         
            //cleaning
            call DummyAddRecycleTimer(this.dummy, 0.1)
            call DestroyEffect(this.spellSfx)
            call DestroyEffect(this.casterSfx)

            //leaks
         
            //call DestroyGroup(this.chilledCopy)
         
            set this.spellSfx = null
            set this.casterSfx = null
            set this.dummy = null
         

            set channelingInstance[this.unitId] = 0
        endmethod
     
        private method ChilledLeftover takes nothing returns nothing
     
     
        endmethod

        implement SpellEvent
     
     

    endstruct


endlibrary
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Array instance members are not allowed for structs extending array. To circumvent this, you can use the Table library. You can then do:
JASS:
struct A extends array
    Table data

    method m takes nothing returns nothing
        set this.data = Table.create()

        set this.data[1] = 123
        set this.data.real[2] = 0.000
        set this.data.boolean[3] = true
        set this.data.timer[4] = CreateTimer()
        // etc

        call this.data.destroy() // destroy table after use
    endmethod
endstruct
 
Level 7
Joined
Feb 9, 2021
Messages
301
I didn't realize ther're two unit indexers in the map, which is probably the reason why Damage Engine was broken. I removed the UnitDex so that only the GUI Unit Indexer remained and created a fake UnitDex library which wraps the GUI Unit Indexer. After that the shimmering worked. I also fixed issue no. 1 above. The only thing I can't confirm is no. 2.

Here is the latest map:
Thanks, let me try it now. Will it work if I use UnitDex instead of GUI Unit Indexer? I forgot that Damage Engine has a Unit Indexer....
 
Level 7
Joined
Feb 9, 2021
Messages
301
Array instance members are not allowed for structs extending array. To circumvent this, you can use the Table library. You can then do:
JASS:
struct A extends array
    Table data

    method m takes nothing returns nothing
        set this.data = Table.create()

        set this.data[1] = 123
        set this.data.real[2] = 0.000
        set this.data.boolean[3] = true
        set this.data.timer[4] = CreateTimer()
        // etc

        call this.data.destroy() // destroy table after use
    endmethod
endstruct
I see, and then I can use unique unit IDs from UniDex for arrays in the table. Thank you.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Thanks, let me try it now. Will it work if I use UnitDex instead of GUI Unit Indexer? I forgot that Damage Engine has a Unit Indexer....
It's difficult to use UnitDex instead of GUI Unit Indexer since Damage Engine is heavily dependent upon the latter.

I see, and then I can use unique unit IDs from UniDex for arrays in the table. Thank you.
Yup
 
Level 7
Joined
Feb 9, 2021
Messages
301
It's difficult to use UnitDex instead of GUI Unit Indexer since Damage Engine is heavily dependent upon the latter.


Yup
That's a shame. I don't know how people use GUI... Will it make any difference?

EDIT: The seems to work fine. Thank you. I will go to examine the code.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Array instance members are not allowed for structs extending array. To circumvent this, you can use the Table library. You can then do:
JASS:
struct A extends array
    Table data

    method m takes nothing returns nothing
        set this.data = Table.create()

        set this.data[1] = 123
        set this.data.real[2] = 0.000
        set this.data.boolean[3] = true
        set this.data.timer[4] = CreateTimer()
        // etc

        call this.data.destroy() // destroy table after use
    endmethod
endstruct
I know you already made a lot for me and answered a lot of questions. This one is not in a rush, but I don't really understand how does it work on something more complex, and there are not many explanations in the Table system (I understand how hashtables work). I wanted to create a separate thread for this, but I will ask it here:

In the spell, I sent you before (Freeze), I have three variables that I want to have hashtable arrays(?). How do I go about implementing the table in there? In short, the spell adds stacks (one each period) to units while they are in the area and removes them (two each period) when they are out of the area. If the stacks = required stacks, the enemy freeze. Therefore, I need to track certain variables for each unit in the group (stacks and whether it is frozen).
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I know you already made a lot for me and answered a lot of questions. This one is not in a rush, but I don't really understand how does it work on something more complex, and there are not many explanations in the Table system (I understand how hashtables work). I wanted to create a separate thread for this, but I will ask it here:
Its usage and API is practically the same with the instance array members which I assume you already are familiar of using. So let me just compare them side by side:
Lua:
struct A[...]
    boolean array chilledStack[8192] // declare

    method chillUnit takes unit u returns nothing
        local integer id = GetUnitId(u)
        set this.chilledStack[id] = this.chilledStack[id] + 1
        //      ^ set value               ^ get value
    endmethod
endstruct

// Using table
struct A extends array
    Table chilledStack // declare

    method chillUnit takes unit u returns nothing
        local integer id = GetUnitId(u)
        set this.chilledStack.boolean[id] = this.chilledStack.boolean[id] + 1
        //      ^ set value                        ^ get value
    endmethod
endstruct
As you can see, they are totally identical in setting and getting except that in table, you need to specify the datatype (in this case 'boolean'). Another difference is that table needs to be created first before it can be usable, so usually one creates it in the constructor of the struct and destroys it in the destructor of the struct.

I suggest reading the header of the NewTable library if you want to take full advantage of it. Its documentation is quite intensive and documents all of its API.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Its usage and API is practically the same with the instance array members which I assume you already are familiar of using. So let me just compare them side by side:
Lua:
struct A[...]
    boolean array chilledStack[8192] // declare

    method chillUnit takes unit u returns nothing
        local integer id = GetUnitId(u)
        set this.chilledStack[id] = this.chilledStack[id] + 1
        //      ^ set value               ^ get value
    endmethod
endstruct

// Using table
struct A extends array
    Table chilledStack // declare

    method chillUnit takes unit u returns nothing
        local integer id = GetUnitId(u)
        set this.chilledStack.boolean[id] = this.chilledStack.boolean[id] + 1
        //      ^ set value                        ^ get value
    endmethod
endstruct
As you can see, they are totally identical in setting and getting except that in table, you need to specify the datatype (in this case 'boolean'). Another difference is that table needs to be created first before it can be usable, so usually one creates it in the constructor of the struct and destroys it in the destructor of the struct.

I suggest reading the header of the NewTable library if you want to take full advantage of it. Its documentation is quite intensive and documents all of its API.
I got it. Thank you.
 
Level 7
Joined
Feb 9, 2021
Messages
301
I didn't realize ther're two unit indexers in the map, which is probably the reason why Damage Engine was broken. I removed the UnitDex so that only the GUI Unit Indexer remained and created a fake UnitDex library which wraps the GUI Unit Indexer. After that the shimmering worked. I also fixed issue no. 1 above. The only thing I can't confirm is no. 2.

Here is the latest map:
I noticed a problem. The caster does not lose invisibility instantly if he cast the spell type channelling. Not sure how to fix it.

Another one: unit attacks works only once. Also, because you used native call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED, function thistype.OnCasterAttacks) instead of DDS, it deploys even when the attack is cancelled. I can fix the second part, but not the first.

Here is the map, but you have to delete Memory Hack folder, StunSystem and all CC Buffs (there are three) for it to work if you are not on 1.26. I changed the spell a little bit, it uses the buff system now. I also added UniDex API for GUI Unit Indexer.
 

Attachments

  • Mammon.w3x
    787.2 KB · Views: 31
Last edited:
Status
Not open for further replies.
Top