Jaina Spellpack v1.1.0

This bundle is marked as pending. It has not been reviewed by a staff member yet.
jaina-spellpack-title-two-png.354520

"Is talking all you demons do?" - The Frost Mage

jaina-spellpack-scene-one-gif.355514

jaina-spellpack-scene-two-gif.355513

jaina-spellpack-scene-three-gif.355512
It contains: 5 hero abilities (shown), 4 mastered abilities, 4 unit abilities (shown), 1 standard ability.
frostbolt-description-png.356191
frostbite-description-png.355503
frost-elemental-description-png.356192
elemental-diverge-description-png.355505
chilling-suicide-description-png.355506
chilling-touch-description-png.355507
freezing-bolt-description-png.356193
blizzard-description-png.355508
ice-storm-description-png.355509

- Credits -
kangyun
Mythic
Mc !
dhguardianes
AGD
hemmedo
Flux
TriggerHappy
Bribe
Nestharus
MyPad
Vexorian
chopinski
(If you don't see your name on this credit list, just reply on this resource)

- Notes -
How To Import A Spell/System
Contents

JAINA SPELLPACK (Map)

Level 19
Joined
Aug 13, 2013
Messages
1,680
Elsa is here x)

No more fancy GIFs for each ability demo
(They consume a lot of cellular data :/)

Just download the map and test or play in action!
(If you ever wonder its file size: 75% custom loading screen, 20% custom models, 5% code and map things)

The systems I'm using are as usual as before,
expect that they're constantly present on my upcoming resources
So having these already in your map is a convenience.

I've also implemented a PauseLibrary,
so that you can just configure once on its global configuration.
In the making of this spellpack, I've noticed a lot of circumstances
where the pause/unpause method breaks, (only in rare cases)
As I can't tolerate such thing, this is made to guarantee a full-safety of its functionality.
(Just read its documentation)

After this, I'm also developing a significant update to Archimonde as well.

