System SpellStruct

Jesus4Lyf

Good Idea™
Reaction score
397
SpellStruct​
Version 1.0.7​

Requirements:
- Jass NewGen

JASS:
//
//
//      Spell Struct
//          By Jesus4Lyf.
//       Version 1.0.7.
//
//      What is SpellStruct?
//     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//          SpellStruct is a system designed for the sake of rapid spell development.
//          It grants features which can be entirely encapsulated in a struct type per
//          ability. It handles event response creation, timer attachment, trigger
//          attachment, area of effect (AoE) enumeration, unit attachment, and all
//          spells made using it should be automatically MUI/leakless and rather efficient.
//          
//      How to implement?
//     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//          Simply create a new trigger object called SpellStruct, go to 'Edit -> Convert
//          to Custom Text', and replace everything that's there with this script.
//
//    _______________________
//    ||                   ||
//    || SpellStruct Usage ||
//    ||                   ||
//    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//      Writing a Simple SpellStruct:
//     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//          - To use SpellStruct, write SpellStructs. These are structs which extend
//            SpellStruct, and implement SpellStruct.
//
//          - Everything is optional to implement/use, except setting thistype.abil='Axxx'
//            in static method onInit.
//
//          - Example:
//
/*          struct MySpell extends SpellStruct
                implement SpellStruct
                
                private static method onInit takes nothing returns nothing
                    set thistype.abil='A000'
                endmethod
            endstruct
*/
//      Event Responses:
//     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//          - The following is a list of event responses that come with SpellStructs.
//
//          - Event responses are stored on your struct as members.
/*
            // Members of your SpellStruct:
            unit caster             // The casting unit.
            integer level           // The level of the ability on the casting unit.
            integer order           // The order id of the caster when it began casting the spell.
            unit targetUnit         // The target unit of the ability being cast (null if none).
            item targetItem         // The target item of the ability being cast (null if none).
            destructable targetDest // The target destructable of the ability being cast (null if none).
            widget targetWidget     // The target unit, item or destructable (see above).
            real targetX            // The point targetted when channeling began (X/Y coordinates).
            real targetY            // If spell is targetted, this is the original position of the target.
            player owner            // The owner of the casting unit, when channeling began.
            integer abilId          // The ability id of the ability being cast.
            
            // Extra methods:
            real casterX                        // The caster's X coordinate (inlines to GetUnitX(this.caster))
            real casterY                        // The caster's Y coordinate (inlines to GetUnitY(this.caster))
*/
//          - The above event response list may be used anywhere in your SpellStruct,
//            and at any time.
//
//          - You may implement methods which are called when the normal Warcraft III
//            spell events would fire. Some of the Warcraft III event responses are
//            broken for certain events, sometimes intermittently, but these are fixed
//            when using SpellStruct.
//            Also, usually in Warcraft III, these are implemented in a way that cycles
//            through all triggered abilities to see if the spell cast is the spell the
//            trigger is for. In SpellStruct, this is changed so that Warcraft III will
//            jump straight to the method for the spell that was cast.
//
//          - These methods, which are called when events fire, are non-static. This means
//            any members you add in your SpellStruct can be accessed from within the method.
//            This is achieved with unit attachment (internally).
//
//          - Example:
//
/*          struct MySpell extends SpellStruct
                implement SpellStruct
                
                // Reserved method names for event methods, in order of firing:
                method onCreate takes nothing returns nothing // fires just before channel
                    // If this is an AoE spell...
                    set this.aoe=this.level*200
                    
                    // Maybe you want to control the lifetime of the struct yourself:
                    set this.autoDestroy=false // there is a thistype.autoDestroyDefault too,
                                               // which is true by default.
                                               // So you can conditionally control the lifetime.
                                               // Do not destroy structs before the spell ends.
                endmethod
                method onChannel takes nothing returns nothing // Unit starts channeling a spell.
                endmethod
                method onStartCast takes nothing returns nothing // Fires much as the same as channel.
                endmethod
                method onEffect takes nothing returns nothing // When the spell has successfully cast, mana deducted.
                endmethod                                     // Will only fire if channeling successfully completed.
                method onFinish takes nothing returns nothing // When the effect finishes (or is interrupted).
                endmethod                                     // This will only fire if the effect fired.
                method onStopCast takes nothing returns nothing // When the spell stops being cast, or is cancelled.
                endmethod
                
                private static method onInit takes nothing returns nothing
                    set thistype.abil='Axxx'
                    set thistype.autoDestroyDefault=true // set to true by default.
                                                         // may be overridden for each
                                                         // instance by setting
                                                         // this.autoDestroy=true/false
                    // AutoDestroyed structs are destroyed after the spell stops casting.
                    
                    set thistype.defaultAoE=200.0 // optional.
                endmethod
            endstruct
*/
//      Disabling Auto-Destruction:
//     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//          - SpellStructs are created just before the .onChannel event method is
//            called.
//
//          - By default, SpellStructs are automatically destroyed, and this occurs
//            just after the method .onStopCast is called.
//
//          - You may extend this lifetime in two ways. If you wish to manually
//            manage the lifetime of a SpellStruct, you may do either of the following:
/*          struct MySpell extends SpellStruct
                implement SpellStruct
                
                method onStopCast takes nothing returns nothing
                    call SpellStruct(this).destroy() // can call this at any time. JassHelper bug requires SpellStruct(this).
                endmethod                            // no more event methods fire after destroying a SpellStruct.
                
                method onCreate takes nothing returns nothing
                    set this.autoDestroy=false // Either this
                endmethod
                private static method onInit takes nothing returns nothing
                    set thistype.abil='Axxx'
                    set thistype.autoDestroyDefault=false // or this.
                endmethod
            endstruct
*/
//          - Setting thistype.autoDestroyDefault to false causes .autoDestroy to
//            be set to false just before onCreate is called, for all new instances.
//
//          - Setting this.autoDestroy to false stops just the current instance
//            from auto destruction. Setting it back to true will cause the struct
//            to be destroyed if it usually would have been by then, else continue
//            to monitor it for auto destruction.
//
//      Locking:
//     ¯¯¯¯¯¯¯¯¯¯
//          - You may also add locks to an instance. This is done using this.addLock(),
//            and this.removeLock(). Calling this.addLock increments a counter, and
//            calling this.removeLock() decrements it. While it is greater than 0,
//            a SpellStruct will not be autodestroyed. Decrementing it back to 0
//            after the .onStopCast method has fired while .autoDestroy is true will
//            destroy the struct automatically. This is a garbage collection mechanism.
//
//          - You may check if a struct has no locks on it by using .isNotLocked.
//
//          - Example:
//
/*          struct MySpell extends SpellStruct
                implement SpellStruct
                
                private integer ticks=57
                
                // T32x example.
                private method periodic takes nothing returns nothing
                    set this.ticks=this.ticks-1
                    if this.ticks==0 then
                        call BJDebugMsg("Fired 57 times")
                        call this.stopPeriodic()
                        call this.removeLock() // Remove lock called here.
                    endif                      // will only destroy the struct if casting finished,
                endmethod                      // so that event methods will continue to fire and
                implement T32x                 // all data is available.
                
                method onEffect takes nothing returns nothing
                    call this.startPeriodic()
                    call this.addLock() // We want the struct to exist until .stopPeriodic is called.
                endmethod               // Locks are useful because we could attach this instance to any
                                        // number of things, which can each unlock it when they are done with it.
                private static method onInit takes nothing returns nothing
                    set thistype.abil='Axxx'
                endmethod
            endstruct
*/
//      Timers:
//     ¯¯¯¯¯¯¯¯¯
//          - If you have TimerUtils in your map, SpellStruct will operate using
//            TimerUtils data attachment for timers.
//          - If you don't have TimerUtils, but have Recycle, SpellStruct will
//            attach to Recycled timers using GetHandleId and a hashtable.
//          - If you have neither, SpellStruct will create timers dynamically,
//            and pause and destroy them when they are done with. Attachment
//            will be done with GetHandleId and a hashtable.
//
//          - .startTimer(method, period) will start a timer for the given method,
//            for the current spell instance. This means all spell event responses
//            will be available from within the callback. This timer will keep
//            firing until you stop it using .stopTimer(method).
//
//          - Starting a timer in this way automatically adds a lock to the struct,
//            and stopping a timer removes a lock. This is to guarantee that when
//            a timer method fires, all data is available and valid (while a struct
//            has locks, it will not be auto destroyed).
//
//          - Manually calling .destroy on a spell struct will stop all timers for
//            that struct automatically.
//
//          - Because it attaches both the method to call and the struct instance
//            to the timer, and then fires the method with .execute(), it is
//            recommended that you use this only for timers that are reasonably
//            infrequent. Using T32x with SpellStruct is recommended for high
//            frequency (low period) timers.
//
//          - Example:
//
/*          struct MySpell extends SpellStruct
                implement SpellStruct
                
                private method whenTimerExpires takes nothing returns nothing
                    call KillUnit(this.targetUnit) // Kill the target of the spell.
                    call this.stopTimer(thistype.whenTimerExpires)
                endmethod
                method onEffect takes nothing returns nothing
                    call this.startTimer(thistype.whenTimerExpires,5.0) // Run .whenTimerExpires in 5.0 seconds.
                endmethod
                
                private static method onInit takes nothing returns nothing
                    set thistype.abil='Axxx'
                endmethod
            endstruct
*/
//      Triggers:
//     ¯¯¯¯¯¯¯¯¯¯¯
//          - Trigger attaching works using GetHandleId and a hashtable.
//
//          - .createTrigger(method) will create a trigger with the method as it's
//            action. Do not use DestroyTrigger to remove this trigger, you must
//            use .destroyTrigger(method) to destroy it instead. This saves having
//            to store the trigger in any sort of variable, generally. Destroying
//            a trigger using SpellStruct has protection against the double free
//            bug in Warcraft III, even if you use TriggerSleepAction.
//
//          - Creating a trigger in this way automatically adds a lock to the struct,
//            and destroying it removes this lock. This is to guarantee that when
//            a timer method fires, all data is available and valid (while a struct
//            has locks, it will not be auto destroyed).
//
//          - Manually calling .destroy on a spell struct will destroy all triggers
//            for that struct automatically.
//
//          - Example:
//
/*          struct MySpell extends SpellStruct
                implement SpellStruct
                
                // Gets a random real between -300.0 and 300.0.
                private static constant real targetOffset=300.0
                private static constant method getOffset takes nothing returns real // inlines
                    return GetRandomReal(-thistype.targetOffset, thistype.targetOffset)
                endmethod
                
                // For skipping an order, when reissuing it.
                private boolean skipOrder = false
                
                private method onTargetPointOrder takes nothing returns nothing
                    if this.skipOrder then
                        set this.skipOrder=false
                    else
                        set this.skipOrder=true
                        call IssuePointOrderById(this.targetUnit, GetIssuedOrderId(), GetOrderPointX() + thistype.getOffset(), GetOrderPointY() + thistype.getOffset())
                    endif
                endmethod
                private method onEffect takes nothing returns nothing
                    // Blurs the aim of the target for 5.0 seconds.
                    call TriggerRegisterUnitEvent(this.createTrigger(thistype.onTargetPointOrder), this.targetUnit, EVENT_UNIT_ISSUED_POINT_ORDER)
                    call TriggerSleepAction(5.0) // a lock from the trigger will stop the struct from being destroyed.
                    call this.destroyTrigger(thistype.onTargetPointOrder)
                endmethod
                
                private static method onInit takes nothing returns nothing
                    set thistype.abil='Axxx'
                endmethod
            endstruct
*/
//      AoE (Area of Effect) enumeration:
//     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//          - You may set the AoE of a spell instance using set this.aoe = x, or you
//            may set the default value for the aoe of a spell using set
//            thistype.defaultAoE = x. This will set the value of .aoe to the value
//            specified before onCreate is called.
//
//          - You may enumerate the units in AoE of the target point, the current
//            position of the targetted object, or the caster. This requires a unit
//            filter to be applied.
//
//          - This enum does not clear the group before hand, like native enums.
//            Actually, it should even be safe to use with dynamic groups.
//
/*          struct MySpell extends SpellStruct
                implement SpellStruct
                
                private method myFilter takes unit u returns boolean
                    return not IsUnitType(u, UNIT_TYPE_DEAD)
                endmethod
                
                private method onEffect takes nothing returns nothing
                    local group g = CreateGroup()
                    call this.enumUnitsInAoE(g, thistype.myFilter) // Like a default WC3 ability, enum in range of the point targetted when channeling began.
                    call this.enumUnitsInAoETarget(g, thistype.myFilter) // Enums all units current within aoe of the targetted widget (unit/item/destructable).
                    call this.enumUnitsInAoECaster(g, thistype.myFilter) // Enums all units current within aoe of the caster.
                    //...
                endmethod
                
                private static method onInit takes nothing returns nothing
                    set thistype.abil='Axxx'
                    set thistype.defaultAoE=500.0
                endmethod
            endstruct
*/
//          - You may also skip groups altogether and do actions for all units within
//            aoe of the target point/target/caster.
//
/*          struct MySpell extends SpellStruct
                implement SpellStruct
                
                private method myAction takes unit u returns nothing
                    if not IsUnitType(u, UNIT_TYPE_DEAD) then
                        call SetUnitX(u, this.casterX)
                        call SetUnitY(u, this.casterY)
                    endif
                endmethod
                
                private method onEffect takes nothing returns nothing
                    set this.aoe = this.level * 100.0 + 100.0
                    call this.forUnitsInAoE(thistype.myAction) // Like a default WC3 ability, units in range of the point targetted when channeling began.
                    call this.forUnitsInAoETarget(thistype.myAction) // For all units current within aoe of the targetted widget (unit/item/destructable).
                    call this.forUnitsInAoECaster(thistype.myAction) // For all units current within aoe of the caster.
                    //...
                endmethod
                
                private static method onInit takes nothing returns nothing
                    set thistype.abil='Axxx'
                endmethod
            endstruct
*/
//          - You may also check, for a single unit, to see if it is within the AoE
//            of the target point, target or caster, using the following:
//            - this.isUnitInAoE(myUnit) // within aoe of target point.
//            - this.isUnitInAoETarget(myUnit) // within aoe of target.
//            - this.isUnitInAoECaster(myUnit) // within aoe of caster.
//          - The above return booleans.
//
//          - All AoE functionality takes into account the collision size of the
//            enumerated units. This matches better with Warcraft III AoE detection,
//            which highlights units the spell will hit in green for AoE abilities.
//
//      Miscellaneous:
//     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//          - call this.forGroup(g, callback method takes unit)
//            Example:
//
/*          method myCallback takes unit u returns nothing
                call SetWidgetLife(u, GetWidgetLife(this.caster))
            endmethod
            // in some method:
                    call this.forGroup(g, thistype.myCallback) // set life for all units in group to the caster's life.
*/
//          - real this.getDistanceToTargetWidget() // The distance between the caster and the target widget.
//          - real this.getDistanceToTargetPoint()  // The distance between the caster and the original point targetted.
//          - real this.getAngleToTargetWidget()    // The angle from the caster to the target object (in radians).
//          - real this.getAngleToTargetPoint()    // The angle from the caster to the target location (in radians).
//
//      Thanks:
//     ¯¯¯¯¯¯¯¯¯
//          - Romek, for helping me with the interface hint to make the methods
//            optional.
//
library SpellStruct uses optional TimerUtils, optional Recycle
    //===========================================================================
    // Configurables
    //
    globals
        private constant real MAX_UNIT_COLLISION_SIZE=256.0
    endglobals
    
    //===========================================================================
    // Header Declarations
    //
    globals
        private group GROUP=CreateGroup()
        private hashtable STORE=InitHashtable() // attaches to:
        // abilid, methodkey
        // abilid, unit
        // timer, key
        // trigger, key
        // spellstruct, method (timer)
        // spellstruct, method (trigger)
    endglobals
    
    private function interface Method takes integer this returns nothing
    private function interface UnitMethodFilter takes integer this, unit u returns boolean
    private function interface UnitMethod takes integer this, unit u returns nothing
    private function interface Allocator takes nothing returns integer
    
    //===========================================================================
    // Recursion stack
    //
    private module Stack
        static thistype top=0 // thistype(0) throws a syntax error, does not compile
        static method increment takes nothing returns nothing
            set thistype.top=thistype(thistype.top+1)
        endmethod
        static method decrement takes nothing returns nothing
            set thistype.top=thistype(thistype.top-1)
        endmethod
    endmodule
    
    //===========================================================================
    // Defaults
    //
    private interface DefaultsInterface
        // For methods that are not implemented.
        // In order of firing.
        method onCreate takes nothing returns nothing defaults nothing
        method onChannel takes nothing returns nothing defaults nothing
        method onStartCast takes nothing returns nothing defaults nothing
        method onEffect takes nothing returns nothing defaults nothing // May not always fire in casting a given spell.
        method onFinish takes nothing returns nothing defaults nothing // May not fire in casting a spell, Blizzard's event response fails.
        method onStopCast takes nothing returns nothing defaults nothing // Blizzard's event response sometimes fails.
        
        method cleanup takes nothing returns nothing defaults nothing
    endinterface
    
    //===========================================================================
    // Event Handlers
    //
    globals
        private constant key ALLOCATOR
    endglobals
    
    globals//locals
        private DefaultsInterface ThisCastData
        private integer CastingAbility
    endglobals
    //! textmacro SpellStruct__EventResponse takes METHOD_NAME, FUNCTION_NAME, FIRST_EVENT, LAST_EVENT
        private function $FUNCTION_NAME$ takes nothing returns boolean
            set CastingAbility=GetSpellAbilityId()
            static if $FIRST_EVENT$ then
                set ThisCastData=DefaultsInterface(LoadInteger(STORE,CastingAbility,ALLOCATOR))
                if ThisCastData==0 then
                    return false
                endif
                set ThisCastData=DefaultsInterface(Allocator(LoadInteger(STORE,CastingAbility,ALLOCATOR)).evaluate())
                call SaveInteger(STORE,CastingAbility,GetHandleId(GetTriggerUnit()),ThisCastData)
            else
                set ThisCastData=DefaultsInterface(LoadInteger(STORE,CastingAbility,GetHandleId(GetTriggerUnit())))
            endif
            //call BJDebugMsg("$FUNCTION_NAME$ - Method: "+I2S(LoadInteger(STORE,CastingAbility,$METHOD_KEY$))+" for struct "+I2S(ThisCastData))
            if ThisCastData!=0 then
                call ThisCastData.$METHOD_NAME$.execute()
                static if $LAST_EVENT$ then
                    call ThisCastData.cleanup.evaluate()
                endif
            endif
            return false
        endfunction
    //! endtextmacro
    
    //! runtextmacro SpellStruct__EventResponse("onChannel","OnChannel","true","false")
    //! runtextmacro SpellStruct__EventResponse("onStartCast","OnStartCast","false","false")
    //! runtextmacro SpellStruct__EventResponse("onEffect","OnEffect","false","false")
    //! runtextmacro SpellStruct__EventResponse("onFinish","OnFinish","false","false")
    //! runtextmacro SpellStruct__EventResponse("onStopCast","OnStopCast","false","true")
    
    //===========================================================================
    // Attachment.
    //
    private struct ChainAttach
        trigger trigger
        timer timer
        integer instance
        Method callback
        thistype next
        thistype prev
    endstruct
    
    //===========================================================================
    // Timers (requires Attachment).
    //
    globals
        private constant key TIMER_DATA
    endglobals
    private function SetTimerStruct takes timer t, integer data returns nothing
        static if LIBRARY_TimerUtils then
            call SetTimerData(t,data)
        else
            call SaveInteger(STORE,GetHandleId(t),TIMER_DATA,data)
        endif
    endfunction
    private function GetTimerStruct takes timer t returns integer
        static if LIBRARY_TimerUtils then
            return GetTimerData(t)
        else
            return LoadInteger(STORE,GetHandleId(t),TIMER_DATA)
        endif
    endfunction
    //! textmacro SpellStruct__GetTimer takes VARIABLE
        static if LIBRARY_TimerUtils then
            set $VARIABLE$=NewTimer()
        elseif LIBRARY_Recycle then
            set $VARIABLE$=Timer.get()
        else
            set $VARIABLE$=CreateTimer()
        endif
    //! endtextmacro
    //! textmacro SpellStruct__ReleaseTimer takes TIMER
        static if LIBRARY_TimerUtils then
            call ReleaseTimer($TIMER$)
        elseif LIBRARY_Recycle then
            call RemoveSavedInteger(STORE,GetHandleId($TIMER$),TIMER_DATA)
            call Timer.release($TIMER$)
        else
            call RemoveSavedInteger(STORE,GetHandleId($TIMER$),TIMER_DATA)
            call PauseTimer($TIMER$)
            call DestroyTimer($TIMER$)
            //set $TIMER$=null // using globals, unnecessary.
        endif
    //! endtextmacro
    
    //===========================================================================
    // Triggers (requires Attachment).
    //
    globals
        private constant key TRIGGER_DATA
    endglobals
    private function SetTriggerStruct takes trigger t, integer data returns nothing
        call SaveInteger(STORE,GetHandleId(t),TRIGGER_DATA,data)
    endfunction
    private function GetTriggerStruct takes trigger t returns integer
        return LoadInteger(STORE,GetHandleId(t),TRIGGER_DATA)
    endfunction
    //! textmacro SpellStruct__FlushTrigger takes TRIGGER
        call RemoveSavedInteger(STORE,GetHandleId($TRIGGER$),TRIGGER_DATA)
    //! endtextmacro
    
    //===========================================================================
    // AoE enumeration.
    //
    private struct EnumStack extends array
        implement Stack
        integer instance
        real x
        real y
        real range
        integer callback
        group for
    endstruct
    private function InternalEnum takes nothing returns boolean // Could be recursive.
        local unit u=GetFilterUnit() // can't be a global due to recursion.
        if IsUnitInRangeXY(u,EnumStack.top.x,EnumStack.top.y,EnumStack.top.range) then // Factors in collision sizes.
            if UnitMethodFilter(EnumStack.top.callback).evaluate(EnumStack.top.instance,u) then
                call GroupAddUnit(EnumStack.top.for,u)
            endif
        endif
        set u=null
        return false
    endfunction
    private function InternalFor takes nothing returns boolean // Could be recursive.
        local unit u=GetFilterUnit() // can't be a global due to recursion.
        if IsUnitInRangeXY(u,EnumStack.top.x,EnumStack.top.y,EnumStack.top.range) then // Factors in collision sizes.
            call UnitMethod(EnumStack.top.callback).execute(EnumStack.top.instance,u)
        endif
        set u=null
        return false
    endfunction
    
    //===========================================================================
    // Exposed Interface - SpellStruct
    //
    struct SpellStruct extends DefaultsInterface
        //===========================================================================
        // Various Spell-based Methods.
        //
        method getDistanceToTargetWidget takes nothing returns real
            local real x=GetWidgetX(this.targetWidget)-this.casterX
            local real y=GetWidgetY(this.targetWidget)-this.casterY
            return SquareRoot(x*x+y*y)
        endmethod
        method getDistanceToTargetPoint takes nothing returns real
            local real x=this.targetX-this.casterX
            local real y=this.targetY-this.casterY
            return SquareRoot(x*x+y*y)
        endmethod
        method getAngleToTargetWidget takes nothing returns real // radians.
            return Atan2(GetWidgetY(this.targetWidget)-this.casterY,GetWidgetX(this.targetWidget)-this.casterX)
        endmethod
        method getAngleToTargetPoint takes nothing returns real // radians.
            return Atan2(this.targetY-this.casterY,this.targetX-this.casterX)
        endmethod
        
        //===========================================================================
        // ForGroup.
        //
        private static method forGroupCallback takes nothing returns nothing
            call UnitMethod(EnumStack.top.callback).execute(EnumStack.top.instance,GetEnumUnit()) // bigger crimes have been committed, but not much bigger.
        endmethod
        method forGroup takes group g, UnitMethod callback returns nothing
            call EnumStack.increment() // just borrowing that stack... would break if event reponses accessed the stack directly, but they don't.
            set EnumStack.top.instance=this
            set EnumStack.top.callback=callback
            call ForGroup(g,function thistype.forGroupCallback)
            call EnumStack.decrement()
        endmethod
        
        //===========================================================================
        // AoE enumeration.
        //
        real aoe
        
        method enumUnitsInAoE takes group whichGroup, UnitMethodFilter filter returns nothing /*eg: method filter takes unit u returns boolean*/
            call EnumStack.increment()
            set EnumStack.top.instance=this
            set EnumStack.top.x=this.targetX
            set EnumStack.top.y=this.targetY
            set EnumStack.top.range=this.aoe
            set EnumStack.top.callback=filter
            set EnumStack.top.for=whichGroup
            call GroupEnumUnitsInRange(GROUP,EnumStack.top.x,EnumStack.top.y,EnumStack.top.range+MAX_UNIT_COLLISION_SIZE,Filter(function InternalEnum))
            call EnumStack.decrement()
        endmethod
        
        method enumUnitsInAoETarget takes group whichGroup, UnitMethodFilter filter returns nothing /*eg: method filter takes unit u returns boolean*/
            call EnumStack.increment()
            set EnumStack.top.instance=this
            set EnumStack.top.x=GetWidgetX(this.targetWidget)
            set EnumStack.top.y=GetWidgetY(this.targetWidget)
            set EnumStack.top.range=this.aoe
            set EnumStack.top.callback=filter
            set EnumStack.top.for=whichGroup
            call GroupEnumUnitsInRange(GROUP,EnumStack.top.x,EnumStack.top.y,EnumStack.top.range+MAX_UNIT_COLLISION_SIZE,Filter(function InternalEnum))
            call EnumStack.decrement()
        endmethod
        
        method enumUnitsInAoECaster takes group whichGroup, UnitMethodFilter filter returns nothing /*eg: method filter takes unit u returns boolean*/
            call EnumStack.increment()
            set EnumStack.top.instance=this
            set EnumStack.top.x=GetUnitX(this.caster)
            set EnumStack.top.y=GetUnitY(this.caster)
            set EnumStack.top.range=this.aoe
            set EnumStack.top.callback=filter
            set EnumStack.top.for=whichGroup
            call GroupEnumUnitsInRange(GROUP,EnumStack.top.x,EnumStack.top.y,EnumStack.top.range+MAX_UNIT_COLLISION_SIZE,Filter(function InternalEnum))
            call EnumStack.decrement()
        endmethod
        
        method forUnitsInAoE takes UnitMethod callback returns nothing /*eg: method callback takes unit u returns nothing*/
            call EnumStack.increment()
            set EnumStack.top.instance=this
            set EnumStack.top.x=this.targetX
            set EnumStack.top.y=this.targetY
            set EnumStack.top.range=this.aoe
            set EnumStack.top.callback=callback
            call GroupEnumUnitsInRange(GROUP,EnumStack.top.x,EnumStack.top.y,EnumStack.top.range+MAX_UNIT_COLLISION_SIZE,Filter(function InternalFor))
            call EnumStack.decrement()
        endmethod
        
        method forUnitsInAoETarget takes UnitMethod callback returns nothing /*eg: method callback takes unit u returns nothing*/
            call EnumStack.increment()
            set EnumStack.top.instance=this
            set EnumStack.top.x=GetWidgetX(this.targetWidget)
            set EnumStack.top.y=GetWidgetY(this.targetWidget)
            set EnumStack.top.range=this.aoe
            set EnumStack.top.callback=callback
            call GroupEnumUnitsInRange(GROUP,EnumStack.top.x,EnumStack.top.y,EnumStack.top.range+MAX_UNIT_COLLISION_SIZE,Filter(function InternalFor))
            call EnumStack.decrement()
        endmethod
        
        method forUnitsInAoECaster takes UnitMethod callback returns nothing /*eg: method callback takes unit u returns nothing*/
            call EnumStack.increment()
            set EnumStack.top.instance=this
            set EnumStack.top.x=GetUnitX(this.caster)
            set EnumStack.top.y=GetUnitY(this.caster)
            set EnumStack.top.range=this.aoe
            set EnumStack.top.callback=callback
            call GroupEnumUnitsInRange(GROUP,EnumStack.top.x,EnumStack.top.y,EnumStack.top.range+MAX_UNIT_COLLISION_SIZE,Filter(function InternalFor))
            call EnumStack.decrement()
        endmethod
        
        method isUnitInAoE takes unit u returns boolean
            return IsUnitInRangeXY(u,this.targetX,this.targetY,this.aoe)
        endmethod
        method isUnitInAoETarget takes unit u returns boolean
            return IsUnitInRangeXY(u,GetWidgetX(this.targetWidget),GetWidgetY(this.targetWidget),this.aoe)
        endmethod
        method isUnitInAoECaster takes unit u returns boolean
            return IsUnitInRangeXY(u,GetUnitX(this.caster),GetUnitY(this.caster),this.aoe)
        endmethod
        
        //===========================================================================
        // Auto Cleanup.
        //
        private boolean doAutoDestroy // set in module's create method.
        /*protected*/ boolean hasStoppedCasting=false
        method operator autoDestroy= takes boolean flag returns nothing
            if flag then
                if this.hasStoppedCasting and this.isNotLocked then
                    set this.hasStoppedCasting=false // double free safety
                    call this.destroy()
                else
                    set this.doAutoDestroy=true
                endif
            else
                set this.doAutoDestroy=false
            endif
        endmethod
        method operator autoDestroy takes nothing returns boolean
            return this.doAutoDestroy
        endmethod
        
        //===========================================================================
        // Locking.
        //
        private integer lockLevel=0
        method operator isLocked takes nothing returns boolean
            return this.lockLevel>0
        endmethod
        method operator isNotLocked takes nothing returns boolean
            return this.lockLevel==0
        endmethod
        method addLock takes nothing returns nothing
            set this.lockLevel=this.lockLevel+1
        endmethod
        method removeLock takes nothing returns nothing
            set this.lockLevel=this.lockLevel-1
            if this.hasStoppedCasting and this.isNotLocked and this.doAutoDestroy then
                set this.hasStoppedCasting=false // double free safety
                call this.destroy()
            endif
        endmethod
        
        //===========================================================================
        // Attachment.
        //
        private static ChainAttach attachNode
        private static ChainAttach attachHead
        
        //! textmacro SpellStruct__CreateAttachmentHead
            set thistype.attachHead=ChainAttach.create()
            set thistype.attachHead.next=thistype.attachHead
            set thistype.attachHead.prev=thistype.attachHead
        //! endtextmacro
        
        //! textmacro SpellStruct__CreateAttachmentNode takes HEAD
            // Create node.
            set thistype.attachNode=ChainAttach.create()
            // Link node (at end of list).
            set thistype.attachHead=$HEAD$
            set thistype.attachHead.prev.next=thistype.attachNode
            set thistype.attachNode.prev=thistype.attachHead.prev
            set thistype.attachHead.prev=thistype.attachNode
            set thistype.attachNode.next=thistype.attachHead
        //! endtextmacro
        
        //! textmacro SpellStruct__AttachmentChainLoop takes HEAD
            set thistype.attachHead=$HEAD$
            set thistype.attachNode=thistype.attachHead.next
            loop
                exitwhen thistype.attachNode==thistype.attachHead
        //! endtextmacro
        //! textmacro SpellStruct__AttachmentChainEndloop
                set thistype.attachNode=thistype.attachNode.next
            endloop
        //! endtextmacro
        
        //===========================================================================
        // Timers (requires Locking).
        //
        private ChainAttach timerAttachments
        private static method timerCallback takes nothing returns nothing
            set thistype.attachNode=GetTimerStruct(GetExpiredTimer()) // first time since h2i/gamecache I've needed this.
            call thistype.attachNode.callback.execute(thistype.attachNode.instance) // good reason to use T32 instead.
            // cannot use thistype.attachNode anymore, value may have changed.
        endmethod
        method startTimer takes Method callback, real period returns nothing
            debug if HaveSavedInteger(STORE,this,callback) then
            debug   call BJDebugMsg("SpellStruct Error: started periodic method twice for struct.")
            debug endif
            // Make node and attach data.
            //! runtextmacro SpellStruct__CreateAttachmentNode("this.timerAttachments")
            set thistype.attachNode.instance=this
            set thistype.attachNode.callback=callback
            // Create timer and attach data.
            //! runtextmacro SpellStruct__GetTimer("thistype.attachNode.timer")
            call SetTimerStruct(thistype.attachNode.timer,thistype.attachNode)
            // Attach node to struct/method.
            call SaveInteger(STORE,this,callback,thistype.attachNode)
            // Start timer.
            call TimerStart(thistype.attachNode.timer,period,true,function thistype.timerCallback)
            call this.addLock()
        endmethod
        method stopTimer takes Method callback returns nothing
            set thistype.attachNode=ChainAttach(LoadInteger(STORE,this,callback))
            // Unchain attachment
            set thistype.attachNode.next.prev=thistype.attachNode.prev
            set thistype.attachNode.prev.next=thistype.attachNode.next
            // Release Timer.
            //! runtextmacro SpellStruct__ReleaseTimer("thistype.attachNode.timer")
            call RemoveSavedInteger(STORE,this,callback)
            call this.removeLock()
        endmethod
        
        //===========================================================================
        // Triggers (requires Locking).
        //
        private ChainAttach triggerAttachments
        private static method triggerCallback takes nothing returns boolean
            set thistype.attachNode=GetTriggerStruct(GetTriggeringTrigger()) // first time since h2i/gamecache I've needed this.
            call thistype.attachNode.callback.execute(thistype.attachNode.instance) // good reason to use T32 instead.
            // cannot use thistype.attachNode anymore, value may have changed.
            return false
        endmethod
        method createTrigger takes Method callback returns trigger
            debug if HaveSavedInteger(STORE,this,callback) then
            debug   call BJDebugMsg("SpellStruct Error: created two triggers for the same method for struct.")
            debug endif
            // Make node and attach data.
            //! runtextmacro SpellStruct__CreateAttachmentNode("this.triggerAttachments")
            set thistype.attachNode.instance=this
            set thistype.attachNode.callback=callback
            // Create timer and attach data.
            set thistype.attachNode.trigger=CreateTrigger()
            call SetTriggerStruct(thistype.attachNode.trigger,thistype.attachNode)
            // Attach node to struct/method.
            call SaveInteger(STORE,this,callback,thistype.attachNode)
            // Init trigger.
            call TriggerAddCondition(thistype.attachNode.trigger,Filter(function thistype.triggerCallback))
            call this.addLock()
            return thistype.attachNode.trigger
        endmethod
        method destroyTrigger takes Method callback returns nothing
            set thistype.attachNode=ChainAttach(LoadInteger(STORE,this,callback))
            // Unchain attachment
            set thistype.attachNode.next.prev=thistype.attachNode.prev
            set thistype.attachNode.prev.next=thistype.attachNode.next
            // Destroy trigger
            //! runtextmacro SpellStruct__FlushTrigger("thistype.attachNode.trigger")
            call DestroyTrigger(thistype.attachNode.trigger)
            call RemoveSavedInteger(STORE,this,callback)
            call this.removeLock()
        endmethod
        
        //===========================================================================
        // Event responses.
        //
        readonly integer abilId
        readonly unit caster
        readonly integer level
        readonly integer order
        readonly unit targetUnit
        readonly widget targetWidget
        readonly destructable targetDest
        readonly item targetItem
        readonly location targetLoc
        readonly real targetX // for target point of AoE spells.
        readonly real targetY // for target point of AoE spells.
        readonly player owner
        
        method operator casterX takes nothing returns real
            return GetUnitX(this.caster)
        endmethod
        method operator casterY takes nothing returns real
            return GetUnitY(this.caster)
        endmethod
        
        private static location loc
        static method create takes nothing returns thistype
            local thistype this=thistype.allocate()
            
            //===========================================================================
            // Event responses.
            //
            set this.abilId=GetSpellAbilityId()
            set this.caster=GetTriggerUnit()
            set this.owner=GetOwningPlayer(this.caster)
            set this.level=GetUnitAbilityLevel(this.caster,this.abilId)
            set this.order=GetUnitCurrentOrder(this.caster)
            // Target stuff
            set this.targetUnit=GetSpellTargetUnit()
            if this.targetUnit==null then
                set this.targetDest=GetSpellTargetDestructable()
                if this.targetDest==null then
                    set this.targetItem=GetSpellTargetItem()
                    if this.targetItem==null then
                        set this.targetWidget=null
                        set thistype.loc=GetSpellTargetLoc()
                        if thistype.loc==null then
                            set this.targetX=GetUnitX(this.caster)
                            set this.targetY=GetUnitY(this.caster)
                        else
                            set this.targetX=GetLocationX(thistype.loc)
                            set this.targetY=GetLocationY(thistype.loc)
                            call RemoveLocation(thistype.loc)
                            set thistype.loc=null // worthwhile
                        endif
                    else
                        set this.targetWidget=this.targetItem
                        set this.targetX=GetItemX(this.targetItem)
                        set this.targetY=GetItemY(this.targetItem)
                    endif
                else
                    set this.targetWidget=this.targetDest
                    set this.targetItem=null
                    set this.targetX=GetWidgetX(this.targetDest) // shorter
                    set this.targetY=GetWidgetY(this.targetDest)
                endif
            else
                set this.targetWidget=this.targetUnit
                set this.targetDest=null
                set this.targetItem=null
                set this.targetX=GetUnitX(this.targetUnit)
                set this.targetY=GetUnitY(this.targetUnit)
            endif
            
            //===========================================================================
            // Attachment (timer & trigger).
            //
            //! runtextmacro SpellStruct__CreateAttachmentHead()
            set this.timerAttachments=thistype.attachHead
            //! runtextmacro SpellStruct__CreateAttachmentHead()
            set this.triggerAttachments=thistype.attachHead
            
            return this
        endmethod
        
        method destroy takes nothing returns nothing
            if this.hasStoppedCasting then
                set this.hasStoppedCasting=false // random double free protection on autoDestroy stuff.
            else
                call RemoveSavedInteger(STORE,this.abilId,GetHandleId(this.caster))
            endif
            
            //===========================================================================
            // Timers
            //
            //! runtextmacro SpellStruct__AttachmentChainLoop("this.timerAttachments")
                //! runtextmacro SpellStruct__ReleaseTimer("thistype.attachNode.timer")
                call RemoveSavedInteger(STORE,this,thistype.attachNode.callback)
            //! runtextmacro SpellStruct__AttachmentChainEndloop()
            call this.timerAttachments.destroy()
            
            //===========================================================================
            // Triggers
            //
            //! runtextmacro SpellStruct__AttachmentChainLoop("this.triggerAttachments")
                //! runtextmacro SpellStruct__FlushTrigger("thistype.attachNode.trigger")
                call DestroyTrigger(thistype.attachNode.trigger)
                call RemoveSavedInteger(STORE,this,thistype.attachNode.callback)
            //! runtextmacro SpellStruct__AttachmentChainEndloop()
            call this.triggerAttachments.destroy()
            
            call this.deallocate()
        endmethod
    endstruct
    
    //===========================================================================
    // Exposed Module - SpellStruct
    //
    module SpellStruct
        //===========================================================================
        // AoE enumeration.
        //
        static real defaultAoE=0.
        
        //===========================================================================
        // Setting up the struct.
        //
        private static integer currentAbil=0
        static method operator abil= takes integer abilId returns nothing
            if thistype.currentAbil!=0 then
                call RemoveSavedInteger(STORE,thistype.currentAbil,ALLOCATOR)
            endif
            set thistype.currentAbil=abilId
            if abilId!=0 then
                call SaveInteger(STORE,abilId,ALLOCATOR,thistype.create)
            endif
        endmethod
        static method operator abil takes nothing returns integer
            return thistype.currentAbil
        endmethod
        
        //===========================================================================
        // Auto Cleanup.
        //
        private static boolean doAutoDestroyDefault=true
        static method operator autoDestroyDefault= takes boolean flag returns nothing
            set thistype.doAutoDestroyDefault=flag
        endmethod
        static method operator autoDestroyDefault takes nothing returns boolean
            return thistype.doAutoDestroyDefault
        endmethod
        method cleanup takes nothing returns nothing // only runs if not destroyed
            call RemoveSavedInteger(STORE,this.abilId,GetHandleId(this.caster))
            set this.hasStoppedCasting=true // can't be readonly because of this.
            if this.autoDestroy and this.isNotLocked then
                call SpellStruct(this).destroy() // Jasshelper bug? Had to typecast "this".
            endif
        endmethod
        
        //===========================================================================
        // Struct Allocation.
        //
        private static method create takes nothing returns thistype
            local thistype this=thistype.allocate()
            // General stuff
            set this.autoDestroy=thistype.autoDestroyDefault
            set this.aoe=thistype.defaultAoE
            
            //static if thistype.onCreate!=DEFAULTS.onCreate then
                call this.onCreate.evaluate() // in case of thread terminate
            //endif
            return this
        endmethod
    endmodule
    
    //===========================================================================
    // Init
    //
    private struct Init extends array
        private static method onInit takes nothing returns nothing
            //===========================================================================
            // Event Responses
            //
            local trigger t
            
            //! textmacro SpellStruct__RegisterEvent takes EVENT, FUNCTION
                set t=CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t,$EVENT$)
                call TriggerAddCondition(t,Filter(function $FUNCTION$))
            //! endtextmacro
            //! runtextmacro SpellStruct__RegisterEvent("EVENT_PLAYER_UNIT_SPELL_CHANNEL","OnChannel")
            //! runtextmacro SpellStruct__RegisterEvent("EVENT_PLAYER_UNIT_SPELL_CAST","OnStartCast")
            //! runtextmacro SpellStruct__RegisterEvent("EVENT_PLAYER_UNIT_SPELL_EFFECT","OnEffect")
            //! runtextmacro SpellStruct__RegisterEvent("EVENT_PLAYER_UNIT_SPELL_FINISH","OnFinish")
            //! runtextmacro SpellStruct__RegisterEvent("EVENT_PLAYER_UNIT_SPELL_ENDCAST","OnStopCast")
            
            set t=null
        endmethod
    endstruct
