1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  3. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still haven't received your rank award? Then please contact the administration.
    Dismiss Notice
  4. Lead your forces to battle in the 15th Techtree Contest. The call is yours, commander!
    Dismiss Notice
  5. The reforging of the races is complete. Come see the 14th Techtree Contest Results.
    Dismiss Notice
  6. It's time to choose your horse in the race - the 32nd Modeling Contest Poll is up!
    Dismiss Notice
  7. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Jaina Spellpack v1.1.0

Submitted by JAKEZINC
This bundle is marked as pending. It has not been reviewed by a staff member yet.
[​IMG]

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

[​IMG]
[​IMG]
[​IMG]

It contains: 5 hero abilities (shown), 4 mastered abilities, 4 unit abilities (shown), 1 standard ability.
[​IMG] [​IMG] [​IMG] [​IMG] [​IMG] [​IMG] [​IMG] [​IMG] [​IMG]


- 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)

  1. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    292
    Resources:
    1
    Spells:
    1
    Resources:
    1
    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: May 23, 2020
  2. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    457
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    Thanks for testing it out. So it must be that calling
    TriggerClearConditions(GetTriggeringTrigger())
    could crash reforge..
     
  3. JAKEZINC

    JAKEZINC

    Joined:
    Aug 13, 2013
    Messages:
    1,461
    Resources:
    9
    Spells:
    9
    Resources:
    9
    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:
    Code (vJASS):
    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:
    Code (vJASS):
    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:
    [​IMG]

    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: May 23, 2020
  4. RCF

    RCF

    Joined:
    Sep 17, 2018
    Messages:
    5
    Resources:
    0
    Resources:
    0
    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 )
     

    Attached Files:

  5. JAKEZINC

    JAKEZINC

    Joined:
    Aug 13, 2013
    Messages:
    1,461
    Resources:
    9
    Spells:
    9
    Resources:
    9
    I see...

    Unpausing does not execute on them.
    It looks like onSpellPeriodic doesn't run or something...
    Try this instead:
    Code (vJASS):
    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
     
  6. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    292
    Resources:
    1
    Spells:
    1
    Resources:
    1

    Ooooh boy, PauseUnit is very nasty function, it works only when it wants ushausa.
     
  7. JAKEZINC

    JAKEZINC

    Joined:
    Aug 13, 2013
    Messages:
    1,461
    Resources:
    9
    Spells:
    9
    Resources:
    9
    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?
     
  8. RCF

    RCF

    Joined:
    Sep 17, 2018
    Messages:
    5
    Resources:
    0
    Resources:
    0
    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: May 24, 2020
  9. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    457
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    RCF, does this debug message appear in your test?
    Code (vJASS):
            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) )
     
     
  10. JAKEZINC

    JAKEZINC

    Joined:
    Aug 13, 2013
    Messages:
    1,461
    Resources:
    9
    Spells:
    9
    Resources:
    9
    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:
    which can be easily fixed (by using alternative) on its next update. Just stay tuned. ;)
     
  11. JAKEZINC

    JAKEZINC

    Joined:
    Aug 13, 2013
    Messages:
    1,461
    Resources:
    9
    Spells:
    9
    Resources:
    9
    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!
     
  12. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    292
    Resources:
    1
    Spells:
    1
    Resources:
    1
    Tested! Everything is working as intended up to the latest Reforged patch.
     
  13. Macadamia

    Macadamia

    Joined:
    Jan 30, 2020
    Messages:
    687
    Resources:
    0
    Resources:
    0
    Wow beautiful spells.

    I wish the latest AOE Ultimate made with the fire thematic was a fraction as good !
     
  14. JAKEZINC

    JAKEZINC

    Joined:
    Aug 13, 2013
    Messages:
    1,461
    Resources:
    9
    Spells:
    9
    Resources:
    9
    Oh you mean Fire Storm?
    [​IMG]

    Or a Fire + Ice Storm? (A symphony of frost and flame) :p
    [​IMG]
     
  15. Macadamia

    Macadamia

    Joined:
    Jan 30, 2020
    Messages:
    687
    Resources:
    0
    Resources:
    0
    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: May 30, 2020 at 12:47 PM
  16. hemmedo

    hemmedo

    Joined:
    Jun 13, 2008
    Messages:
    312
    Resources:
    0
    Resources:
    0
    Unfortunately, the concept is already predefined and mostly inspired from Heroes of the Storm but will be modified heavily for WC3 mechanics. (His ultimate will be even more awesome than Jaina's)
     
  17. Macadamia

    Macadamia

    Joined:
    Jan 30, 2020
    Messages:
    687
    Resources:
    0
    Resources:
    0
    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 !!!
     
  18. JAKEZINC

    JAKEZINC

    Joined:
    Aug 13, 2013
    Messages:
    1,461
    Resources:
    9
    Spells:
    9
    Resources:
    9
    Here's the Fire Storm.
    Code (vJASS):

    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)
    Code (vJASS):

    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: May 30, 2020 at 1:29 PM
  19. Macadamia

    Macadamia

    Joined:
    Jan 30, 2020
    Messages:
    687
    Resources:
    0
    Resources:
    0
    Thank you so much, will start working on it tonight :)
     
  20. Macadamia

    Macadamia

    Joined:
    Jan 30, 2020
    Messages:
    687
    Resources:
    0
    Resources:
    0
    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 !