Eventually, I'll be moving on to the next spotlight: Kael'thas.
The theme is fel + fire. His concept was already established by hemmedo.
(I'll start to code him once I get back)

That's all for now. Enjoy!
 
Level 19
Joined
Aug 13, 2013
Messages
1,680
Casting Frostbolt crashes the game for me. Pretty cool spells, good job!
ah me too! But otherwise really cool spells. The glacial storm was amazing!
Frostbolt works fine for me. Pretty neat stuff here.
That's strange and alarming. I'm always ensuring that there would be no bugs or crashes... Maybe it's related to a patch :/
However, I can guarantee that this is 100% safe and tested on versions that still relates to 1.30.4. (I'm using this editor version)
I'll take a look on it. Thanks for the feedback =)
 
Level 15
Joined
May 16, 2012
Messages
549
That's strange and alarming. I'm always ensuring that there would be no bugs or crashes... Maybe it's related to a patch :/
However, I can guarantee that this is 100% safe and tested on versions that still relates to 1.30.4. (I'm using this editor version)
I'll take a look on it. Thanks for the feedback =)

Oh that's probably it, I'm on latest patch.
 
Level 19
Joined
Aug 13, 2013
Messages
1,680
Oh that's probably it, I'm on latest patch.
I can't solve this alone as I don't have reforged versions yet.
Perhaps you could test this code?
JASS:
library Frostbolt /*


    */uses /*

    */Buff               /* https://www.hiveworkshop.com/threads/288640/
    */UnitDex            /* https://www.hiveworkshop.com/threads/248209/
    */SpellEvent         /* https://www.hiveworkshop.com/threads/301895/
    */SpellCloner        /* https://www.hiveworkshop.com/threads/292751/
    */DummyRecycler      /* https://www.hiveworkshop.com/threads/277659/
    */ZLibrary           /* https://www.hiveworkshop.com/threads/237821/

    */

    /**************************************************
    *                GLOBAL CONFIGURATION
    ***************************************************/
    globals

        private constant real PERIODIC_INTERVAL = 0.03125

        private constant real MODEL_DEATH_TIME  = 2.5

        /*
        *  Note that when importing, make sure the two objects (Buff Creator and the Buff)
        *  In object editor have the same rawcode except for their first characters
        *  You can easily customize rawcodes using an object merger script or Grimoire extension
        *  This feature makes the ability to work like a debuff (with Dispel library)
        *  The slow-stats are configurable inside the Object Editor, on the buff creator itself
        */
        //                             Chilled Ability Buff Creator ID
        private constant integer BUFF_CREATOR_ID = 'ACHL'
        //                              The model of the Chilled buff
        private constant string  BUFF_MODEL = "war3mapImported\\ChilledColdBuff.mdx"
        private constant string  BUFF_MODEL_ORIGIN = "chest"

    endglobals

        // Filtration of the units that will be targeted
    private function Filtration takes unit picked, player owner returns boolean
        return IsUnitEnemy(picked,owner)                    and /*
            */ not IsUnitType(picked,UNIT_TYPE_STRUCTURE)   and /*
            */ not IsUnitType(picked,UNIT_TYPE_MAGIC_IMMUNE) and /*
            */ UnitAlive(picked)
    endfunction
    /**************************************************
    *           END OF GLOBAL CONFIGURATION
    ***************************************************/

    /*============================= CORE CODE =============================*/

    native UnitAlive takes unit id returns boolean

    globals
        private constant integer DEFAULT_PITCH = 90
        private integer array index
        private group Group = CreateGroup()
    endglobals

    public struct ChilledBuff extends Buff
        public effect model
        readonly static string buffModel = BUFF_MODEL
        readonly static string buffModelOrigin = BUFF_MODEL_ORIGIN
        private static constant integer RAWCODE = BUFF_CREATOR_ID
        private static constant integer DISPEL_TYPE = BUFF_NEGATIVE
        private static constant integer STACK_TYPE = BUFF_STACK_NONE
        method onRemove takes nothing returns nothing
            call DestroyEffect(.model)
            set .model = null
        endmethod
        method onApply takes nothing returns nothing
        endmethod
        implement BuffApply
    endstruct

    struct Frostbolt extends array

        implement SpellClonerHeader

        real size
        real height
        real distance
        real range

        real radius
        real velocity
        real damage
        real duration

        integer count

        string projectileModel
        string projectileModelOrigin
        string hitModel
        string hitModelOrigin

        boolean reduction

        attacktype attackType
        damagetype damageType
        weapontype weaponType

        readonly unit caster
        readonly unit target
        readonly unit projectile

        readonly real sizeReduction
        readonly real damageReduction

        private effect model

        readonly static constant real SPELL_PERIOD = PERIODIC_INTERVAL

        private method onSpellStart takes nothing returns thistype
            local real x
            local real y
            local real angle
            call this.initSpellConfiguration( GetEventSpellAbilityId() )
            if .count > 0 then
                set .caster = GetEventSpellCaster()
                set .target = GetEventSpellTargetUnit()

                set x = GetUnitX(.caster)
                set y = GetUnitY(.caster)
                set angle = Atan2(GetEventSpellTargetY()-y,GetEventSpellTargetX()-x)
                set x = x + .distance*Cos(angle)
                set y = y + .distance*Sin(angle)

                set .projectile = GetRecycledDummy(x,y,.height,angle*bj_RADTODEG)
                call SetUnitScale(.projectile,.size,0,0)
                set .model = AddSpecialEffectTarget(.projectileModel,.projectile,.projectileModelOrigin)
                if .reduction then
                    set .sizeReduction   = .size/count
                    set .damageReduction = .damage/count
                endif
                return this
            else
                return 0
            endif
        endmethod

        private method onSpellPeriodic takes nothing returns boolean
            local unit picked
            local ChilledBuff chilled

            local real x1  = GetUnitX(.projectile)
            local real x2  = GetUnitX(.target)
            local real y1  = GetUnitY(.projectile)
            local real y2  = GetUnitY(.target)
            local real z1  = GetUnitFlyHeight(.projectile) + GetTerrainZ(x1,y1)
            local real z2  = GetUnitFlyHeight(.target) + .height + GetTerrainZ(x2,y2)
            local real ang = Atan2(y2-y1,x2-x1)
            local real dis = SquareRoot( (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1) )
            //local real phi = Atan2(z2-z1,dis)
            local integer count = 0

            //call SetUnitX(.projectile,x1 + .velocity*Cos(ang)*Cos(phi) )
            //call SetUnitY(.projectile,y1 + .velocity*Sin(ang)*Cos(phi) )
            call SetUnitX(.projectile,x1 + .velocity*Cos(ang) )
            call SetUnitY(.projectile,y1 + .velocity*Sin(ang) )
            //call SetUnitFlyHeight(.projectile,z1 + .velocity*Sin(phi)-GetTerrainZ(x1,y1),0)
            //call SetUnitAnimationByIndex(.projectile,R2I(phi*bj_RADTODEG+0.5)+DEFAULT_PITCH)
            call SetUnitFacing(.projectile,ang*bj_RADTODEG)

            set dis = (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)+(z2-z1)*(z2-z1)
            if (dis <= .range*.range) then
                call DestroyEffect( AddSpecialEffectTarget(.hitModel,.target,.hitModelOrigin) )
                call UnitDamageTarget(.caster,.target,.damage,false,false,.attackType,.damageType,.weaponType)
                set chilled = ChilledBuff.add(.caster,.target)
                set chilled.duration = .duration
                if (chilled.model == null) then
                    set chilled.model = AddSpecialEffectTarget(ChilledBuff.buffModel,.target,ChilledBuff.buffModelOrigin)
                endif
                set .count = .count - 1
                if  .count > 0 then
                    if .reduction then
                        set .size   = .size   - .sizeReduction
                        set .damage = .damage - .damageReduction
                    endif
                    call GroupEnumUnitsInRange(Group,x1,y1,.radius,null)
                    loop
                        set picked = FirstOfGroup(Group)
                        exitwhen picked == null
                        if Filtration( picked,GetOwningPlayer(.caster) ) and picked != .target then
                            set count = count + 1
                            set index[count] = GetUnitUserData(picked)
                        endif
                        call GroupRemoveUnit(Group,picked)
                    endloop
                    if count > 0 then
                        set z1 = GetUnitFlyHeight(.projectile)

                        call DestroyEffect(.model)
                        call DummyAddRecycleTimer(.projectile,MODEL_DEATH_TIME)
                        set .target = GetUnitById(index[GetRandomInt(1,count)])
                        set .model     = null
                        set .projectile = null

                        set .projectile = GetRecycledDummy(x1,y1,z1,Atan2(GetUnitY(.target)-y1,GetUnitX(.target)-x1)*bj_RADTODEG)
                        call SetUnitScale(.projectile,.size,0,0)
                        set .model = AddSpecialEffectTarget(.projectileModel,.projectile,.projectileModelOrigin)
                    endif
                else
                    return false
                endif
            endif
            return UnitAlive(.target)
        endmethod

        private method onSpellEnd takes nothing returns nothing
            if .model != null and .projectile != null then
                call DestroyEffect(.model)
                call DummyAddRecycleTimer(.projectile,MODEL_DEATH_TIME)
                set .model     = null
                set .projectile = null
            endif
            set .caster = null
            set .target = null
        endmethod

        implement SpellEvent
        implement SpellClonerFooter

    endstruct

    module FrostboltConfigurationCloner

        private static method configHandler takes nothing returns nothing
            local Frostbolt clone           = Frostbolt.configuredInstance
            // Configuration by constants
            set clone.size                 = PROJECTILE_MODEL_SIZE
            set clone.height               = PROJECTILE_MODEL_HEIGHT
            set clone.distance             = PROJECTILE_MODEL_DISTANCE
            set clone.range                = PROJECTILE_DETECT_RANGE
            set clone.projectileModel      = PROJECTILE_MODEL
            set clone.projectileModelOrigin = PROJECTILE_MODEL_ORIGIN
            set clone.hitModel             = PROJECTILE_HIT_MODEL
            set clone.hitModelOrigin       = PROJECTILE_HIT_MODEL_ORIGIN
            set clone.attackType           = ATTACK_TYPE
            set clone.damageType           = DAMAGE_TYPE
            set clone.weaponType           = WEAPON_TYPE
            // Configuration with levels
            set clone.count                = count(GetEventSpellLevel())
            set clone.radius               = radius(GetEventSpellLevel())
            set clone.velocity             = velocity(GetEventSpellLevel())
            set clone.damage               = damage(GetEventSpellLevel())
            set clone.duration             = duration(GetEventSpellLevel())
            set clone.reduction            = reduction(GetEventSpellLevel())
        endmethod

        private static method onInit takes nothing returns nothing
            call Frostbolt.create(thistype.typeid,ABILITY_ID,SPELL_EVENT_TYPE,function thistype.configHandler)
        endmethod

    endmodule

endlibrary

I've commented out some of the suspected code, especially the phi calculation...
 
Level 15
Joined
May 16, 2012
Messages
549
I can't solve this alone as I don't have reforged versions yet.
Perhaps you could test this code?
JASS:
library Frostbolt /*


    */uses /*

    */Buff               /* https://www.hiveworkshop.com/threads/288640/
    */UnitDex            /* https://www.hiveworkshop.com/threads/248209/
    */SpellEvent         /* https://www.hiveworkshop.com/threads/301895/
    */SpellCloner        /* https://www.hiveworkshop.com/threads/292751/
    */DummyRecycler      /* https://www.hiveworkshop.com/threads/277659/
    */ZLibrary           /* https://www.hiveworkshop.com/threads/237821/

    */

    /**************************************************
    *                GLOBAL CONFIGURATION
    ***************************************************/
    globals

        private constant real PERIODIC_INTERVAL = 0.03125

        private constant real MODEL_DEATH_TIME  = 2.5

        /*
        *  Note that when importing, make sure the two objects (Buff Creator and the Buff)
        *  In object editor have the same rawcode except for their first characters
        *  You can easily customize rawcodes using an object merger script or Grimoire extension
        *  This feature makes the ability to work like a debuff (with Dispel library)
        *  The slow-stats are configurable inside the Object Editor, on the buff creator itself
        */
        //                             Chilled Ability Buff Creator ID
        private constant integer BUFF_CREATOR_ID = 'ACHL'
        //                              The model of the Chilled buff
        private constant string  BUFF_MODEL = "war3mapImported\\ChilledColdBuff.mdx"
        private constant string  BUFF_MODEL_ORIGIN = "chest"

    endglobals

        // Filtration of the units that will be targeted
    private function Filtration takes unit picked, player owner returns boolean
        return IsUnitEnemy(picked,owner)                    and /*
            */ not IsUnitType(picked,UNIT_TYPE_STRUCTURE)   and /*
            */ not IsUnitType(picked,UNIT_TYPE_MAGIC_IMMUNE) and /*
            */ UnitAlive(picked)
    endfunction
    /**************************************************
    *           END OF GLOBAL CONFIGURATION
    ***************************************************/

    /*============================= CORE CODE =============================*/

    native UnitAlive takes unit id returns boolean

    globals
        private constant integer DEFAULT_PITCH = 90
        private integer array index
        private group Group = CreateGroup()
    endglobals

    public struct ChilledBuff extends Buff
        public effect model
        readonly static string buffModel = BUFF_MODEL
        readonly static string buffModelOrigin = BUFF_MODEL_ORIGIN
        private static constant integer RAWCODE = BUFF_CREATOR_ID
        private static constant integer DISPEL_TYPE = BUFF_NEGATIVE
        private static constant integer STACK_TYPE = BUFF_STACK_NONE
        method onRemove takes nothing returns nothing
            call DestroyEffect(.model)
            set .model = null
        endmethod
        method onApply takes nothing returns nothing
        endmethod
        implement BuffApply
    endstruct

    struct Frostbolt extends array

        implement SpellClonerHeader

        real size
        real height
        real distance
        real range

        real radius
        real velocity
        real damage
        real duration

        integer count

        string projectileModel
        string projectileModelOrigin
        string hitModel
        string hitModelOrigin

        boolean reduction

        attacktype attackType
        damagetype damageType
        weapontype weaponType

        readonly unit caster
        readonly unit target
        readonly unit projectile

        readonly real sizeReduction
        readonly real damageReduction

        private effect model

        readonly static constant real SPELL_PERIOD = PERIODIC_INTERVAL

        private method onSpellStart takes nothing returns thistype
            local real x
            local real y
            local real angle
            call this.initSpellConfiguration( GetEventSpellAbilityId() )
            if .count > 0 then
                set .caster = GetEventSpellCaster()
                set .target = GetEventSpellTargetUnit()

                set x = GetUnitX(.caster)
                set y = GetUnitY(.caster)
                set angle = Atan2(GetEventSpellTargetY()-y,GetEventSpellTargetX()-x)
                set x = x + .distance*Cos(angle)
                set y = y + .distance*Sin(angle)

                set .projectile = GetRecycledDummy(x,y,.height,angle*bj_RADTODEG)
                call SetUnitScale(.projectile,.size,0,0)
                set .model = AddSpecialEffectTarget(.projectileModel,.projectile,.projectileModelOrigin)
                if .reduction then
                    set .sizeReduction   = .size/count
                    set .damageReduction = .damage/count
                endif
                return this
            else
                return 0
            endif
        endmethod

        private method onSpellPeriodic takes nothing returns boolean
            local unit picked
            local ChilledBuff chilled

            local real x1  = GetUnitX(.projectile)
            local real x2  = GetUnitX(.target)
            local real y1  = GetUnitY(.projectile)
            local real y2  = GetUnitY(.target)
            local real z1  = GetUnitFlyHeight(.projectile) + GetTerrainZ(x1,y1)
            local real z2  = GetUnitFlyHeight(.target) + .height + GetTerrainZ(x2,y2)
            local real ang = Atan2(y2-y1,x2-x1)
            local real dis = SquareRoot( (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1) )
            //local real phi = Atan2(z2-z1,dis)
            local integer count = 0

            //call SetUnitX(.projectile,x1 + .velocity*Cos(ang)*Cos(phi) )
            //call SetUnitY(.projectile,y1 + .velocity*Sin(ang)*Cos(phi) )
            call SetUnitX(.projectile,x1 + .velocity*Cos(ang) )
            call SetUnitY(.projectile,y1 + .velocity*Sin(ang) )
            //call SetUnitFlyHeight(.projectile,z1 + .velocity*Sin(phi)-GetTerrainZ(x1,y1),0)
            //call SetUnitAnimationByIndex(.projectile,R2I(phi*bj_RADTODEG+0.5)+DEFAULT_PITCH)
            call SetUnitFacing(.projectile,ang*bj_RADTODEG)

            set dis = (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)+(z2-z1)*(z2-z1)
            if (dis <= .range*.range) then
                call DestroyEffect( AddSpecialEffectTarget(.hitModel,.target,.hitModelOrigin) )
                call UnitDamageTarget(.caster,.target,.damage,false,false,.attackType,.damageType,.weaponType)
                set chilled = ChilledBuff.add(.caster,.target)
                set chilled.duration = .duration
                if (chilled.model == null) then
                    set chilled.model = AddSpecialEffectTarget(ChilledBuff.buffModel,.target,ChilledBuff.buffModelOrigin)
                endif
                set .count = .count - 1
                if  .count > 0 then
                    if .reduction then
                        set .size   = .size   - .sizeReduction
                        set .damage = .damage - .damageReduction
                    endif
                    call GroupEnumUnitsInRange(Group,x1,y1,.radius,null)
                    loop
                        set picked = FirstOfGroup(Group)
                        exitwhen picked == null
                        if Filtration( picked,GetOwningPlayer(.caster) ) and picked != .target then
                            set count = count + 1
                            set index[count] = GetUnitUserData(picked)
                        endif
                        call GroupRemoveUnit(Group,picked)
                    endloop
                    if count > 0 then
                        set z1 = GetUnitFlyHeight(.projectile)

                        call DestroyEffect(.model)
                        call DummyAddRecycleTimer(.projectile,MODEL_DEATH_TIME)
                        set .target = GetUnitById(index[GetRandomInt(1,count)])
                        set .model     = null
                        set .projectile = null

                        set .projectile = GetRecycledDummy(x1,y1,z1,Atan2(GetUnitY(.target)-y1,GetUnitX(.target)-x1)*bj_RADTODEG)
                        call SetUnitScale(.projectile,.size,0,0)
                        set .model = AddSpecialEffectTarget(.projectileModel,.projectile,.projectileModelOrigin)
                    endif
                else
                    return false
                endif
            endif
            return UnitAlive(.target)
        endmethod

        private method onSpellEnd takes nothing returns nothing
            if .model != null and .projectile != null then
                call DestroyEffect(.model)
                call DummyAddRecycleTimer(.projectile,MODEL_DEATH_TIME)
                set .model     = null
                set .projectile = null
            endif
            set .caster = null
            set .target = null
        endmethod

        implement SpellEvent
        implement SpellClonerFooter

    endstruct

    module FrostboltConfigurationCloner

        private static method configHandler takes nothing returns nothing
            local Frostbolt clone           = Frostbolt.configuredInstance
            // Configuration by constants
            set clone.size                 = PROJECTILE_MODEL_SIZE
            set clone.height               = PROJECTILE_MODEL_HEIGHT
            set clone.distance             = PROJECTILE_MODEL_DISTANCE
            set clone.range                = PROJECTILE_DETECT_RANGE
            set clone.projectileModel      = PROJECTILE_MODEL
            set clone.projectileModelOrigin = PROJECTILE_MODEL_ORIGIN
            set clone.hitModel             = PROJECTILE_HIT_MODEL
            set clone.hitModelOrigin       = PROJECTILE_HIT_MODEL_ORIGIN
            set clone.attackType           = ATTACK_TYPE
            set clone.damageType           = DAMAGE_TYPE
            set clone.weaponType           = WEAPON_TYPE
            // Configuration with levels
            set clone.count                = count(GetEventSpellLevel())
            set clone.radius               = radius(GetEventSpellLevel())
            set clone.velocity             = velocity(GetEventSpellLevel())
            set clone.damage               = damage(GetEventSpellLevel())
            set clone.duration             = duration(GetEventSpellLevel())
            set clone.reduction            = reduction(GetEventSpellLevel())
        endmethod

        private static method onInit takes nothing returns nothing
            call Frostbolt.create(thistype.typeid,ABILITY_ID,SPELL_EVENT_TYPE,function thistype.configHandler)
        endmethod

    endmodule

endlibrary

I've commented out some of the suspected code, especially the phi calculation...

tested it and the problem persist. I was trying to comment blocks of code to try finding or narrow down where the problem is, and it seems that the crash is caused probably by one of the libraries used to create the system rather than this code specifically, because neither onSpellStart or the periodic method execute.
 
Level 19
Joined
Aug 13, 2013
Messages
1,680
Have you tried disabling the Multi Frostbolt code? (inside the modified abilities folder)
It's very strange that only frostbolt encounters this. There's nothing special about it except for its modified version.
The libraries used are working properly from other abilities. My last suspicious is that Multi Frostbolt. (its co-existing)

I hope you can test one more time by disabling Multi Frostbolt?
Otherwise, If it persists then sadly I can't do anything about it,
I'll just make a note that this frostbolt ability can crash on latest patch. (For unknown reason)

If it didn't crash by disabling it then I highly doubt on a system which can be fixed somehow.
Thanks for a handy test and report ;)
 
Level 15
Joined
May 16, 2012
Messages
549
Have you tried disabling the Multi Frostbolt code? (inside the modified abilities folder)
It's very strange that only frostbolt encounters this. There's nothing special about it except for its modified version.
The libraries used are working properly from other abilities. My last suspicious is that Multi Frostbolt. (its co-existing)

I hope you can test one more time by disabling Multi Frostbolt?
Otherwise, If it persists then sadly I can't do anything about it,
I'll just make a note that this frostbolt ability can crash on latest patch. (For unknown reason)

If it didn't crash by disabling it then I highly doubt on a system which can be fixed somehow.
Thanks for a handy test and report ;)

It was the Multi Frostbolt!
 

RCF

RCF

Level 1
Joined
Sep 17, 2018
Messages
5
tested it and the problem persist. I was trying to comment blocks of code to try finding or narrow down where the problem is, and it seems that the crash is caused probably by one of the libraries used to create the system rather than this code specifically, because neither onSpellStart or the periodic method execute.
It works !
 

RCF

RCF

Level 1
Joined
Sep 17, 2018
Messages
5
Why when i summon elemental frost, the elemental stay in pause or issu stop order in periodic interval ? Thank you for response ! Very good job
 
Level 19
Joined
Aug 13, 2013
Messages
1,680
Thanks for clarifying!

I'm glad to conclude that there's nothing wrong about the actual Frostbolt. (but only on its modified one: Multi Frostbolt)
I'll just include the crash fix for latest patches on its v1.1.0 update after Archimonde's v1.1.0 update.
This is system & patch related problem though... (So it may take some time to identify the root cause of this)
 

RCF

RCF

Level 1
Joined
Sep 17, 2018
Messages
5
1.32 , yes is totaly stop and if i disable pause channeling elemental's can't move, stop with each action, i have optimise pauseLibrary ( report ID ) i do not understand
 
Level 19
Joined
Aug 13, 2013
Messages
1,680
@JAKEZINC will add that 1.31 patch is stable.
Yeah, I can't even encounter some bugs also. It looks like it's plaguing the versions 1.32+ :/

EDIT:

I'll make the spells that requiring each other to 'work independently' on the next update.
So this means, even if there's a single ability that bugs on latest patches, you can still import
the stable ones. This can be achieved by making them an optional prerequisites.

As of today, I don't mod on reforged editor. (although I care for my resource's compatibility)
 