endlibrary
So, I started getting my hands into hashtables, and came up with a minimalistic interface for writing spells. This is designed for rapid map development, and has an efficiency bonus with event responses. It fixes all kinds of WC3 errors, like broken event responses, broken AoE detection, O(n) events for spells, and it also provides simple attachment to a unit for the lifetime of a spell.

This is not an efficiency based system. It is not inefficient, but it is slightly heavyweight in some areas to ensure it runs stably and supports all reasonable uses, and cleans up even mild WC3 errors and provides strong functionality.

I intend to build on this in future. For now, there is a JassHelper bug that stands in the way of this system until I find a work-around or it is resolved. That bug is that all methods must be implemented - they are not optional as intended.


Demonstration #1 (Life Drain):
JASS:
struct LifeDrain extends SpellStruct
    implement SpellStruct
    
    // The following ripped from <a href="http://www.thehelper.net/forums/showthread.php?t=133865" class="link link--internal">http://www.thehelper.net/forums/showthread.php?t=133865</a>
    private static method setDistanceToUnit takes unit from, unit to, real dist returns nothing
        local real x=GetUnitX(to)-GetUnitX(from)
        local real y=GetUnitY(to)-GetUnitY(from)
        local real factor=dist/SquareRoot(x*x+y*y)
        call SetUnitX(to,GetUnitX(from)+x*factor)
        call SetUnitY(to,GetUnitY(from)+y*factor)
    endmethod
    
    private method periodic takes nothing returns nothing
        //if not this.isUnitInAoECaster(this.targetUnit) then
            call thistype.setDistanceToUnit(this.targetUnit,this.caster,this.aoe)
        //endif
    endmethod
    
    implement T32x
    
    method onEffect takes nothing returns nothing
        call this.startPeriodic()
    endmethod
    method onFinish takes nothing returns nothing
        call this.stopPeriodic()
    endmethod
    
    private static method onInit takes nothing returns nothing
        set thistype.abil=&#039;ANdr&#039;
        set thistype.defaultAoE=500.0
    endmethod