Last edited:
Level 15
Joined
May 16, 2012
Messages
549
In Archimonde Spellpack, there is also a spell that uses a similar code, called Doomfire Judgement. I also don't have Reforged so I can't test this. Can you see if Doomfire Judgement also crashes in Reforged? If so, there is some problem in one of the system used :|.

Ok, I'll check it out.

@AGD it does indeed crash the game. The normal version of the ability doe not, but it's upgrade crash on cast.
 
Last edited:
  • Like
Reactions: AGD
Level 19
Joined
Aug 13, 2013
Messages
1,680
1.32 , yes is totaly stop and if i disable pause channeling elemental's can't move, stop with each action, i have optimise pauseLibrary ( report ID ) i do not understand
I didn't noticed this post yesterday, but only pops up on my notification alert recently.
So I hope for another reply regarding this... (if you would mind) :)

You shouldn't touch PauseLibrary inside the demo map as it's already configured to match the rawcodes (ID).
However, a mismatch on its pause ability object id configuration would only lead to DISARM or DISABLE method which is not the case for this.

As you can see on the preview GIFs, elementals are performing well in-game.

Based on your report,
It looks like a periodic operation that continuously orders the elemental to stop? (Or something?)
If PauseLibrary is to blame then Frostbite ability should also encounter this kind of problem.

So you should observe how the 'frozen' units react when they're paused also.
(as it uses the same pause for elementals)

However, we can properly debug this problem.
Just replace the code of Frost Elemental with this one:
JASS:
library FrostElemental /*


    */uses /*

    */Alloc              /* library is used to make this work on instances efficiently
    */SpellEvent         /* https://www.hiveworkshop.com/threads/301895/
    */SpellCloner        /* https://www.hiveworkshop.com/threads/292751/
    */Frostbolt          /* library is used for its ChilledBuffStruct
    */Frostbite          /* library is used for its FrozenBuffStruct
    */PauseLibrary       /* library is used for pausing/unpausing a unit safely

    */

    /**************************************************
    *               GLOBAL CONFIGURATION
    ***************************************************/

    globals

        private constant real PERIODIC_INTERVAL = 0.05

    endglobals

        // Filtration of the units that will be damaged, chilled, or frozen
    private function Filtration takes unit picked, player owner returns boolean
        return IsUnitEnemy(picked,owner)                    and /*
            */ not IsUnitType(picked,UNIT_TYPE_FLYING)      and /*
            */ not IsUnitType(picked,UNIT_TYPE_STRUCTURE)   and /*
            */ not IsUnitType(picked,UNIT_TYPE_MAGIC_IMMUNE) and /*
            */ UnitAlive(picked)
    endfunction

    /**************************************************
    *            END OF GLOBAL CONFIGURATION
    ***************************************************/

    /*============================= CORE CODE =============================*/

    native UnitAlive takes unit id returns boolean

    globals
        private constant real TAU = 2.*bj_PI
        private constant integer EXPIRATION_TYPE  = 'BTLF'
        private constant string DEFAULT_ANIMATION = "stand"
        private group Group = CreateGroup()
    endglobals

    struct FrostElemental extends array

        implement Alloc
        implement SpellClonerHeader

        integer id
        integer count

        real delay
        real distance
        real duration
        real radius
        real damage

        real chilledDuration
        real frozenDuration
        real frozenHeroDuration

        string birthAnimation
        string birthModel
        string birthModelOrigin

        string pickedModel
        string pickedModelOrigin

        attacktype attackType
        damagetype damageType
        weapontype weaponType

        readonly unit summoner
        readonly unit summoned

        readonly real remainingDelay
        readonly real remainingDuration

        private string unpauseString
        readonly static constant real SPELL_PERIOD = PERIODIC_INTERVAL

        private method onSpellStart takes nothing returns thistype
            local real x
            local real y
            local real angle
            local real delta
            local real facing
            local integer count
            local thistype node
            local integer configID = this.initSpellConfiguration( GetEventSpellAbilityId() )
            call BJDebugMsg(GetObjectName(GetEventSpellAbilityId()) + " is casted with an instance of: " + I2S(this) )
            set facing = GetUnitFacing(GetEventSpellCaster())*bj_DEGTORAD
            set angle  = facing
            set count  = .count
            set delta  = (TAU/count)
            loop
                set  node = allocate()
                call node.loadSpellConfiguration(configID)
                set  node.summoner = GetEventSpellCaster()
                set x = GetUnitX(node.summoner) + .distance*Cos(angle)
                set y = GetUnitY(node.summoner) + .distance*Sin(angle)
                set  node.summoned = CreateUnit(GetEventSpellUser(),.id,x,y,facing*bj_RADTODEG)
                call BJDebugMsg("A handle id of: " + I2S(GetHandleId(node.summoned) ) + "(" + GetObjectName(GetUnitTypeId(node.summoned) ) + ")" + " is created with a node of: " + I2S(node) )
                /* temporarily disabling this suspicious part before pausing
                if  .duration > 0. then
                    call UnitApplyTimedLife(node.summoned,EXPIRATION_TYPE,.duration)
                endif */
                call DestroyEffect( AddSpecialEffectTarget(.birthModel,node.summoned,.birthModelOrigin) )
                call PauseSystem.pause(node.summoned)
                /* temporarily disabling this suspicious part after pausing
                call SetUnitAnimation(node.summoned,.birthAnimation) */
                set node.unpauseString = ""
                set node.remainingDelay   = .delay
                set node.remainingDuration = .duration
                set node.next = 0
                set node.prev = thistype(0).prev
                set thistype(0).prev.next = node
                set thistype(0).prev = node
                set angle = angle + delta
                set count = count  - 1
                exitwhen   count == 0
            endloop
            return 0
        endmethod
        private method onSpellPeriodic takes nothing returns boolean
            if .remainingDelay > 0. then
                set .remainingDelay = .remainingDelay - SPELL_PERIOD
                if  .remainingDelay <= 0. then
                    call PauseSystem.unpause(.summoned)
                    //call SetUnitAnimation(.summoned,DEFAULT_ANIMATION)
                    if .unpauseString != "" then
                        call BJDebugMsg("|cffff0000You should receive this debug only ONCE for frost elemental node:|r " + I2S(this) )
                    else
                        set .unpauseString = "A handle id of: " + I2S(GetHandleId(.summoned) ) + "(" + GetObjectName(GetUnitTypeId(.summoned) ) + ")" + " and a node of: " + I2S(this) + " is finished pausing."
                        call BJDebugMsg(.unpauseString)
                    endif
                endif
            endif
            set .remainingDuration = .remainingDuration - SPELL_PERIOD
            return UnitAlive(.summoned)
        endmethod
        private method onSpellEnd takes nothing returns nothing
            local Frostbolt_ChilledBuff chilled
            local Frostbite_FrozenBuff  frozen
            local unit picked
            call GroupEnumUnitsInRange(Group,GetUnitX(.summoned),GetUnitY(.summoned),.radius,null)
            loop
                set picked = FirstOfGroup(Group)
                exitwhen picked == null
                if Filtration( picked,GetOwningPlayer(.summoner) ) then
                    call DestroyEffect( AddSpecialEffectTarget(.pickedModel,picked,.pickedModelOrigin) )
                    call UnitDamageTarget(.summoner,picked,.damage,false,false,.attackType,.damageType,.weaponType)
                    if UnitAlive(picked) then
                        if .chilledDuration > 0. then
                            set chilled = Frostbolt_ChilledBuff.add(.summoner,picked)
                            set chilled.duration = .chilledDuration
                            if (chilled.model == null) then
                                set chilled.model = AddSpecialEffectTarget(Frostbolt_ChilledBuff.buffModel,picked,Frostbolt_ChilledBuff.buffModelOrigin)
                            endif
                        endif
                        if .frozenDuration > 0. or .frozenHeroDuration > 0. then
                            set frozen = Frostbite_FrozenBuff.add(.summoner,picked)
                            if not IsUnitType(picked,UNIT_TYPE_HERO) then
                                set frozen.duration = .frozenDuration    + Frostbite_FrozenBuff.delay
                            else
                                set frozen.duration = .frozenHeroDuration + Frostbite_FrozenBuff.delay
                            endif
                            if (frozen.model == null) then
                                set frozen.model = AddSpecialEffectTarget(Frostbite_FrozenBuff.buffModel,picked,Frostbite_FrozenBuff.buffModelOrigin)
                            endif
                        endif
                    endif
                endif
                call GroupRemoveUnit(Group,picked)
            endloop
            call this.deallocate()
            set .summoner = null
            set .summoned = null
        endmethod

        implement SpellEvent
        implement SpellClonerFooter

    endstruct

    module FrostElementalConfigurationCloner

        private static method configHandler takes nothing returns nothing
            local FrostElemental clone  = FrostElemental.configuredInstance
            // Configuration by constants
            set clone.delay            = CREATION_DELAY
            set clone.birthAnimation   = CREATION_ANIMATION
            set clone.birthModel       = CREATION_MODEL
            set clone.birthModelOrigin  = CREATION_MODEL_ORIGIN
            set clone.pickedModel      = PICKED_MODEL
            set clone.pickedModelOrigin = PICKED_MODEL_ORIGIN
            set clone.attackType       = ATTACK_TYPE
            set clone.damageType       = DAMAGE_TYPE
            set clone.weaponType       = WEAPON_TYPE
            // Configuration with levels
            set clone.id                = summonID(GetEventSpellLevel())
            set clone.distance          = distance(GetEventSpellLevel())
            set clone.count             = count(GetEventSpellLevel())
            set clone.duration          = duration(GetEventSpellLevel())
            set clone.radius            = radius(GetEventSpellLevel())
            set clone.damage            = damage(GetEventSpellLevel())
            set clone.chilledDuration   = chilledDuration(GetEventSpellLevel())
            set clone.frozenDuration    = frozenDuration(GetEventSpellLevel())
            set clone.frozenHeroDuration = frozenHeroDuration(GetEventSpellLevel())
        endmethod

        private static method onInit takes nothing returns nothing
            call FrostElemental.create(thistype.typeid,ABILITY_ID,SPELL_EVENT_TYPE,function thistype.configHandler)
        endmethod
    endmodule