endstruct
Demonstration #2 (Time Bomb):
JASS:
struct DeathCoil extends SpellStruct
    implement SpellStruct
    
    method onCreate takes nothing returns nothing
        set this.aoe=this.level*200
    endmethod
    
    private method kill takes unit u returns nothing
        // note that you have access to everything in the struct in these for group methods.
        call KillUnit(u)
    endmethod
    private method afterWait takes nothing returns nothing
        call this.forUnitsInAoETarget(thistype.kill)
        call this.stopTimer(thistype.afterWait)
    endmethod
    private method onEffect takes nothing returns nothing
        //call BJDebugMsg(GetUnitName(this.caster)+&quot; deathcoiled &quot;+GetUnitName(this.targetUnit))
        call this.startTimer(thistype.afterWait,2.0)
    endmethod
    
    private static method onInit takes nothing returns nothing
        set thistype.abil=&#039;AUdc&#039;
    endmethod
endstruct
This was inspired mostly by my lack of inspiration to map. :D

Updates:
- Version 1.0.7: Made the .createTrigger() method actually return the trigger. Documented that you must call SpellStruct(this).destroy() to destroy due to a JassHelper bug.
- Version 1.0.6: Removed a bug that caused errors when casting abilities that were not SpellStruct based.
- Version 1.0.5 (BETA): Updated documentation. Made .targetX and .targetY set to the caster's x and y in spells that target neither a widget or point. Added .getAngleToTargetWidget() and .getAngleToTargetPoint().
- Version 1.0.4 (BETA): Got event response methods to be optional.
- Version 1.0.3 (BETA): Added .hasStoppedCasting, made the struct get destroyed when autoDestroy is set to true after the struct would normally be destroyed. Added .addLock() and .removeLock(), and .isNotLocked. Added .startTimer(method, timeout), .stopTimer(method), .createTrigger(method) and .destroyTrigger(method). Added .abilId event response. Fixed .forUnitsInAoE and .enumUnitsInAoE which worked in the demo by sheer chance (struct id happened to be the method id, was using the wrong one). Fixed onStopCast being called twice and structs never being auto-destroyed. Added support for careless destruction of the struct - all triggers and timers attached to will also immediate cease, and no more event responses shall occur.
- Version 1.0.2 (BETA): Fixed stack incrementing instead of decrementing.
- Version 1.0.1 (BETA): Added .owner, .casterX, .casterY, .getDistanceToTargetWidget(), .getDistanceToTargetPoint(), and split some functionality into the base SpellStruct to prevent code duplication.
- Version 1.0.0 (BETA): Release.
 

Attachments

  • SpellStruct.w3x
    38.7 KB · Views: 572

Narks

Vastly intelligent whale-like being from the stars
Reaction score
90
That looks very cool. I think.

Could this possibly be integrated with a projectile system or something?
 

Viikuna

No Marlo no game.
Reaction score
265
Hrm, thats kinda cool, but for some reason I just dont liken those group enum methods in that module.

The amount of code that generates gives me nightmares.

Wouldnt it be easier and better to have some simple library for that with one cool GroupEnumUnitsInArea -user function for doing that "takes units collinsion size in account" -stuff?

It would be more flexible and people will probably need it in situations where they cant use this module.
 

Jesus4Lyf

Good Idea™
Reaction score
397
>for some reason I just dont liken those group enum methods in that module.
>The amount of code that generates gives me nightmares.
I intend to change the interface it extends to a struct, and implement those methods only once. That will make the code display once, and I can do the same with the event responses, reducing array duplication. Remember, it's beta - I'm kind of scouting for interest. ;)

>Could this possibly be integrated with a projectile system or something?
You can integrate it however you like. What you could do for ease of mapping and 100% efficiency is implement T32 as well as SpellStruct and make a periodic method which does projectile stuff (set autoDestroy to false). Then you have all the event responses in your projectile.

I should add a way to get a unit vector to the target... :thup:
 