endlibrary
And replace the code of PauseLibrary with this one:
JASS:
library PauseLibrary


    globals
        //                   This serves as the main setup for pausing/unpausing a unit.

        //                                       Pause Ability ID
        public constant integer PAUSE_ID            = 'A00A' // Based on 'Infinite Channel Ability'
        public constant integer PAUSE_ORDER_ID      = 852055

        /*
                                    OBJECT DATA IMPORTS ARE 'OPTIONAL' BELOW
           I've discovered that infinite channel ability alone is not enough for some rare cases.
           So if you don't care about these rare cases you can simply set the booleans to FALSE. (<-NO IMPORT REQUIRED)
           If the infinite channel ability doesn't get implemented to a unit, the pause method adapts to a new method.

           These are some of the rare cases where infinite channel ability method breaks:                             */
        /*
           Case #1: Militia - for unknown reason, we can only put aura abilities to them,
           so we can't implement this method to militia units as it's considered using an active ability...
           In order to pause them, we can DISARM them.                                                                */
        public constant boolean DISARM              = true
        //                                       Disarm Ability ID
        public constant integer DISARM_ID           = 'A001' // Based on 'Cargo Hold Ability'
        /*
           Case #2: Same as #1 but the unit has abilities, therefore DISARM is not enough.
           In order to pause them, we can DISABLE (DISARM + SILENCE) them.
           So you should set DISARM to false and set DISABLE to true instead.
           Note that this DISABLE method requires and creates a global dummy unit to
           cast the Disable Ability on a unit. It also shows on the unit's status bar.
           Make sure you set the 'Stats - Buffs' field of the Disable Ability to Disable <- (Buff) in OE.             */
        public constant boolean DISABLE             = false
        //                                        Dummy Unit ID
        public constant integer DISABLE_CASTER      = 'dumi' // Based on 'DummyRecycler Library'
        //                                      Disable Ability ID
        public constant integer DISABLE_ID          = 'A004' // Based on 'Drunken Haze Ability'
        public constant integer DISABLE_ORDER_ID    = 852585
        //                                       Disable Buff ID
        public constant integer DISABLE_BUFF        = 'B000' // Based on 'Drunken Haze Buff'

        /* If you have Breath Of Fire ability that ignites the units having the Drunken Haze Buff in your map,
           You must specify the Drunken Haze buff and the Breath of Fire buff that is related to each other.
           In this way, Breath Of Fire ability will not ignite the units that are paused using the DISABLE method.    */
        //                                     Drunken Haze Buff ID
        public constant integer DRUNKEN_HAZE_BUFF   = 'BNdh'
        //                                    Breath Of Fire Buff ID
        public constant integer BREATH_OF_FIRE_BUFF = 'BNbf'
        /*
           Case #3: Silenced - for obvious reason, we can't implement the method to a silenced unit.
           In order to pause them, the silence must be removed from the unit first before pausing it.                 */
        public constant boolean SILENCE             = true
        //                                       Silence Buff ID
        public constant integer SILENCE_BUFF        = 'BNsi'
        /*
           Case #4: Polymorphed - for obvious reason, we can't implement the method to a polymorphed unit.
           In order to pause them, periodic polymorph checking, disabling its movement, and re-enable pause.          */
        public constant boolean POLYMORPH           = true
        /*
           Case #5: Sleeping - for obvious reason, we can't implement the method to a sleeping unit.
           In order to pause them, the unit must wake up first so we can order it to execute the pause.               */
        public constant boolean SLEEPING            = true

        public constant real PERIODIC_INTERVAL      = 0.05
    endglobals

    struct PauseSystem extends array

        private static hashtable table = InitHashtable()

        private static method periodic takes nothing returns nothing
            local unit target = LoadUnitHandle(table,0,GetHandleId(GetExpiredTimer()))
            local thistype this = GetHandleId(target)
            local integer count = LoadInteger(table,this,0)
            static if DISABLE then
                if GetUnitAbilityLevel(target,BREATH_OF_FIRE_BUFF) > 0 and GetUnitAbilityLevel(target,DRUNKEN_HAZE_BUFF) == 0 then
                    call UnitRemoveAbility(target,BREATH_OF_FIRE_BUFF)
                endif
            endif
            static if SILENCE then
                if GetUnitAbilityLevel(target,SILENCE_BUFF) > 0 then
                    static if DISARM then
                        if GetUnitAbilityLevel (target,DISARM_ID) == 0 then
                            call UnitAddAbility(target,DISARM_ID)
                        endif
                    elseif   DISABLE then
                        if GetUnitAbilityLevel(target,DISABLE_BUFF) == 0 then
                            call SetUnitX( disabler,GetUnitX(target) )
                            call SetUnitY( disabler,GetUnitY(target) )
                            call UnitAddAbility(disabler,DISABLE_ID)
                            call IssueTargetOrderById(disabler,DISABLE_ORDER_ID,target)
                            call UnitRemoveAbility(disabler,DISABLE_ID)
                        endif
                    else
                        call UnitRemoveAbility(target,SILENCE_BUFF)
                    endif
                endif
            endif
            if GetUnitTypeId(target) != LoadInteger(table,this,2) then
                call BJDebugMsg("|cffff0000Attempting to pause a changed unit type of target!|r A handle id of: " + I2S(this) + "(" + GetObjectName(GetUnitTypeId(target) ) + ")" + " is paused with a count of: " + I2S(count) )
                call SaveInteger( table,this,2,GetUnitTypeId(target) )
                call UnitRemoveAbility(target,PAUSE_ID)
                call SetUnitTurnSpeed(target,0)
                call SetUnitPropWindow(target,0)
                if UnitAddAbility(target,PAUSE_ID) and IssueImmediateOrderById(target,PAUSE_ORDER_ID) then
                endif
                if GetUnitAbilityLevel(target,PAUSE_ID) == 0 or GetUnitCurrentOrder(target) != PAUSE_ORDER_ID then
                    static if DISARM then
                        if GetUnitAbilityLevel (target,DISARM_ID) == 0 then
                            call UnitAddAbility(target,DISARM_ID)
                        endif
                    elseif   DISABLE then
                        if GetUnitAbilityLevel(target,DISABLE_BUFF) == 0 then
                            call SetUnitX( disabler,GetUnitX(target) )
                            call SetUnitY( disabler,GetUnitY(target) )
                            call UnitAddAbility(disabler,DISABLE_ID)
                            call IssueTargetOrderById(disabler,DISABLE_ORDER_ID,target)
                            call UnitRemoveAbility(disabler,DISABLE_ID)
                        endif
                    endif
                endif
            endif
            static if POLYMORPH then
                if not IsUnitType(target,UNIT_TYPE_POLYMORPHED) and LoadBoolean(table,this,3) then
                call BJDebugMsg("|cffff0000Attempting to pause a non-polymorphed target!|r A handle id of: " + I2S(this) + "(" + GetObjectName(GetUnitTypeId(target) ) + ")" + " is paused with a count of: " + I2S(count) )
                    call SetUnitTurnSpeed(target,0)
                    call SetUnitPropWindow(target,0)
                    static if SILENCE then
                        if GetUnitAbilityLevel(target,SILENCE_BUFF) > 0 then
                            call UnitRemoveAbility(target,SILENCE_BUFF)
                        endif
                    endif
                    call IssueImmediateOrderById(target,PAUSE_ORDER_ID)
                    call RemoveSavedBoolean(table,this,3)
                elseif IsUnitType(target,UNIT_TYPE_POLYMORPHED) then
                call BJDebugMsg("|cffff0000Attempting to pause a polymorphed target!|r A handle id of: " + I2S(this) + "(" + GetObjectName(GetUnitTypeId(target) ) + ")" + " is paused with a count of: " + I2S(count) )
                    call SetUnitTurnSpeed(target,0)
                    call SetUnitPropWindow(target,0)
                    call SaveBoolean( table,this,3,true )
                endif
            endif
            set target = null
        endmethod

        static method pause takes unit target returns nothing
            local thistype this = GetHandleId(target)
            local integer count = LoadInteger(table,this,0)
            local timer time
            if count == 0 then
                set time = CreateTimer()
                call SaveUnitHandle(table,0,GetHandleId(time),target)
                call TimerStart(time,PERIODIC_INTERVAL,true,function thistype.periodic)
                call SaveTimerHandle(table,this,1,time)
                call SaveInteger( table,this,2,GetUnitTypeId(target) )
                if count == 0 then
                call BJDebugMsg("A handle id of: " + I2S(this) + "(" + GetObjectName(GetUnitTypeId(target) ) + ")" + " is paused with a count of: " + I2S(count) + " (normal)")
                else
                call BJDebugMsg("A handle id of: " + I2S(this) + "(" + GetObjectName(GetUnitTypeId(target) ) + ")" + " is paused with a count of: " + I2S(count) + " (|cffff0000error|r)")
                endif
                static if SLEEPING then
                    if IsUnitType(target,UNIT_TYPE_SLEEPING) then
                        call UnitWakeUp(target)
                    endif
                endif
                static if SILENCE then
                    if GetUnitAbilityLevel(target,SILENCE_BUFF) > 0 then
                        call UnitRemoveAbility(target,SILENCE_BUFF)
                    endif
                endif
                call SetUnitTurnSpeed(target,0)
                call SetUnitPropWindow(target,0)
                if UnitAddAbility(target,PAUSE_ID) and IssueImmediateOrderById(target,PAUSE_ORDER_ID) then
                endif
                if GetUnitAbilityLevel(target,PAUSE_ID) == 0 or GetUnitCurrentOrder(target) != PAUSE_ORDER_ID then
                    static if DISARM then
                        call UnitAddAbility(target,DISARM_ID)
                    elseif   DISABLE then
                        call SetUnitX( disabler,GetUnitX(target) )
                        call SetUnitY( disabler,GetUnitY(target) )
                        call UnitAddAbility(disabler,DISABLE_ID)
                        call IssueTargetOrderById(disabler,DISABLE_ORDER_ID,target)
                        call UnitRemoveAbility(disabler,DISABLE_ID)
                    endif
                endif
                static if POLYMORPH then
                    if IsUnitType(target,UNIT_TYPE_POLYMORPHED)  then
                        call BJDebugMsg("Is the paused unit polymorphed? YES")
                        call SaveBoolean( table,this,3,true )
                    else
                        call BJDebugMsg("Is the paused unit polymorphed? NO")
                        call SaveBoolean( table,this,3,false )
                    endif
                    /* temporarily disabling suspicious part
                    call SaveBoolean( table,this,3,IsUnitType(target,UNIT_TYPE_POLYMORPHED) ) */
                endif
                set time = null
            else
                call BJDebugMsg("|cffff0000Attempting to pause a unit again!|r A handle id of: " + I2S(this) + "(" + GetObjectName(GetUnitTypeId(target) ) + ")" + " is paused with a count of: " + I2S(count) )
            endif
            call SaveInteger(table,this,0,count + 1)
        endmethod

        static method unpause takes unit target returns nothing
            local timer time
            local thistype this = GetHandleId(target)
            local integer count = LoadInteger(table,this,0)
            set count = count - 1
            call SaveInteger(table,this,0,count)
            if count == 0 then
                static if DISARM then
                    call UnitRemoveAbility(target,DISARM_ID)
                endif
                static if DISABLE then
                    call UnitRemoveAbility(target,DISABLE_BUFF)
                endif
                call UnitRemoveAbility(target,PAUSE_ID)
                call SetUnitTurnSpeed(target,GetUnitDefaultTurnSpeed(target) )
                call SetUnitPropWindow(target,GetUnitDefaultPropWindow(target)*bj_RADTODEG)
                if count == 0 then
                call BJDebugMsg("A handle id of: " + I2S(this) + "(" + GetObjectName(GetUnitTypeId(target) ) + ")" + " is unpaused with a count of: " + I2S(count) + " (normal)")
                else
                call BJDebugMsg("A handle id of: " + I2S(this) + "(" + GetObjectName(GetUnitTypeId(target) ) + ")" + " is unpaused with a count of: " + I2S(count) + " (|cfffff0000error|r)")
                endif
                set time = LoadTimerHandle(table,this,1)
                call DestroyTimer(time)
                call RemoveSavedHandle( table,0,GetHandleId(time) )
                call FlushChildHashtable(table,this)
                set time = null
            else
                call BJDebugMsg("|cffff0000Attempting to unpause a unit again!|r A handle id of: " + I2S(this) + "(" + GetObjectName(GetUnitTypeId(target) ) + ")" + " is unpaused with a count of: " + I2S(count) )
            endif
        endmethod

        static if DISABLE then
            private static unit disabler = null
            private static method onInit takes nothing returns nothing
                set disabler = CreateUnit(Player(bj_PLAYER_NEUTRAL_EXTRA),DISABLE_CASTER,0,0,0)
            endmethod
        endif
    endstruct