kingkingyyk3

Visitor (Welcome to the Jungle, Baby!)
Reaction score
216
What is that meant to be? o_O
Filter out the units which you want to damage/add special effects/etc.
 

Romek

Super Moderator
Reaction score
963
> all methods must be implemented - they are not optional as intended.
Use a struct interface instead of a delegate?

And using both a module and extending a struct..? :thdown:

Anyway, I think that this seems pretty overkill.
I can't imagine using this at all, and I don't think I'd recommend anyone to use this. In fact, I'm surprised you made something like this. :p
I was expecting something more creative. x)
 

Nestharus

o-o
Reaction score
84
Useful for quickly creating spells, but I don't know that this does everything ; )


Def well done though for generic spell design I think : o.

I don't think that it's overkill considering you are trying to catch every situation : ). Also it doesn't have a massive learning curve like some of the stuff I've done and it def has a decent interface : ).
 

Jesus4Lyf

Good Idea™
Reaction score
397
>Use a struct interface instead of a delegate?
Doesn't work. Same error.

>And using both a module and extending a struct..?
Is there a better way to get the methods of a child struct as objects?

>Anyway, I think that this seems pretty overkill.
In what way?

>I can't imagine using this at all, and I don't think I'd recommend anyone to use this. In fact, I'm surprised you made something like this.
I thought that at first, too. Now I can't imagine not using this. ;)