endlibrary
Then test it in-game by casting the Frost Elemental ability.

I've implemented debug messages even on its periodical operations
and removed some suspicious parts of the code.
At this point, red messages shouldn't be displayed if the operations are normal.

My result:
frost-elemental-pause-debug-png.355700

Honestly, I don't know what reforged have done with the internal codes,
(As obviously, they don't include patch notes about what they
change regarding scripting and what is broken accidentally or not)
It's too hindrance for a resource to be compatible at least.
 
Last edited:

RCF

RCF

Level 1
Joined
Sep 17, 2018
Messages
5
Thank so much for you response !
here is the report I don't know what it corresponds to the level of the bug but the elementary is still on pause even if this time it resumes its animation speed ( sorry for my english )
 

Attachments

  • upload_2020-5-23_23-51-9.png
    upload_2020-5-23_23-51-9.png
    5.2 MB · Views: 32
  • bug.png
    bug.png
    4.5 MB · Views: 66
  • bug2.png
    bug2.png
    4.8 MB · Views: 29
Level 19
Joined
Aug 13, 2013
Messages
1,680
I see...

Unpausing does not execute on them.
It looks like onSpellPeriodic doesn't run or something...
Try this instead:
JASS:
library FrostElemental /*


    */uses /*

    */Alloc              /* library is used to make this work on instances efficiently
    */SpellEvent         /* https://www.hiveworkshop.com/threads/301895/
    */SpellCloner        /* https://www.hiveworkshop.com/threads/292751/
    */Frostbolt          /* library is used for its ChilledBuffStruct
    */Frostbite          /* library is used for its FrozenBuffStruct
    */PauseLibrary       /* library is used for pausing/unpausing a unit safely

    */

    /**************************************************
    *               GLOBAL CONFIGURATION
    ***************************************************/

    globals

        private constant real PERIODIC_INTERVAL = 0.05

    endglobals

        // Filtration of the units that will be damaged, chilled, or frozen
    private function Filtration takes unit picked, player owner returns boolean
        return IsUnitEnemy(picked,owner)                   and /*
            */ not IsUnitType(picked,UNIT_TYPE_FLYING)     and /*
            */ not IsUnitType(picked,UNIT_TYPE_STRUCTURE)   and /*
            */ not IsUnitType(picked,UNIT_TYPE_MAGIC_IMMUNE) and /*
            */ UnitAlive(picked)
    endfunction

    /**************************************************
    *            END OF GLOBAL CONFIGURATION
    ***************************************************/

    /*============================= CORE CODE =============================*/

    native UnitAlive takes unit id returns boolean

    globals
        private constant real TAU = 2.*bj_PI
        private constant integer EXPIRATION_TYPE  = 'BTLF'
        private constant string DEFAULT_ANIMATION = "stand"
        private group Group = CreateGroup()
    endglobals

    struct FrostElemental extends array

        implement Alloc
        implement SpellClonerHeader

        integer id
        integer count

        real delay
        real distance
        real duration
        real radius
        real damage

        real chilledDuration
        real frozenDuration
        real frozenHeroDuration

        string birthAnimation
        string birthModel
        string birthModelOrigin

        string pickedModel
        string pickedModelOrigin

        attacktype attackType
        damagetype damageType
        weapontype weaponType

        readonly unit summoner
        readonly unit summoned

        readonly real remainingDelay
        readonly real remainingDuration

        private string unpauseString
        readonly static constant real SPELL_PERIOD = PERIODIC_INTERVAL

        private method onSpellStart takes nothing returns thistype
            local real x
            local real y
            local real angle
            local real delta
            local real facing
            local integer count
            local thistype node
            local integer configID = this.initSpellConfiguration( GetEventSpellAbilityId() )
            call BJDebugMsg(GetObjectName(GetEventSpellAbilityId()) + " is casted with an instance of: " + I2S(this) )
            set facing = GetUnitFacing(GetEventSpellCaster())*bj_DEGTORAD
            set angle  = facing
            set count  = .count
            set delta  = (TAU/count)
            loop
                set  node = allocate()
                call node.loadSpellConfiguration(configID)
                set  node.summoner = GetEventSpellCaster()
                set x = GetUnitX(node.summoner) + .distance*Cos(angle)
                set y = GetUnitY(node.summoner) + .distance*Sin(angle)
                set  node.summoned = CreateUnit(GetEventSpellUser(),.id,x,y,facing*bj_RADTODEG)
                call BJDebugMsg("A handle id of: " + I2S(GetHandleId(node.summoned) ) + "(" + GetObjectName(GetUnitTypeId(node.summoned) ) + ")" + " is created with a node of: " + I2S(node) )
                /* temporarily disabling this suspicious part before pausing
                if  .duration > 0. then
                    call UnitApplyTimedLife(node.summoned,EXPIRATION_TYPE,.duration)
                endif */
                call DestroyEffect( AddSpecialEffectTarget(.birthModel,node.summoned,.birthModelOrigin) )
                call PauseSystem.pause(node.summoned)
                /* temporarily disabling this suspicious part after pausing
                call SetUnitAnimation(node.summoned,.birthAnimation) */
                set node.unpauseString = ""
                set node.remainingDelay   = .delay
                set node.remainingDuration = .duration
                set node.next = 0
                set node.prev = thistype(0).prev
                set thistype(0).prev.next = node
                set thistype(0).prev = node
                set angle = angle + delta
                set count = count  - 1
                exitwhen   count == 0
            endloop
            return 0
        endmethod
        private method onSpellPeriodic takes nothing returns boolean
            call BJDebugMsg("onSpellPeriodic runs with a node of:" + I2S(this) + " with a remainingDuration of: " + R2S(.remainingDuration)  + " and a remainingDelay of: " + R2S(.remainingDelay) )
            if .remainingDelay > 0. then
                set .remainingDelay = .remainingDelay - PERIODIC_INTERVAL
                if  .remainingDelay <= 0. then
                    call PauseSystem.unpause(.summoned)
                    //call SetUnitAnimation(.summoned,DEFAULT_ANIMATION)
                    if .unpauseString != "" then
                        call BJDebugMsg("|cffff0000You should receive this debug only ONCE for frost elemental node:|r " + I2S(this) )
                    else
                        set .unpauseString = "A handle id of: " + I2S(GetHandleId(.summoned) ) + "(" + GetObjectName(GetUnitTypeId(.summoned) ) + ")" + " and a node of: " + I2S(this) + " is finished pausing."
                        call BJDebugMsg(.unpauseString)
                    endif
                endif
            endif
            set .remainingDuration = .remainingDuration - PERIODIC_INTERVAL
            if UnitAlive(.summoned) then
                return true
            else
               call BJDebugMsg("A handle id of: " + I2S(GetHandleId(.summoned) ) + "(" + GetObjectName(GetUnitTypeId(.summoned) ) + ")" + " and a node of: " + I2S(this) + " is considered dead.")
                return false
            endif
        endmethod
        private method onSpellEnd takes nothing returns nothing
            local Frostbolt_ChilledBuff chilled
            local Frostbite_FrozenBuff  frozen
            local unit picked
            call BJDebugMsg("onSpellEnd runs with a node of:" + I2S(this) )
            call GroupEnumUnitsInRange(Group,GetUnitX(.summoned),GetUnitY(.summoned),.radius,null)
            loop
                set picked = FirstOfGroup(Group)
                exitwhen picked == null
                if Filtration( picked,GetOwningPlayer(.summoner) ) then
                    call DestroyEffect( AddSpecialEffectTarget(.pickedModel,picked,.pickedModelOrigin) )
                    call UnitDamageTarget(.summoner,picked,.damage,false,false,.attackType,.damageType,.weaponType)
                    if UnitAlive(picked) then
                        if .chilledDuration > 0. then
                            set chilled = Frostbolt_ChilledBuff.add(.summoner,picked)
                            set chilled.duration = .chilledDuration
                            if (chilled.model == null) then
                                set chilled.model = AddSpecialEffectTarget(Frostbolt_ChilledBuff.buffModel,picked,Frostbolt_ChilledBuff.buffModelOrigin)
                            endif
                        endif
                        if .frozenDuration > 0. or .frozenHeroDuration > 0. then
                            set frozen = Frostbite_FrozenBuff.add(.summoner,picked)
                            if not IsUnitType(picked,UNIT_TYPE_HERO) then
                                set frozen.duration = .frozenDuration   + Frostbite_FrozenBuff.delay
                            else
                                set frozen.duration = .frozenHeroDuration + Frostbite_FrozenBuff.delay
                            endif
                            if (frozen.model == null) then
                                set frozen.model = AddSpecialEffectTarget(Frostbite_FrozenBuff.buffModel,picked,Frostbite_FrozenBuff.buffModelOrigin)
                            endif
                        endif
                    endif
                endif
                call GroupRemoveUnit(Group,picked)
            endloop
            call this.deallocate()
            set .summoner = null
            set .summoned = null
        endmethod

        implement SpellEvent
        implement SpellClonerFooter

    endstruct

    module FrostElementalConfigurationCloner

        private static method configHandler takes nothing returns nothing
            local FrostElemental clone  = FrostElemental.configuredInstance
            // Configuration by constants
            set clone.delay           = CREATION_DELAY
            set clone.birthAnimation   = CREATION_ANIMATION
            set clone.birthModel      = CREATION_MODEL
            set clone.birthModelOrigin  = CREATION_MODEL_ORIGIN
            set clone.pickedModel     = PICKED_MODEL
            set clone.pickedModelOrigin = PICKED_MODEL_ORIGIN
            set clone.attackType      = ATTACK_TYPE
            set clone.damageType      = DAMAGE_TYPE
            set clone.weaponType      = WEAPON_TYPE
            // Configuration with levels
            set clone.id               = summonID(GetEventSpellLevel())
            set clone.distance         = distance(GetEventSpellLevel())
            set clone.count            = count(GetEventSpellLevel())
            set clone.duration         = duration(GetEventSpellLevel())
            set clone.radius           = radius(GetEventSpellLevel())
            set clone.damage           = damage(GetEventSpellLevel())
            set clone.chilledDuration   = chilledDuration(GetEventSpellLevel())
            set clone.frozenDuration   = frozenDuration(GetEventSpellLevel())
            set clone.frozenHeroDuration = frozenHeroDuration(GetEventSpellLevel())
        endmethod

        private static method onInit takes nothing returns nothing
            call FrostElemental.create(thistype.typeid,ABILITY_ID,SPELL_EVENT_TYPE,function thistype.configHandler)
        endmethod
    endmodule

endlibrary
 
Level 15
Joined
May 16, 2012
Messages
549
I see...