>I was expecting something more creative. x)
See Transport and Abduct. :D
 

Kenny

Back for now.
Reaction score
202
I have to admit, the first time I read over this, I had the exact same thoughts as Romek, initially thinking that this was a spin off of something like the caster system and xe. However, after reading over it some more, it has grown on me a fair bit.

The layout seems quite intuitive and simple, which is awesome.

One thing I dislike it that you need to extend SpellStruct as well. Which kinda of makes using this with my projectile system quite difficult.

Can you explain something to me though..

JASS:
.
    private struct DEFAULTS extends array
        // For methods that are not implemented.
        // In order of firing.
        method onCreate takes nothing returns nothing
        endmethod
        method onChannel takes nothing returns nothing
        endmethod
        method onStartCast takes nothing returns nothing
        endmethod
        method onEffect takes nothing returns nothing // May not fire
        endmethod
        method onFinish takes nothing returns nothing // Target response fails, may not fire
        endmethod
        method onStopCast takes nothing returns nothing // Target response fails sometimes
        endmethod
    endstruct


What does all this "May not fire" and "fails sometimes" mean?

Also, why can't you use a method interface for onChannel and all that? Couldn't you use something like [ljass]if this.onChannel.exists then[/ljass]? I don't see why you can't make the methods optional using something like that.
 

Jesus4Lyf

Good Idea™
Reaction score
397
>What does all this "May not fire" and "fails sometimes" mean?
Oops, those were my notes from constructing this.
May not fire means in any given casting, it may be cancelled and the event never occur.
Fails sometimes means the event responses don't work properly in WC3.

>One thing I dislike it that you need to extend SpellStruct as well.
Originally, you didn't have to. But it hails some pretty nasty code duplications.

>Also, why can't you use a method interface for onChannel and all that? Couldn't you use something like if this.onChannel.exists then? I don't see why you can't make the methods optional using something like that.
Results...
JASS:
//
//
//      Spell Struct
//          By Jesus4Lyf.
//       Version 1.0.2 BETA.
//
//  Turns spells into structs.
//  SpellStructs last the duration of the spell, unless explicitly told otherwise.
//
//  Use:
/*
    struct MySpell extends SpellStruct // must extend SpellStruct and...
        implement SpellStruct // must implement SpellStruct.
        
        // In any methods you may access these event responses:
        //unit caster
        //integer level
        //integer order
        //unit targetUnit
        //widget targetWidget
        //destructable targetDest
        //item targetItem
        //real targetX
        //real targetY
        //player owner
        
        // There is also some convenience methods for you:
        //real casterX
        //real casterY
        //real getDistanceToTargetWidget()
        //real getDistanceToTargetPoint()
        
        // You may flag:
        
        // Will be optional when JassHelper recieves a fix:
        method onCreate takes nothing returns nothing // fires just before channel
            // If this is an AoE spell...
            set this.aoe=this.level*200
            
            // Maybe you want to control the lifetime of the struct yourself:
            set this.autoDestroy=false // there is a thistype.autoDestroyDefault too,
                                       // which is true by default.
                                       // So you can conditionally control the lifetime.
                                       // Do not destroy structs before the spell ends.
        endmethod
        // Will be optional when JassHelper recieves a fix:
        method onChannel takes nothing returns nothing
        endmethod
        // Will be optional when JassHelper recieves a fix:
        method onStartCast takes nothing returns nothing
        endmethod
        // Will be optional when JassHelper recieves a fix:
        method onEffect takes nothing returns nothing // May not fire for a given spell instance.
        endmethod
        // Will be optional when JassHelper recieves a fix:
        method onFinish takes nothing returns nothing // May not fire for a given spell instance.
        endmethod
        // Will be optional when JassHelper recieves a fix:
        method onStopCast takes nothing returns nothing
        endmethod
        
        // There are lots of wonderful things you can do with enumerating within
        // AoE of a spell. I will document this some other time...
        
        private static method onInit takes nothing returns nothing
            // This is vital: It defines what ability this struct is for.
            set thistype.abil=&#039;AUdc&#039;
            
            // This is optional:
            set thistype.autoDestroyDefault=true // set to true by default.
                                                 // may be overridden for each
                                                 // instance by setting
                                                 // this.autoDestroy=true/false
            // AutoDestroyed structs are destroyed after the spell stops casting.
            
            set thistype.defaultAoE=200.0 // optional.
        endmethod
    endstruct
*/
//
//  Note that this system is not heavy on efficiency, but is not laggy either.
//  It is designed for rapid map development.
//
library SpellStruct
    //===========================================================================
    // Configurables
    //
    globals
        private constant real MAX_UNIT_COLLISION_SIZE=256.0
    endglobals
    
    //===========================================================================
    // Header Declarations
    //
    globals
        private group GROUP=CreateGroup()
    endglobals
    
    private function interface Method takes integer this returns nothing
    private function interface UnitMethodFilter takes integer this, unit u returns boolean
    private function interface UnitMethod takes integer this, unit u returns nothing
    private function interface Allocator takes nothing returns integer
    
    //===========================================================================
    // Recursion stack
    //
    private module Stack
        static thistype top=0 // thistype(0) throws a syntax error, does not compile
        static method increment takes nothing returns nothing
            set thistype.top=thistype(thistype.top+1)
        endmethod
        static method decrement takes nothing returns nothing
            set thistype.top=thistype(thistype.top-1)
        endmethod
    endmodule
    
    //===========================================================================
    // Defaults
    //
    private struct DEFAULTS extends array
        // For methods that are not implemented.
        // In order of firing.
        method onCreate takes nothing returns nothing
        endmethod
        method onChannel takes nothing returns nothing
        endmethod
        method onStartCast takes nothing returns nothing
        endmethod
        method onEffect takes nothing returns nothing // May not fire
        endmethod
        method onFinish takes nothing returns nothing // Target response fails, may not fire
        endmethod
        method onStopCast takes nothing returns nothing // Target response fails sometimes
        endmethod
    endstruct
    
    //===========================================================================
    // Event Handlers
    //
    globals
        private hashtable STORE=InitHashtable()
        private constant key METHOD_ON_CHANNEL
        private constant key METHOD_ON_START_CAST
        private constant key METHOD_ON_EFFECT
        private constant key METHOD_ON_FINISH
        private constant key METHOD_ON_STOP_CAST
        private constant key METHOD_CLEANUP
        private constant key ALLOCATOR
        private constant integer FIRST_EVENT=METHOD_ON_CHANNEL
        private constant integer FINISH_EVENT=METHOD_ON_STOP_CAST
    endglobals
    
    globals//locals
        private integer ThisCastData
        private integer CastingAbility
    endglobals
    //! textmacro SpellStruct__EventResponse takes METHOD_KEY, FUNCTION_NAME, FIRST_EVENT, LAST_EVENT
        private function $FUNCTION_NAME$ takes nothing returns boolean
            set CastingAbility=GetSpellAbilityId()
            static if $FIRST_EVENT$ then
                set ThisCastData=Allocator(LoadInteger(STORE,CastingAbility,ALLOCATOR)).evaluate()
                call SaveInteger(STORE,CastingAbility,GetHandleId(GetTriggerUnit()),ThisCastData)
            else
                set ThisCastData=LoadInteger(STORE,CastingAbility,GetHandleId(GetTriggerUnit()))
            endif
            call Method(LoadInteger(STORE,CastingAbility,$METHOD_KEY$)).execute(ThisCastData)
            static if $LAST_EVENT$ then
                call Method(LoadInteger(STORE,CastingAbility,$METHOD_KEY$)).evaluate(ThisCastData)
            endif
            return false
        endfunction
    //! endtextmacro
    //! runtextmacro SpellStruct__EventResponse(&quot;METHOD_ON_CHANNEL&quot;,&quot;OnChannel&quot;,&quot;true&quot;,&quot;false&quot;)
    //! runtextmacro SpellStruct__EventResponse(&quot;METHOD_ON_START_CAST&quot;,&quot;OnStartCast&quot;,&quot;false&quot;,&quot;false&quot;)
    //! runtextmacro SpellStruct__EventResponse(&quot;METHOD_ON_EFFECT&quot;,&quot;OnEffect&quot;,&quot;false&quot;,&quot;false&quot;)
    //! runtextmacro SpellStruct__EventResponse(&quot;METHOD_ON_FINISH&quot;,&quot;OnFinish&quot;,&quot;false&quot;,&quot;false&quot;)
    //! runtextmacro SpellStruct__EventResponse(&quot;METHOD_ON_STOP_CAST&quot;,&quot;OnStopCast&quot;,&quot;false&quot;,&quot;true&quot;)
    
    //===========================================================================
    // AoE enumeration.
    //
    private struct EnumStack extends array
        implement Stack
        integer instance
        real x
        real y
        real range
        integer callback
        group for
    endstruct
    private function InternalEnum takes nothing returns boolean // Could be recursive.
        local unit u=GetFilterUnit() // can&#039;t be a global due to recursion.
        if IsUnitInRangeXY(u,EnumStack.top.x,EnumStack.top.y,EnumStack.top.range) then // Factors in collision sizes.
            if UnitMethodFilter(EnumStack.top.instance).evaluate(EnumStack.top.instance,u) then
                call GroupAddUnit(EnumStack.top.for,u)
            endif
        endif
        set u=null
        return false
    endfunction
    private function InternalFor takes nothing returns boolean // Could be recursive.
        local unit u=GetFilterUnit() // can&#039;t be a global due to recursion.
        if IsUnitInRangeXY(u,EnumStack.top.x,EnumStack.top.y,EnumStack.top.range) then // Factors in collision sizes.
            call UnitMethod(EnumStack.top.instance).execute(EnumStack.top.instance,u)
        endif
        set u=null
        return false
    endfunction
    
    //===========================================================================
    // Exposed Interface - SpellStruct
    //
    private interface Defaults
        method onChannel takes nothing returns nothing defaults nothing
    endinterface
    struct SpellStruct extends Defaults
        //===========================================================================
        // AoE enumeration.
        //
        real aoe
        
        method enumUnitsInAoE takes group whichGroup, UnitMethodFilter filter returns nothing /*eg: method filter takes unit u returns boolean*/
            call EnumStack.increment()
            set EnumStack.top.instance=this
            set EnumStack.top.x=this.targetX
            set EnumStack.top.y=this.targetY
            set EnumStack.top.range=this.aoe
            set EnumStack.top.callback=filter
            set EnumStack.top.for=whichGroup
            call GroupEnumUnitsInRange(GROUP,this.targetX,this.targetY,this.aoe+MAX_UNIT_COLLISION_SIZE,Filter(function InternalEnum))
            call EnumStack.decrement()
        endmethod
        
        method enumUnitsInAoETarget takes group whichGroup, UnitMethodFilter filter returns nothing /*eg: method filter takes unit u returns boolean*/
            call EnumStack.increment()
            set EnumStack.top.instance=this
            set EnumStack.top.x=GetWidgetX(this.targetWidget)
            set EnumStack.top.y=GetWidgetY(this.targetWidget)
            set EnumStack.top.range=this.aoe
            set EnumStack.top.callback=filter
            set EnumStack.top.for=whichGroup
            call GroupEnumUnitsInRange(GROUP,this.targetX,this.targetY,this.aoe+MAX_UNIT_COLLISION_SIZE,Filter(function InternalEnum))
            call EnumStack.decrement()
        endmethod
        
        method enumUnitsInAoECaster takes group whichGroup, UnitMethodFilter filter returns nothing /*eg: method filter takes unit u returns boolean*/
            call EnumStack.increment()
            set EnumStack.top.instance=this
            set EnumStack.top.x=GetUnitX(this.caster)
            set EnumStack.top.y=GetUnitY(this.caster)
            set EnumStack.top.range=this.aoe
            set EnumStack.top.callback=filter
            set EnumStack.top.for=whichGroup
            call GroupEnumUnitsInRange(GROUP,this.targetX,this.targetY,this.aoe+MAX_UNIT_COLLISION_SIZE,Filter(function InternalEnum))
            call EnumStack.decrement()
        endmethod
        
        method forUnitsInAoE takes UnitMethod callback returns nothing /*eg: method callback takes unit u returns nothing*/
            call EnumStack.increment()
            set EnumStack.top.instance=this
            set EnumStack.top.x=this.targetX
            set EnumStack.top.y=this.targetY
            set EnumStack.top.range=this.aoe
            set EnumStack.top.callback=callback
            call GroupEnumUnitsInRange(GROUP,this.targetX,this.targetY,this.aoe+MAX_UNIT_COLLISION_SIZE,Filter(function InternalFor))
            call EnumStack.decrement()
        endmethod
        
        method forUnitsInAoETarget takes UnitMethod callback returns nothing /*eg: method callback takes unit u returns nothing*/
            call EnumStack.increment()
            set EnumStack.top.instance=this
            set EnumStack.top.x=GetWidgetX(this.targetWidget)
            set EnumStack.top.y=GetWidgetY(this.targetWidget)
            set EnumStack.top.range=this.aoe
            set EnumStack.top.callback=callback
            call GroupEnumUnitsInRange(GROUP,this.targetX,this.targetY,this.aoe+MAX_UNIT_COLLISION_SIZE,Filter(function InternalFor))
            call EnumStack.decrement()
        endmethod
        
        method forUnitsInAoECaster takes UnitMethod callback returns nothing /*eg: method callback takes unit u returns nothing*/
            call EnumStack.increment()
            set EnumStack.top.instance=this
            set EnumStack.top.x=GetUnitX(this.caster)
            set EnumStack.top.y=GetUnitY(this.caster)
            set EnumStack.top.range=this.aoe
            set EnumStack.top.callback=callback
            call GroupEnumUnitsInRange(GROUP,this.targetX,this.targetY,this.aoe+MAX_UNIT_COLLISION_SIZE,Filter(function InternalFor))
            call EnumStack.decrement()
        endmethod
        
        method isUnitInAoE takes unit u returns boolean
            return IsUnitInRangeXY(u,this.targetX,this.targetY,this.aoe)
        endmethod
        method isUnitInAoETarget takes unit u returns boolean
            return IsUnitInRangeXY(u,GetWidgetX(this.targetWidget),GetWidgetY(this.targetWidget),this.aoe)
        endmethod
        method isUnitInAoECaster takes unit u returns boolean
            return IsUnitInRangeXY(u,GetUnitX(this.caster),GetUnitY(this.caster),this.aoe)
        endmethod
        
        //===========================================================================
        // Event responses.
        //
        readonly unit caster
        readonly integer level
        readonly integer order
        readonly unit targetUnit
        readonly widget targetWidget
        readonly destructable targetDest
        readonly item targetItem
        readonly real targetX
        readonly real targetY
        readonly player owner
        
        method operator casterX takes nothing returns real
            return GetUnitX(this.caster)
        endmethod
        method operator casterY takes nothing returns real
            return GetUnitY(this.caster)
        endmethod
        
        static method create takes nothing returns thistype
            local thistype this=thistype.allocate()
            
            // Event Responses
            set this.caster=GetTriggerUnit()
            set this.owner=GetOwningPlayer(this.caster)
            set this.level=GetUnitAbilityLevel(this.caster,GetSpellAbilityId())
            set this.order=GetUnitCurrentOrder(this.caster)
            // Target stuff
            set this.targetUnit=GetSpellTargetUnit()
            if this.targetUnit==null then
                set this.targetDest=GetSpellTargetDestructable()
                if this.targetDest==null then
                    set this.targetItem=GetSpellTargetItem()
                    if this.targetItem==null then
                        set this.targetWidget=null
                        set this.targetX=GetSpellTargetX()
                        set this.targetY=GetSpellTargetY()
                    else
                        set this.targetWidget=this.targetItem
                        set this.targetX=GetItemX(this.targetItem)
                        set this.targetY=GetItemY(this.targetItem)
                    endif
                else
                    set this.targetWidget=this.targetDest
                    set this.targetItem=null
                    set this.targetX=GetWidgetX(this.targetDest) // shorter
                    set this.targetY=GetWidgetY(this.targetDest)
                endif
            else
                set this.targetWidget=this.targetUnit
                set this.targetDest=null
                set this.targetItem=null
                set this.targetX=GetUnitX(this.targetUnit)
                set this.targetY=GetUnitY(this.targetUnit)
            endif
            
            return this
        endmethod
        
        //===========================================================================
        // For free:
        //
        method getDistanceToTargetWidget takes nothing returns real
            local real x=GetWidgetX(this.targetWidget)-this.casterX
            local real y=GetWidgetY(this.targetWidget)-this.casterY
            return SquareRoot(x*x+y*y)
        endmethod
        method getDistanceToTargetPoint takes nothing returns real
            local real x=this.targetX-this.casterX
            local real y=this.targetY-this.casterY
            return SquareRoot(x*x+y*y)
        endmethod
    endstruct
    
    //===========================================================================
    // Exposed Module - SpellStruct
    //
    module SpellStruct
        //===========================================================================
        // AoE enumeration.
        //
        static real defaultAoE=0.
        
        //===========================================================================
        // For unused methods.
        //
        private static delegate DEFAULTS default=0
        
        //===========================================================================
        // Setting up the struct.
        //
        private static integer currentAbil=0
        static method operator abil= takes integer abilId returns nothing
            if thistype.currentAbil!=0 then
                call FlushChildHashtable(STORE,thistype.currentAbil)
                //call RemoveSavedInteger(STORE,thistype.currentAbil,ALLOCATOR)
                //call RemoveSavedInteger(STORE,thistype.currentAbil,METHOD_ON_CHANNEL)
                //call RemoveSavedInteger(STORE,thistype.currentAbil,METHOD_ON_START_CAST)
                //call RemoveSavedInteger(STORE,thistype.currentAbil,METHOD_ON_EFFECT)
                //call RemoveSavedInteger(STORE,thistype.currentAbil,METHOD_ON_FINISH)
                //call RemoveSavedInteger(STORE,thistype.currentAbil,METHOD_ON_STOP_CAST)
                //call RemoveSavedInteger(STORE,thistype.currentAbil,METHOD_CLEANUP)
            endif
            set thistype.currentAbil=abilId
            if abilId!=0 then
                call SaveInteger(STORE,abilId,ALLOCATOR,thistype.create)
                //! textmacro SpellStruct__StoreMethod takes METHOD_KEY, METHOD_NAME
                    //static if thistype.$METHOD_NAME$!=DEFAULTS.$METHOD_NAME$ then
                    if thistype.$METHOD_NAME$.exists then
                        call SaveInteger(STORE,abilId,$METHOD_KEY$,thistype.$METHOD_NAME$)
                    endif
                    //endif
                //! endtextmacro
                //! runtextmacro SpellStruct__StoreMethod(&quot;METHOD_ON_CHANNEL&quot;,&quot;onChannel&quot;)
                //! runtextmacro SpellStruct__StoreMethod(&quot;METHOD_ON_START_CAST&quot;,&quot;onStartCast&quot;)
                //! runtextmacro SpellStruct__StoreMethod(&quot;METHOD_ON_EFFECT&quot;,&quot;onEffect&quot;)
                //! runtextmacro SpellStruct__StoreMethod(&quot;METHOD_ON_FINISH&quot;,&quot;onFinish&quot;)
                //! runtextmacro SpellStruct__StoreMethod(&quot;METHOD_ON_STOP_CAST&quot;,&quot;onStopCast&quot;)
                call SaveInteger(STORE,abilId,METHOD_CLEANUP,thistype.cleanup)
            endif
        endmethod
        static method operator abil takes nothing returns integer
            return thistype.currentAbil
        endmethod
        
        //===========================================================================
        // Auto Cleanup.
        //
        private static boolean doAutoDestroyDefault=true
        static method operator autoDestroyDefault= takes boolean flag returns nothing
            set thistype.doAutoDestroyDefault=flag
        endmethod
        static method operator autoDestroyDefault takes nothing returns boolean
            return thistype.doAutoDestroyDefault
        endmethod
        private boolean doAutoDestroy
        method operator autoDestroy= takes boolean flag returns nothing
            set this.doAutoDestroy=flag
        endmethod
        method operator autoDestroy takes nothing returns boolean
            return this.doAutoDestroy
        endmethod
        private method cleanup takes nothing returns nothing
            call RemoveSavedInteger(STORE,thistype.currentAbil,GetHandleId(this.caster))
            if this.autoDestroy then
                call this.destroy()
            endif
        endmethod
        
        //===========================================================================
        // Struct Allocation.
        //
        private static method create takes nothing returns thistype
            local thistype this=thistype.allocate()
            // General stuff
            set this.autoDestroy=thistype.autoDestroyDefault
            set this.aoe=thistype.defaultAoE
            
            //static if thistype.onCreate!=DEFAULTS.onCreate then
                call this.onCreate.evaluate() // in case of thread terminate
            //endif
            return this
        endmethod
    endmodule
    
    //===========================================================================
    // Init
    //
    private struct Init extends array
        private static method onInit takes nothing returns nothing
            local trigger t
            
            //! textmacro SpellStruct__RegisterEvent takes EVENT, FUNCTION
                set t=CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t,$EVENT$)
                call TriggerAddCondition(t,Filter(function $FUNCTION$))
            //! endtextmacro
            //! runtextmacro SpellStruct__RegisterEvent(&quot;EVENT_PLAYER_UNIT_SPELL_CHANNEL&quot;,&quot;OnChannel&quot;)
            //! runtextmacro SpellStruct__RegisterEvent(&quot;EVENT_PLAYER_UNIT_SPELL_CAST&quot;,&quot;OnStartCast&quot;)
            //! runtextmacro SpellStruct__RegisterEvent(&quot;EVENT_PLAYER_UNIT_SPELL_EFFECT&quot;,&quot;OnEffect&quot;)
            //! runtextmacro SpellStruct__RegisterEvent(&quot;EVENT_PLAYER_UNIT_SPELL_FINISH&quot;,&quot;OnFinish&quot;)
            //! runtextmacro SpellStruct__RegisterEvent(&quot;EVENT_PLAYER_UNIT_SPELL_ENDCAST&quot;,&quot;OnStopCast&quot;)
            
            set t=null
        endmethod
    endstruct