Unpausing does not execute on them.
It looks like onSpellPeriodic doesn't run or something...
Try this instead:
JASS:
library FrostElemental /*


    */uses /*

    */Alloc              /* library is used to make this work on instances efficiently
    */SpellEvent         /* https://www.hiveworkshop.com/threads/301895/
    */SpellCloner        /* https://www.hiveworkshop.com/threads/292751/
    */Frostbolt          /* library is used for its ChilledBuffStruct
    */Frostbite          /* library is used for its FrozenBuffStruct
    */PauseLibrary       /* library is used for pausing/unpausing a unit safely

    */

    /**************************************************
    *               GLOBAL CONFIGURATION
    ***************************************************/

    globals

        private constant real PERIODIC_INTERVAL = 0.05

    endglobals

        // Filtration of the units that will be damaged, chilled, or frozen
    private function Filtration takes unit picked, player owner returns boolean
        return IsUnitEnemy(picked,owner)                   and /*
            */ not IsUnitType(picked,UNIT_TYPE_FLYING)     and /*
            */ not IsUnitType(picked,UNIT_TYPE_STRUCTURE)   and /*
            */ not IsUnitType(picked,UNIT_TYPE_MAGIC_IMMUNE) and /*
            */ UnitAlive(picked)
    endfunction

    /**************************************************
    *            END OF GLOBAL CONFIGURATION
    ***************************************************/

    /*============================= CORE CODE =============================*/

    native UnitAlive takes unit id returns boolean

    globals
        private constant real TAU = 2.*bj_PI
        private constant integer EXPIRATION_TYPE  = 'BTLF'
        private constant string DEFAULT_ANIMATION = "stand"
        private group Group = CreateGroup()
    endglobals

    struct FrostElemental extends array

        implement Alloc
        implement SpellClonerHeader

        integer id
        integer count

        real delay
        real distance
        real duration
        real radius
        real damage

        real chilledDuration
        real frozenDuration
        real frozenHeroDuration

        string birthAnimation
        string birthModel
        string birthModelOrigin

        string pickedModel
        string pickedModelOrigin

        attacktype attackType
        damagetype damageType
        weapontype weaponType

        readonly unit summoner
        readonly unit summoned

        readonly real remainingDelay
        readonly real remainingDuration

        private string unpauseString
        readonly static constant real SPELL_PERIOD = PERIODIC_INTERVAL

        private method onSpellStart takes nothing returns thistype
            local real x
            local real y
            local real angle
            local real delta
            local real facing
            local integer count
            local thistype node
            local integer configID = this.initSpellConfiguration( GetEventSpellAbilityId() )
            call BJDebugMsg(GetObjectName(GetEventSpellAbilityId()) + " is casted with an instance of: " + I2S(this) )
            set facing = GetUnitFacing(GetEventSpellCaster())*bj_DEGTORAD
            set angle  = facing
            set count  = .count
            set delta  = (TAU/count)
            loop
                set  node = allocate()
                call node.loadSpellConfiguration(configID)
                set  node.summoner = GetEventSpellCaster()
                set x = GetUnitX(node.summoner) + .distance*Cos(angle)
                set y = GetUnitY(node.summoner) + .distance*Sin(angle)
                set  node.summoned = CreateUnit(GetEventSpellUser(),.id,x,y,facing*bj_RADTODEG)
                call BJDebugMsg("A handle id of: " + I2S(GetHandleId(node.summoned) ) + "(" + GetObjectName(GetUnitTypeId(node.summoned) ) + ")" + " is created with a node of: " + I2S(node) )
                /* temporarily disabling this suspicious part before pausing
                if  .duration > 0. then
                    call UnitApplyTimedLife(node.summoned,EXPIRATION_TYPE,.duration)
                endif */
                call DestroyEffect( AddSpecialEffectTarget(.birthModel,node.summoned,.birthModelOrigin) )
                call PauseSystem.pause(node.summoned)
                /* temporarily disabling this suspicious part after pausing
                call SetUnitAnimation(node.summoned,.birthAnimation) */
                set node.unpauseString = ""
                set node.remainingDelay   = .delay
                set node.remainingDuration = .duration
                set node.next = 0
                set node.prev = thistype(0).prev
                set thistype(0).prev.next = node
                set thistype(0).prev = node
                set angle = angle + delta
                set count = count  - 1
                exitwhen   count == 0
            endloop
            return 0
        endmethod
        private method onSpellPeriodic takes nothing returns boolean
            call BJDebugMsg("onSpellPeriodic runs with a node of:" + I2S(this) + " with a remainingDuration of: " + R2S(.remainingDuration)  + " and a remainingDelay of: " + R2S(.remainingDelay) )
            if .remainingDelay > 0. then
                set .remainingDelay = .remainingDelay - PERIODIC_INTERVAL
                if  .remainingDelay <= 0. then
                    call PauseSystem.unpause(.summoned)
                    //call SetUnitAnimation(.summoned,DEFAULT_ANIMATION)
                    if .unpauseString != "" then
                        call BJDebugMsg("|cffff0000You should receive this debug only ONCE for frost elemental node:|r " + I2S(this) )
                    else
                        set .unpauseString = "A handle id of: " + I2S(GetHandleId(.summoned) ) + "(" + GetObjectName(GetUnitTypeId(.summoned) ) + ")" + " and a node of: " + I2S(this) + " is finished pausing."
                        call BJDebugMsg(.unpauseString)
                    endif
                endif
            endif
            set .remainingDuration = .remainingDuration - PERIODIC_INTERVAL
            if UnitAlive(.summoned) then
                return true
            else
               call BJDebugMsg("A handle id of: " + I2S(GetHandleId(.summoned) ) + "(" + GetObjectName(GetUnitTypeId(.summoned) ) + ")" + " and a node of: " + I2S(this) + " is considered dead.")
                return false
            endif
        endmethod
        private method onSpellEnd takes nothing returns nothing
            local Frostbolt_ChilledBuff chilled
            local Frostbite_FrozenBuff  frozen
            local unit picked
            call BJDebugMsg("onSpellEnd runs with a node of:" + I2S(this) )
            call GroupEnumUnitsInRange(Group,GetUnitX(.summoned),GetUnitY(.summoned),.radius,null)
            loop
                set picked = FirstOfGroup(Group)
                exitwhen picked == null
                if Filtration( picked,GetOwningPlayer(.summoner) ) then
                    call DestroyEffect( AddSpecialEffectTarget(.pickedModel,picked,.pickedModelOrigin) )
                    call UnitDamageTarget(.summoner,picked,.damage,false,false,.attackType,.damageType,.weaponType)
                    if UnitAlive(picked) then
                        if .chilledDuration > 0. then
                            set chilled = Frostbolt_ChilledBuff.add(.summoner,picked)
                            set chilled.duration = .chilledDuration
                            if (chilled.model == null) then
                                set chilled.model = AddSpecialEffectTarget(Frostbolt_ChilledBuff.buffModel,picked,Frostbolt_ChilledBuff.buffModelOrigin)
                            endif
                        endif
                        if .frozenDuration > 0. or .frozenHeroDuration > 0. then
                            set frozen = Frostbite_FrozenBuff.add(.summoner,picked)
                            if not IsUnitType(picked,UNIT_TYPE_HERO) then
                                set frozen.duration = .frozenDuration   + Frostbite_FrozenBuff.delay
                            else
                                set frozen.duration = .frozenHeroDuration + Frostbite_FrozenBuff.delay
                            endif
                            if (frozen.model == null) then
                                set frozen.model = AddSpecialEffectTarget(Frostbite_FrozenBuff.buffModel,picked,Frostbite_FrozenBuff.buffModelOrigin)
                            endif
                        endif
                    endif
                endif
                call GroupRemoveUnit(Group,picked)
            endloop
            call this.deallocate()
            set .summoner = null
            set .summoned = null
        endmethod

        implement SpellEvent
        implement SpellClonerFooter

    endstruct

    module FrostElementalConfigurationCloner

        private static method configHandler takes nothing returns nothing
            local FrostElemental clone  = FrostElemental.configuredInstance
            // Configuration by constants
            set clone.delay           = CREATION_DELAY
            set clone.birthAnimation   = CREATION_ANIMATION
            set clone.birthModel      = CREATION_MODEL
            set clone.birthModelOrigin  = CREATION_MODEL_ORIGIN
            set clone.pickedModel     = PICKED_MODEL
            set clone.pickedModelOrigin = PICKED_MODEL_ORIGIN
            set clone.attackType      = ATTACK_TYPE
            set clone.damageType      = DAMAGE_TYPE
            set clone.weaponType      = WEAPON_TYPE
            // Configuration with levels
            set clone.id               = summonID(GetEventSpellLevel())
            set clone.distance         = distance(GetEventSpellLevel())
            set clone.count            = count(GetEventSpellLevel())
            set clone.duration         = duration(GetEventSpellLevel())
            set clone.radius           = radius(GetEventSpellLevel())
            set clone.damage           = damage(GetEventSpellLevel())
            set clone.chilledDuration   = chilledDuration(GetEventSpellLevel())
            set clone.frozenDuration   = frozenDuration(GetEventSpellLevel())
            set clone.frozenHeroDuration = frozenHeroDuration(GetEventSpellLevel())
        endmethod

        private static method onInit takes nothing returns nothing
            call FrostElemental.create(thistype.typeid,ABILITY_ID,SPELL_EVENT_TYPE,function thistype.configHandler)
        endmethod
    endmodule

endlibrary


Ooooh boy, PauseUnit is very nasty function, it works only when it wants ushausa.
 
Level 19
Joined
Aug 13, 2013
Messages
1,680
Ooooh boy, PauseUnit is very nasty function, it works only when it wants ushausa.
The thing is I don't use the PauseUnit() native.
I'm relying on my PauseLibrary to execute pausing alternatives in any case...
At this point, I can conclude there's nothing wrong with the PauseLibrary.

I'm suspecting that the onSpellPeriodic doesn't run or execute somehow (or something)
that makes the elementals pause forever in reforged patches. Perhaps, you can test it?
 

RCF

RCF

Level 1
Joined
Sep 17, 2018
Messages
5
problem solved !
it was just an internal conflict with other trigger I would be more careful next time,
thank you again for your work, it's beautiful you are genius ! :D
 
Last edited:

AGD

AGD

Level 14
Joined
Mar 29, 2016
Messages
678
RCF, does this debug message appear in your test?
JASS:
        private method onSpellPeriodic takes nothing returns boolean
            call BJDebugMsg("onSpellPeriodic runs with a node of:" + I2S(this) + " with a remainingDuration of: " + R2S(.remainingDuration)  + " and a remainingDelay of: " + R2S(.remainingDelay) )
 
Level 19
Joined
Aug 13, 2013
Messages
1,680
problem solved !
it was just an internal conflict with other trigger I would be more careful next time,
thank you again for your work, it's beautiful you are genius ! :D
I'm glad to hear this, so it's just a misinterpretation. Thanks for clearing this up! ;)



So the only concern for now is this:
calling TriggerClearConditions(GetTriggeringTrigger()) could crash reforged...
which can be easily fixed (by using alternative) on its next update. Just stay tuned. ;)
 
Level 19
Joined
Aug 13, 2013
Messages
1,680
Jaina Spellpack v1.1.0 - Public Update
CHANGELOG

Systems:

- Updated LinkedList.
- Updated SpellEvent.
- Updated SpellCloner.
^ AGD recently marked these systems as the finalized dependencies.
You're required to upgrade them if they're present in your map ;)

- Updated DispelLibrary.
^ This optional library is also required for upgrade to
incorporate the changes made by the dependencies above.

Abilities:

Frostbolt & Multi Frostbolt
- No longer crashes reforged versions.
- Fixed a typo in tooltip description.
- Adjusted Chilled buff slow-stats: -50%(MS),-25%(AS) to -20%/-40%/-60%(MS),-10%/-20%/-30%(AS)
- Now configurable to apply Chilled buff and Frozen buff.

Frostbite
- Fixed beep sound playing for all players when ability fails.

Frost Elemental
- Implemented a new unit ability called Freezing Bolt.
- Increased animation cast point time from 0 to 0.100

Miscellaneous:
- Changed the term of 'modified' to 'mastered' instead.
- Prerequisites are now optional. Each spell can standalone.
^ Spells can now work independently without other spells.
^ Separated library for Buff structs. (Now you can use my self-defined buffs for your other purposes!)

Alright, this is a major update. Archimonde will also receive this kind of update sooner.