endlibrary

JASS:
struct DeathCoil extends SpellStruct
    implement SpellStruct
    
    method onCreate takes nothing returns nothing
        set this.aoe=this.level*200
    endmethod
    method onChannel takes nothing returns nothing
    endmethod
    method onStartCast takes nothing returns nothing
    endmethod
    method onEffect takes nothing returns nothing // Target response fails, may not fire
    endmethod
    method onStopCast takes nothing returns nothing // Target response fails sometimes
    endmethod
    
    private method kill takes unit u returns nothing
        // note that you have access to everything in the struct in these for group methods.
        call KillUnit(u)
    endmethod
    private method onFinish takes nothing returns nothing
        call BJDebugMsg(GetUnitName(this.caster)+&quot; deathcoiled &quot;+GetUnitName(this.targetUnit))
        call this.forUnitsInAoETarget(thistype.kill)
    endmethod
    
    private static method onInit takes nothing returns nothing
        set thistype.abil=&#039;AUdc&#039;
    endmethod
endstruct

Error: [internal error] Malformed method passing data, report this bug.
Line: [LJASS]if thistype.onChannel.exists then[/LJASS]
-.-

Edit: I'd like someone to link in the JassHelper thread to here because JassHelper actually asked me to report the bug. So if someone could do that for Vex cos he's got me banned still...

Now, Romek...
>Anyway, I think that this seems pretty overkill.
>I can't imagine using this at all, and I don't think I'd recommend anyone to use this.
See the following:
JASS:
struct LifeDrain extends SpellStruct
    implement SpellStruct
    
    // The following ripped from <a href="http://www.thehelper.net/forums/showthread.php?t=133865" class="link link--internal">http://www.thehelper.net/forums/showthread.php?t=133865</a>
    private static method setDistanceToUnit takes unit from, unit to, real dist returns nothing
        local real x=GetUnitX(to)-GetUnitX(from)
        local real y=GetUnitY(to)-GetUnitY(from)
        local real factor=dist/SquareRoot(x*x+y*y)
        call SetUnitX(to,GetUnitX(from)+x*factor)
        call SetUnitY(to,GetUnitY(from)+y*factor)
    endmethod
    
    private method periodic takes nothing returns nothing // should inline, lol
        //if not this.isUnitInAoECaster(this.targetUnit) then
            call thistype.setDistanceToUnit(this.targetUnit,this.caster,this.aoe)
        //endif
    endmethod
    
    implement T32x
    
    method onEffect takes nothing returns nothing
        call this.startPeriodic()
    endmethod
    method onFinish takes nothing returns nothing
        call this.stopPeriodic()
    endmethod
    
    // Unneeded.
    method onCreate takes nothing returns nothing
    endmethod
    method onChannel takes nothing returns nothing
    endmethod
    method onStartCast takes nothing returns nothing
    endmethod
    method onStopCast takes nothing returns nothing
    endmethod
    
    private static method onInit takes nothing returns nothing
        set thistype.abil=&#039;ANdr&#039;
        set thistype.defaultAoE=500.0
    endmethod
endstruct
How would you code that? Think hard.
It took me more time to copy paste the resources than to code. This system (along with T32x) cut down the fact that I'd need to do unit and timer attachment, associate the two, save event responses (like casting unit/target), declare WC3 events, create/destroy structs, and still facilitate the code for sliding a unit in some logical way...

Not only that, but you'd be hard pressed to optimise this further in any practical way. It runs straight on T32x.

The whole thing took under a minute to write. Because all I had to write was this:
JASS:
private method periodic takes nothing returns nothing // should inline, lol
        //if not this.isUnitInAoECaster(this.targetUnit) then
            call thistype.setDistanceToUnit(this.targetUnit,this.caster,this.aoe)
        //endif
    endmethod
    
    implement T32x
    
    method onEffect takes nothing returns nothing
        call this.startPeriodic()
    endmethod
    method onFinish takes nothing returns nothing
        call this.stopPeriodic()
    endmethod
    
    private static method onInit takes nothing returns nothing
        set thistype.abil=&#039;ANdr&#039;
        set thistype.defaultAoE=500.0
    endmethod

The rest was copy/paste. :)
 

Romek

Super Moderator
Reaction score
963
> Doesn't work. Same error.
You sure about that? UAC somehow manages to implement optional methods using a struct interface without errors.

> How would you code that? Think hard.
Same as with that, but with a two triggers and unit indexing (or attachment). Not a great difference in my opinion. It'd probably end up with less code than this too - that module creates a lot of unneeded code.
I don't know how that'd compare in terms of efficiency, as I haven't read your system code yet.
 

Jesus4Lyf

Good Idea™
Reaction score
397
>probably end up with less code than this too - that module creates a lot of unneeded code.
It implements 3 short methods (the rest will inline to struct members).
Remember the focus on this is development time. :)
Thanks for your feedback.

>You sure about that? UAC somehow manages to implement optional methods using a struct interface without errors.
Does it try to pass those optional members as objects?
 

Executor

I see you
Reaction score
57

Jesus4Lyf

Good Idea™
Reaction score
397
That would be a static integer on the parent type, making this fail. Each spell would write to the same global variable. It is possible to get around this, however, by moving it to the child structs with the module...

I'm not really sure if it is worth it. Do you know you can change the ability that a struct type is for during the game? :p Would cause this feature to break slightly (even though it is undocumented as I am not totally sure how reliable it would be to do such a thing...).
 

Jesus4Lyf

Good Idea™
Reaction score
397
I've added a .hasStoppedCasting member, and made setting .autoDestroy to true immediately destroy the struct if it would have already done so by then.

I'm happy to take more requests. There is no reason this should not be a monster system, because you can simply not use the features you don't feel like using. Their availability doesn't impede on anything else...

Anything that would make spell creation faster belongs, so let's hear things. :)

I'm intending to implement an optional TU_start(method, period) method at the moment. Along with it, a TU_stop(method) method, and perhaps an integer member TU_instances (so you can mindlessly attach it to heaps of stuff and just destroy it when TU_instances == 0). I'm still thinking carefully about the design of this, however. It doesn't make sense at all on many levels. Feel free to share your opinion (about this or other ideas). :)

Example use:
JASS:
private integer favouriteCallbackCount=0
private method myFavouriteCallback takes nothing returns nothing
    set this.favouriteCallbackCount = this.favouriteCallbackCount + 1
    call BJDebugMsg(&quot;Caster: &quot; + GetUnitName(this.caster))
    if this.favouriteCallbackCount == 3 then
        call this.TU_stop(thistype.myFavouriteCallback)
        set this.autoDestroy = true
    endif
endmethod

private method onEffect takes nothing returns nothing
    set this.autoDestroy = false
    call this.TU_start(thistype.myFavouriteCallback, 1.0)
endmethod

?
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top