@chopinski I believe the spellpack is now stable in reforged versions.
Can you at least confirm this? I've also included you in the credit list. Thanks in advance!
 
Level 12
Joined
Jan 30, 2020
Messages
876
Oh my....

These are both beautiful, although my dragon is a fire dragon.
I would love that Fire Storm in my map.
It would be the only external spell, but I think it would be worth it.

I am willing to convert it in Lua by hand if required !!
Err... where can I find it ?

EDIT :
Will it be a part of the upcoming Kael'thas Spellpack ?
 
Last edited:
Level 12
Joined
Jan 30, 2020
Messages
876
Oh..ok. Well thats really promising !

I have never made very elaborate spells before, until know I use a smart combination of object editor and Lua, but even the most complex spell mechanics I use so far is really basic compared to all these spells posted in this sections.
I am really impressed !!!
 
Level 19
Joined
Aug 13, 2013
Messages
1,680
Oh my....

These are both beautiful, although my dragon is a fire dragon.
I would love that Fire Storm in my map.
It would be the only external spell, but I think it would be worth it.

I am willing to convert it in Lua by hand if required !!
Err... where can I find it ?

EDIT :
Will it be a part of the upcoming Kael'thas Spellpack ?
Here's the Fire Storm.
JASS:
library FireStormConfiguration uses IceStorm optional ChilledBuffLibrary optional FrozenBuffLibrary

    private struct Configuration extends array
        //                                                   Ice Storm Ability ID
        private static constant integer ABILITY_ID               = 'A005'
        //                                          The size of the primary projectile
        private static constant real   PRIMARY_SIZE              = 1.00
        //                                         The size of the secondary projectile
        private static constant real   SECONDARY_SIZE            = 0.75
        //                                         The height of the primary projectile
        private static constant real   PRIMARY_HEIGHT            = 50.00
        //                                        The height of the secondary projectile
        private static constant real   SECONDARY_HEIGHT          = 50.00
        //                                          The model of the primary projectile
        private static constant string PRIMARY_MODEL             = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl"
        private static constant string PRIMARY_MODEL_ORIGIN      = "origin"
        //                                         The model of the secondary projectile
        private static constant string SECONDARY_MODEL           = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile.mdl"
        private static constant string SECONDARY_MODEL_ORIGIN    = "origin"
        //                                           The model spawned on the caster
        private static constant string CASTER_MODEL              = "Abilities\\Spells\\Items\\VampiricPotion\\VampPotionCaster.mdl"
        private static constant string CASTER_MODEL_ORIGIN       = "chest"
        //                               The model spawned on the picked unit when inflicting damage
        private static constant string PICKED_MODEL              = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile_mini.mdl"
        private static constant string PICKED_MODEL_ORIGIN       = "chest"
        //                      The primary model spawning randomly on the ground within the radius per period
        private static constant string PRIMARY_PERIODIC_MODEL    = "Abilities\\Weapons\\DemolisherFireMissile\\DemolisherFireMissile.mdl"
        //                     The secondary model spawning randomly on the ground within the radius per period
        private static constant string SECONDARY_PERIODIC_MODEL  = "Abilities\\Spells\\Human\\Feedback\\SpellBreakerAttack.mdl"
        //                                         The attack type of inflicting damage
        private static constant attacktype ATTACK_TYPE           = ATTACK_TYPE_MAGIC
        //                                         The damage type of inflicting damage
        private static constant damagetype DAMAGE_TYPE           = DAMAGE_TYPE_MAGIC
        //                                         The weapon type of inflicting damage
        private static constant weapontype WEAPON_TYPE           = WEAPON_TYPE_WHOKNOWS

        private static constant method radius takes integer level returns real
            return 750.00 + (0.00*level)    // radius of the ability
        endmethod
        private static constant method delay takes integer level returns real
            return 5.00   - (1.50*level)    // time it takes to cover the radius
        endmethod
        private static constant method duration takes integer level returns real
            return 5.00   + (5.00*level)    // duration of the ability (starts ticking after delay time)
        endmethod
        private static constant method primaryCount takes integer level returns integer
            return 15     + (0   *level)    // maximum number of the primary projectile
        endmethod
        private static constant method rotation takes integer level returns real
            return 2.50   + (0.00*level)    // rotation of the primary projectile
        endmethod
        private static constant method distance takes integer level returns real
            return 100.00 + (0.00*level)    // minimum distance of the secondary projectile
        endmethod
        private static constant method minInterval takes integer level returns real
            return 0.30   - (0.05*level)    // minimum spawning interval of the secondary projectile
        endmethod
        private static constant method maxInterval takes integer level returns real
            return 0.80   - (0.10*level)    // maximum spawning interval of the secondary projectile
        endmethod
        private static constant method secondaryCount takes integer level returns integer
            return 0      + (5   *level)    // maximum number of the secondary projectile
        endmethod
        private static constant method minRotation takes integer level returns real
            return 5.00   + (0.00*level)    // minimum rotation of the secondary projectile
        endmethod
        private static constant method maxRotation takes integer level returns real
            return 7.50   + (0.00*level)    // maximum rotation of the secondary projectile
        endmethod
        private static constant method collision takes integer level returns real
            return 50.00  + (0.00*level)    // collision of the secondary projectile
        endmethod
        private static constant method damage takes integer level returns real
            return 150.00 + (0.00*level)    // inflicting damage of the secondary projectile in collision per second
        endmethod
        static if LIBRARY_ChilledBuffLibrary then
            private static constant method chilledRadius takes integer level returns boolean
                return true                 // applying chilled buff on the radius
            endmethod
        endif
        static if LIBRARY_FrozenBuffLibrary then
            private static constant method frozenRadius takes integer level returns boolean
                return false                // applying frozen buff on the radius
            endmethod
        endif
        private static constant method rate takes integer level returns real
            return 10.00  + (10.00*level)   // spawning rate of the periodical models
        endmethod

        private static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
        implement IceStormConfigurationCloner
    endstruct
endlibrary
Just configure it more to your likings.

EDIT:

And use this updated core code instead... (as I have also tweaked projectile's facing)
JASS:
library IceStorm /*


    */uses /*

    */Alloc                       /* library is used to make this work on instances efficiently
    */LinkedList                  /* library is used to make this work on a list data structure
    */SpellEvent                  /* https://www.hiveworkshop.com/threads/301895/
    */SpellCloner                 /* https://www.hiveworkshop.com/threads/292751/
    */DummyRecycler               /* https://www.hiveworkshop.com/threads/277659/
    */optional WorldBounds        /* https://github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds
    */optional ChilledBuffLibrary /* library is used to make this work on Chilled buff
    */optional FrozenBuffLibrary  /* library is used to make this work on Frozen buff

    */

    /**************************************************
    *             GLOBAL CONFIGURATION
    ***************************************************/

    globals
        private constant real PERIODIC_INTERVAL = 0.03125
        private constant real MODEL_DEATH_TIME  = 2.5
    endglobals

        // Filtration of the units that will be damaged, chilled, or frozen
    private function Filtration takes unit picked, player owner returns boolean
        return IsUnitEnemy(picked,owner)                     and /*
            */ not IsUnitType(picked,UNIT_TYPE_MAGIC_IMMUNE) and /*
            */ UnitAlive(picked)
    endfunction

    /**************************************************
    *          END OF GLOBAL CONFIGURATION
    ***************************************************/

    /*============================= CORE CODE =============================*/

    native UnitAlive takes unit id returns boolean

    globals
        private constant real TAU = 2.*bj_PI
        private group Group = CreateGroup()

        private key PRIMARY
        private key SECONDARY
    endglobals

    private struct Node extends array
        implement Alloc
    endstruct

    private struct Projectile extends array

        integer stage
        unit projectile
        real   angle
        real  rotation
        real   curDis
        real   maxDis
        effect model

        method operator x takes nothing returns real
            return GetUnitX(.projectile)
        endmethod
        method operator y takes nothing returns real
            return GetUnitY(.projectile)
        endmethod
        method destroy takes nothing returns nothing
            call DestroyEffect(.model)
            call DummyAddRecycleTimer(.projectile,MODEL_DEATH_TIME)
            set .model      = null
            set .projectile = null
            call Node(this).deallocate()
        endmethod
        static method create takes real x, real y, real z, real size, real facing, string model, string modelOrigin returns thistype
            local thistype node = Node.allocate()
            set node.projectile = GetRecycledDummy(x,y,z,facing*bj_RADTODEG)
            set node.model = AddSpecialEffectTarget(model,node.projectile,modelOrigin)
            call SetUnitScale(node.projectile,size,0,0)
            return node
        endmethod

    endstruct

    private struct ProjectileList extends array

        private static method onRemove takes thistype node returns nothing
            call Projectile(node).destroy()
        endmethod
        private static method allocate takes nothing returns thistype
            return Node.allocate()
        endmethod
        private method deallocate takes nothing returns nothing
            call Node(this).deallocate()
        endmethod
        implement InstantiatedList

    endstruct

    struct IceStorm extends array

        real radius
        real delay
        real duration

        real rotation
        real distance
        real minInterval
        real maxInterval
        real minRotation
        real maxRotation
        real collision
        real damage
        real rate

        real primarySize
        real secondarySize
        real primaryHeight
        real secondaryHeight

        string primaryModel
        string primaryModelOrigin
        string secondaryModel
        string secondaryModelOrigin
        string casterModel
        string casterModelOrigin
        string pickedModel
        string pickedModelOrigin
        string periodicPrimaryModel
        string periodicSecondaryModel

        integer primaryCount
        integer secondaryCount

        static if LIBRARY_ChilledBuffLibrary then
            boolean chilledRadius
        endif
        static if LIBRARY_FrozenBuffLibrary then
            boolean frozenRadius
        endif

        attacktype attackType
        damagetype damageType
        weapontype weaponType

        readonly unit   caster
        readonly player owner

        readonly integer level
        readonly integer counter

        readonly real remainingDelay
        readonly real remainingDuration
        readonly real randomizeInterval
        readonly real remainingInterval
        readonly real primaryRadius
        readonly real primaryVelocity

        private effect model

        private ProjectileList list
        readonly static constant real SPELL_PERIOD = PERIODIC_INTERVAL

        private method onClonedSpellStart takes nothing returns thistype
            local real x1
            local real x2
            local real y1
            local real y2
            local real angle
            local real delta
            local real facing
            local integer count
            local Projectile node

            set  .caster = GetEventSpellCaster()
            set  .owner  = GetEventSpellPlayer()
            set  .level  = GetEventSpellLevel()
            set  .list   = ProjectileList.create()
            set  .model  = AddSpecialEffectTarget(.casterModel,.caster,.casterModelOrigin)

            set x1      = GetUnitX(.caster)
            set y1      = GetUnitY(.caster)
            set facing  = GetUnitFacing(.caster)*bj_DEGTORAD
            set angle   = facing
            set count   = .primaryCount
            set delta   = (TAU/count)
            if GetRandomReal(0,1.0) <= .5 then
                set .rotation = -.rotation
            endif
            loop
                set x2 = x1 + 0.*Cos(angle)
                set y2 = y1 + 0.*Sin(angle)
                set node = Projectile.create(x2,y2,.primaryHeight,.primarySize,angle,.primaryModel,.primaryModelOrigin)
                set node.angle = angle
                set node.rotation = .rotation
                set node.stage = PRIMARY
                call .list.pushFront(node)
                set angle = angle + delta
                set count = count  - 1
                exitwhen    count == 0
            endloop

            set .counter           = .secondaryCount
            set .remainingDelay    = .delay
            set .remainingDuration = .duration
            set .randomizeInterval = GetRandomReal(.minInterval,.maxInterval)
            set .remainingInterval = .randomizeInterval
            set .primaryRadius     = 0.
            set .primaryVelocity   = .radius/(.delay/PERIODIC_INTERVAL)
            return this
        endmethod

        private method onClonedSpellPeriodic takes nothing returns boolean
            local Projectile node
            local ProjectileList listNode

            static if LIBRARY_ChilledBuffLibrary then
                local ChilledBuffLibrary_ChilledBuff chilled
            endif
            static if LIBRARY_FrozenBuffLibrary then
                local FrozenBuffLibrary_FrozenBuff frozen
            endif

            local real x = GetUnitX(.caster)
            local real y = GetUnitY(.caster)
            local real x1
            local real y1
            local real x2
            local real y2
            local real ang
            local real dis
            local unit picked

            if  .primaryRadius < .radius then
                 set .primaryRadius = .primaryRadius + .primaryVelocity
            endif
            set listNode = this.list.next
            loop
                exitwhen listNode == this.list
                if Projectile(listNode).stage == PRIMARY then
                    set Projectile(listNode).angle = Projectile(listNode).angle + Projectile(listNode).rotation
                    set x1 = x + .primaryRadius*Cos(Projectile(listNode).angle)
                    set y1 = y + .primaryRadius*Sin(Projectile(listNode).angle)
                    static if LIBRARY_WorldBounds then
                        if x1 < WorldBounds.maxX and x1 > WorldBounds.minX and y1 < WorldBounds.maxY and y1 > WorldBounds.minY then
                            call SetUnitX(Projectile(listNode).projectile,x1)
                            call SetUnitY(Projectile(listNode).projectile,y1)
                    set x1 = x + .primaryRadius*Cos(Projectile(listNode).angle + Projectile(listNode).rotation)
                    set y1 = y + .primaryRadius*Sin(Projectile(listNode).angle + Projectile(listNode).rotation)
                            call SetUnitFacing(Projectile(listNode).projectile,Atan2(y1-Projectile(listNode).y,x1-Projectile(listNode).x)*bj_RADTODEG)
                        endif
                    else
                            call SetUnitX(Projectile(listNode).projectile,x1)
                            call SetUnitY(Projectile(listNode).projectile,y1)
                    set x1 = x + .primaryRadius*Cos(Projectile(listNode).angle + Projectile(listNode).rotation)
                    set y1 = y + .primaryRadius*Sin(Projectile(listNode).angle + Projectile(listNode).rotation)
                            call SetUnitFacing(Projectile(listNode).projectile,Atan2(y1-Projectile(listNode).y,x1-Projectile(listNode).x)*bj_RADTODEG)
                    endif
                elseif Projectile(listNode).stage == SECONDARY then
                    if Projectile(listNode).curDis < Projectile(listNode).maxDis then
                        set Projectile(listNode).curDis = Projectile(listNode).curDis + .primaryVelocity
                    endif
                    set Projectile(listNode).angle = Projectile(listNode).angle + Projectile(listNode).rotation
                    set x2 = x + Projectile(listNode).curDis*Cos(Projectile(listNode).angle)
                    set y2 = y + Projectile(listNode).curDis*Sin(Projectile(listNode).angle)
                    static if LIBRARY_WorldBounds then
                        if x2 < WorldBounds.maxX and x2 > WorldBounds.minX and y2 < WorldBounds.maxY and y2 > WorldBounds.minY then
                            call SetUnitX(Projectile(listNode).projectile,x2)
                            call SetUnitY(Projectile(listNode).projectile,y2)
                    set x2 = x + Projectile(listNode).curDis*Cos(Projectile(listNode).angle + Projectile(listNode).rotation)
                    set y2 = y + Projectile(listNode).curDis*Sin(Projectile(listNode).angle + Projectile(listNode).rotation)
                            call SetUnitFacing(Projectile(listNode).projectile,Atan2(y2-Projectile(listNode).y,x2-Projectile(listNode).x)*bj_RADTODEG)
                        endif
                    else
                            call SetUnitX(Projectile(listNode).projectile,x2)
                            call SetUnitY(Projectile(listNode).projectile,y2)
                    set x2 = x + Projectile(listNode).curDis*Cos(Projectile(listNode).angle + Projectile(listNode).rotation)
                    set y2 = y + Projectile(listNode).curDis*Sin(Projectile(listNode).angle + Projectile(listNode).rotation)
                            call SetUnitFacing(Projectile(listNode).projectile,Atan2(y2-Projectile(listNode).y,x2-Projectile(listNode).x)*bj_RADTODEG)
                    endif
                    call GroupEnumUnitsInRange(Group,Projectile(listNode).x,Projectile(listNode).y,.collision,null)
                    loop
                        set picked = FirstOfGroup(Group)
                        exitwhen picked == null
                        if Filtration(picked,.owner) then
                            call DestroyEffect( AddSpecialEffectTarget(.pickedModel,picked,.pickedModelOrigin) )
                            call UnitDamageTarget(.caster,picked,.damage,false,false,.attackType,.damageType,.weaponType)
                        endif
                        call GroupRemoveUnit(Group,picked)
                    endloop
                endif
                set listNode = listNode.next
            endloop

            if .counter > 0 then
                if .remainingInterval <= 0. then
                    set ang = GetRandomReal(-bj_PI,bj_PI)
                    set node = Projectile.create(x + 0.*Cos(ang),y + 0.*Sin(ang),.secondaryHeight,.secondarySize,ang,.secondaryModel,.secondaryModelOrigin)
                    set node.angle  = ang
                    set node.curDis = 0.
                    set node.maxDis = GetRandomReal(.distance,.primaryRadius)
                    set node.stage  = SECONDARY
                    if .rotation > 0. then
                        set .rotation =  GetRandomReal(.minRotation,.maxRotation)
                    else
                        set .rotation = -GetRandomReal(.minRotation,.maxRotation)
                    endif
                    set node.rotation = .rotation
                    call .list.pushFront(node)
                    set .randomizeInterval = GetRandomReal(.minInterval,.maxInterval)
                    set .remainingInterval = .randomizeInterval
                    set .counter = .counter - 1
                else
                    set .remainingInterval = .remainingInterval - PERIODIC_INTERVAL
                endif
            endif

            call GroupEnumUnitsInRange(Group,x,y,.primaryRadius,null)
            loop
                set picked = FirstOfGroup(Group)
                exitwhen picked == null
                if Filtration(picked,.owner) then
                    static if LIBRARY_ChilledBuffLibrary then
                        if .chilledRadius then
                            set chilled = ChilledBuffLibrary_ChilledBuff.add(.caster,picked)
                            set chilled.duration = 1.
                            if GetUnitAbilityLevel(picked,ChilledBuffLibrary_ChilledBuff.RAWCODE) != .level then
                                call SetUnitAbilityLevel(picked,ChilledBuffLibrary_ChilledBuff.RAWCODE,.level)
                            endif
                        endif
                    endif
                    static if LIBRARY_FrozenBuffLibrary then
                        if .frozenRadius then
                            set frozen = FrozenBuffLibrary_FrozenBuff.add(.caster,picked)
                            set frozen.duration = 1. + FrozenBuffLibrary_FrozenBuff.DELAY
                        endif
                    endif
                endif
                call GroupRemoveUnit(Group,picked)
            endloop

            if GetRandomReal(0,100) <= .rate then
                set ang = GetRandomReal(-bj_PI,bj_PI)
                set dis = GetRandomReal(0,.primaryRadius)
                call DestroyEffect(AddSpecialEffect(.periodicPrimaryModel,x + dis*Cos(ang),y + dis*Sin(ang)))
            endif
            if GetRandomReal(0,100) <= .rate then
                set ang = GetRandomReal(-bj_PI,bj_PI)
                set dis = GetRandomReal(0,.primaryRadius)
                call DestroyEffect(AddSpecialEffect(.periodicSecondaryModel,x + dis*Cos(ang),y + dis*Sin(ang)))
            endif
            if .remainingDelay > 0. then
                set .remainingDelay    = .remainingDelay    - PERIODIC_INTERVAL
            else
                set .remainingDuration = .remainingDuration - PERIODIC_INTERVAL
            endif
            return UnitAlive(.caster) and .remainingDuration > 0.

        endmethod

        private method onClonedSpellEnd takes nothing returns nothing
            call this.list.destroy()
            call DestroyEffect(.model)
            set .model  = null
            set .caster = null
        endmethod

        implement SpellCloner

    endstruct

    module IceStormConfigurationCloner

        private static method configHandler takes nothing returns nothing
            local IceStorm clone             = SpellCloner.configuredInstance
            // Configuration by constants
            set clone.primarySize            = PRIMARY_SIZE
            set clone.primaryHeight          = PRIMARY_HEIGHT
            set clone.secondarySize          = SECONDARY_SIZE
            set clone.secondaryHeight        = SECONDARY_HEIGHT
            set clone.primaryModel           = PRIMARY_MODEL
            set clone.primaryModelOrigin     = PRIMARY_MODEL_ORIGIN
            set clone.secondaryModel         = SECONDARY_MODEL
            set clone.secondaryModelOrigin   = SECONDARY_MODEL_ORIGIN
            set clone.casterModel            = CASTER_MODEL
            set clone.casterModelOrigin      = CASTER_MODEL_ORIGIN
            set clone.pickedModel            = PICKED_MODEL
            set clone.pickedModelOrigin      = PICKED_MODEL_ORIGIN
            set clone.periodicPrimaryModel   = PRIMARY_PERIODIC_MODEL
            set clone.periodicSecondaryModel = SECONDARY_PERIODIC_MODEL
            set clone.attackType             = ATTACK_TYPE
            set clone.damageType             = DAMAGE_TYPE
            set clone.weaponType             = WEAPON_TYPE
            // Configuration with levels
            set clone.radius                 = radius(GetEventSpellLevel())
            set clone.delay                  = delay(GetEventSpellLevel())
            set clone.duration               = duration(GetEventSpellLevel())
            set clone.primaryCount           = primaryCount(GetEventSpellLevel())
            set clone.rotation               = rotation(GetEventSpellLevel())*bj_DEGTORAD
            set clone.distance               = distance(GetEventSpellLevel())
            set clone.minInterval            = minInterval(GetEventSpellLevel())
            set clone.maxInterval            = maxInterval(GetEventSpellLevel())
            set clone.secondaryCount         = secondaryCount(GetEventSpellLevel())
            set clone.minRotation            = minRotation(GetEventSpellLevel())*bj_DEGTORAD
            set clone.maxRotation            = maxRotation(GetEventSpellLevel())*bj_DEGTORAD
            set clone.collision              = collision(GetEventSpellLevel())
            set clone.damage                 = damage(GetEventSpellLevel())/(1./PERIODIC_INTERVAL)
            static if LIBRARY_ChilledBuffLibrary then
                set clone.chilledRadius      = chilledRadius(GetEventSpellLevel())
            endif
            static if LIBRARY_FrozenBuffLibrary then
                set clone.frozenRadius       = frozenRadius(GetEventSpellLevel())
            endif
            set clone.rate                   = rate(GetEventSpellLevel())
        endmethod

        private static method onInit takes nothing returns nothing
            call IceStorm.create(thistype.typeid,ABILITY_ID,SPELL_EVENT_TYPE,function thistype.configHandler)
        endmethod

    endmodule
endlibrary
 
Last edited:
Level 12
Joined
Jan 30, 2020
Messages
876
UPDATE :
OK, I have spent a lot of timer studying the spell code, as well as the related libraries.
This is really clever and efficient stuff.

My map being a self-educative project, I have decided to not do a plain conversion of Fire Storm to Lua, even if it is so beautiful.
I will rather use the knowledge this has brought to my attention and try to build my own spell out of it from the ground up.

The map currently does not need any spell cloning as there won't be more than 1 or 2 abilities based on the same core principle.
So for the time being I might just do something simple, and consider the spell cloning approach in my next project.

Thanks again for sharing, this has opened my mind to many new perspectives !
 
Level 5
Joined
Jan 19, 2020
Messages
103
i tryed to import in another map, but i got a error, textmakro redeclared its super fuction something.
if i copy the folder into a empty map it works, why?

multi forstbolt still bugged? how i use single?
 
Level 5
Joined
Jan 19, 2020
Messages
103
i found the problem u are useing liberys i already have in the map but different versions, and if i deactivate one, another is not working.
 
Top