Spell Pudge Wars Meat Hook Revisited

BlackRose

Forum User
Reaction score
239
Does this version bounce off cliffs? :)
I think Tossrock just placed 'invisible' doodads or trees or buildings on the edges (or bottom) of the cliffs to make it bounce off.

Also,
JASS:
 //  Config Descriptions:                                                              \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                              \\
//    Below is a description of all the constant globals and functions used in Path   \\
//    of Shadows.

I think you should look at your configurable list again...
 

Kenny

Back for now.
Reaction score
202
hook range was 4000 + 100 * lvl

I really think I had to many handles.

I once made the hook have 30,000 range and it still worked. It was just the aforementioned bug that caused the problem (I think).

I think Tossrock just placed 'invisible' doodads or trees or buildings on the edges (or bottom) of the cliffs to make it bounce off.

After heaps of testing I came to the same conclusion, there are certain things that happen to the hook when it gets near destructables that don't happen when it gets near a cliff.

I think you should look at your configurable list again...

I will change that in the next release (even though it is just a typo).

I would like to know whether or not the bug still occurs for you if you use the script in my above post. Any testing would be appreciated.
 

BlackRose

Forum User
Reaction score
239
Everything is ok now.
I'll try test more and find anything else.

Only thing is that hooks can go through and not bounce off stuff >.>. I placed Pudge inside a circle of barrels so he couldn't escape, but the hook got out ..........
 

Kenny

Back for now.
Reaction score
202
Okay, version 1.0.5 is out. As of this version, I have completed everything I wanted to complete with this spell. Bouncing off of destructables is now an option, and the bug found by BlackRose has been fixed. There was also a minor visual problem when removing unneeded links which is now fixed.

More comments are always welcome.
 

Kenny

Back for now.
Reaction score
202
Then would you mind explaining how you made the bug occur? Its no help, you just saying you can do it.

I have had over half a dozen people stress test the spell for me now, and no one has been able to do that. So unless you did something stupid, I fail to see how it can be done.

So, explain.. please. :)
 

Kenny

Back for now.
Reaction score
202
<_<

Why would you spam such a labour intensive spell? It makes no sense.

I spammed a 4000 range hook 6 times and it caused me to lag, and about 8-10 times to drop my FPS to about 3-4.

This spell isn't meant for continuous spamming, it needs to do too much work behind the scenes.

I also tried spamming a 2300 range hook (with 1150 speed) and it didn't lag at all, as I could only make about 5 or 6 of them at once. But it was all good.

Seems it is just a question of how much your computer can take before it gives up.

Anywho, I will be updating this shortly, just a few minor things I found wrong (using bj_DEGTORAD insteadof bj_RADTODEG and shit like that :p).
 

GetTriggerUnit-

DogEntrepreneur
Reaction score
129
Do you really think a spell was made to be spammed over and over until your computer dies?

Why would my computer die?

It does not. Warcraft III does. I can alt+tab without lag.

Once it bugged, you can't hook anymore.

Imo, warcraft III die, not my computer.
 

Kenny

Back for now.
Reaction score
202
Might update this soon. Just attempted to change from my Linked List Module to an internal implementation of T32.

Want to know what Jesus4Lyf thinks about it (I know it is no where near as efficient due to it using a timer and pausing it when nothing is happening).

Here's the script:

JASS:
//------------------------------------------------------------------------------------\\
//                         Pudge Wars Meat Hook [v1.0.6]                              \\
//                                   By kenny!                                        \\
//                            Constructed using vJASS                                 \\
//              Requires NewGen WE, GTrigger &amp; Linked List Module                     \\
//------------------------------------------------------------------------------------\\

scope MeatHookRevisited

    globals
        private constant integer    ABIL_ID          = &#039;A000&#039;
        private constant integer    HEAD_ID          = &#039;h000&#039;
        private constant integer    LINK_ID          = &#039;h001&#039;
        private constant integer    CROW_ID          = &#039;Amrf&#039;
        private constant integer    LOCUST_ID        = &#039;Aloc&#039;
        private constant integer    ANIM_INDEX       = 2
        private constant integer    UNITS_PER_INT    = 10
        
        private constant real       INTERVAL         = 0.04
        private constant real       MIN_HEIGHT       = 40.00
        private constant real       UNIT_RADIUS      = 100.00
        private constant real       BUILD_RADIUS     = 128.00
        private constant real       DEST_RADIUS      = 128.00
        private constant real       TIME_SCALE       = 1.00
        private constant real       HEAD_SCALE       = 1.25
        private constant real       LINK_SCALE       = 1.50
        private constant real       FLY_HEIGHT       = 75.00
        private constant real       DISTANCE         = 75.00
        private constant real       DEST_DMG_PERC    = 0.00
        
        private constant string     BLOOD_SFX        = &quot;Objects\\Spawnmodels\\Human\\HumanBlood\\BloodElfSpellThiefBlood.mdl&quot;
        private constant string     BLOOD_POINT      = &quot;origin&quot;
        private constant string     HEADSHOT_TEXT    = &quot;HEADSHOT!&quot;
        private constant string     TEXT_COLOUR      = &quot;|c00ff0000&quot;
        private constant string     HEADSHOT_SOUND   = &quot;war3mapImported\\headshot.wav&quot;
        
        private constant attacktype A_TYPE           = ATTACK_TYPE_CHAOS
        private constant damagetype D_TYPE           = DAMAGE_TYPE_UNIVERSAL
        private constant weapontype W_TYPE           = WEAPON_TYPE_WHOKNOWS
        
        private constant boolean    ALLOW_PRELOAD    = true
        private constant boolean    ALLOW_BOUNCE     = true
        private constant boolean    ALLOW_GRAPPLE    = false
        private constant boolean    ALLOW_HEADSHOT   = true
        private constant boolean    ABOVE_CASTER     = false
        private constant boolean    DISCARD_TARGET   = false
        private constant boolean    DEST_BOUNCE      = true
        private constant boolean    KILL_DESTS       = false
    endglobals

    //---------------------------------------------------------------------\\
    private constant function MAXDIST takes integer lvl returns real
        return 800.00 + (400.00 * lvl)
    endfunction

    private constant function DAMAGE takes integer lvl returns real
        return 0.00 + (100.00 * lvl)
    endfunction
    
    private constant function SPEED takes integer lvl returns real
        return 1000.00 + (0.00  * lvl)
    endfunction
    
    private function FilterUnits takes unit caster, unit filter returns boolean
        return GetUnitFlyHeight(filter) &lt; MIN_HEIGHT and GetUnitAbilityLevel(filter,LOCUST_ID) &lt; 1
    endfunction
    
    //---------------------------------------------------------------------\\
    //                                                                     \\
    //  DO NOT TOUCH PAST THIS POINT UNLESS YOU KNOW WHAT YOUR ARE DOING!  \\
    //                                                                     \\
    //---------------------------------------------------------------------\\

    private struct Link // Individual links in the linked list for units.
    
        thistype next = 0    // Next link in the list.
        thistype prev = 0    // Previous link in the list.
        thistype data = 0    // Pointer needed to remember specific links.
        unit     link = null // The actual chain link.
        
        // Reset all the members for this struct instance.
        private method onDestroy takes nothing returns nothing            
            set this.next = 0
            set this.prev = 0
            set this.data = 0
            
            // If the chain link is still there, remove it.
            if this.link != null then
                call RemoveUnit(this.link)
                set this.link = null
            endif
        endmethod
        
    endstruct
   
    //---------------------------------------------------------------------\\
    private struct List // Linked list to hold all the units that make the chain.

        Link    first = 0 // First link in the list.
        Link    last  = 0 // Last link in the list.
        Link    data  = 0 // Pointer node needed.
        integer size  = 0 // Size of the list.

        // Attach a link to the list.
        method attach takes nothing returns Link
            local Link curr = Link.create() // Create a link.
        
            // Restructuring the start of the list.
            if this.size == 0 then
                set this.first = curr
            elseif this.size == 1 then
                set curr.prev = this.first
                set this.first.next = curr
            elseif this.size &gt; 1 then
                set curr.prev = this.last
                set this.last.next = curr
            endif
        
            // Setting the rest of this link.
            set this.last = curr
            set this.data = curr
            set this.size = this.size + 1
        
            return curr
        endmethod

        // Detach a link from the list.
        method detach takes Link curr returns nothing
            local Link new = curr.prev
            
            // Removing the current link.
            if curr.next != 0 and curr.prev != 0 then
                set curr.prev.next = curr.next
                set curr.next.prev = curr.prev
            endif
            
            if this.first == curr then
                set this.first = curr.next
            endif
        
            if this.last == curr then
                set this.last = curr.prev
            endif
        
            call RemoveUnit(curr.link)
            set curr.link = null
        
            set this.size = this.size - 1
            set curr.data = new
        endmethod

        // Destroy the list.
        private method onDestroy takes nothing returns nothing
            local Link linkx = this.data
            local Link linkz = 0
        
            loop
                exitwhen linkx == 0
                set linkz = linkx.data
                call linkx.destroy()
                set linkx = linkz
            endloop
        endmethod
    
    endstruct
   
   //---------------------------------------------------------------------\\
    private struct Hook // Struct that takes care of the rest.
    
        unit     cast  = null  // The caster.
        unit     targ  = null  // The possible target.
        unit     head  = null  // The head of the chain.
        player   own   = null  // The owner of the caster.
        
        real     ang   = 0.00  // The angle the chain is travelling in.
        real     dist  = 0.00  // The current distance travelled.
        real     cos   = 0.00  // Cos of the current angle.
        real     sin   = 0.00  // Sin of the current angle.
        real     speed = 0.00  // Speed of the hook, saves function calls.
        
        integer  level = 0     // Level of the Meat Hook spell for the caster.
        List     list  = 0     // Linked list needed to track chain links.
        
        boolean  ext   = true  // If the hook is extending.
        boolean  hit   = false // If a unit has been hit.
        boolean  grap  = false // If the caster is grappling.
        
        thistype next  = 0     // Needed for T32 implementation.
        thistype prev  = 0     // Needed for T32 Implementation.
        
        static thistype Temp  = 0    // Temp struct needed for data transfer.
        static timer    Timer = null // Timer for the spell to run.
        static group    Group = null // Group for enumerating units and buildings.
        static rect     Rectx = null // Rect for bouncing off destructables.
        static boolexpr Build = null // Filter function for finding buildings.
        static boolexpr Unit  = null // Filter function for finding units.
        
        static real     MaxX  = 0.00 // Map boundaries.
        static real     MaxY  = 0.00 // Map boundaries.
        static real     MinX  = 0.00 // Map boundaries.
        static real     MinY  = 0.00 // Map boundaries.
        
        // Finds a safe X location for a given position, needed for bouncing.
        static method safeX takes real x returns real            
            if x &lt; thistype.MinX then
                set x = thistype.MinX
            elseif x &gt; thistype.MaxX then 
                set x = thistype.MaxX
            endif
            return x
        endmethod

        // Finds a safe Y location for a given position, needed for bouncing.
        static method safeY takes real y returns real
            if y &lt; thistype.MinY then
                set y = thistype.MinY
            elseif y &gt; thistype.MaxY then 
                set y = thistype.MaxY
            endif
            return y
        endmethod
        
        // If a possible target unit is already the target unit of another hook.
        static method isTarget takes unit u returns boolean
            local thistype this = thistype(0).next
            
            loop
                exitwhen this == 0
                
                if this.targ == u then
                    return true
                endif
                
                set this = this.next
            endloop
            
            return false
        endmethod
        
        // If the caster is already grappling to another building.
        static method isGrappling takes unit u returns boolean
            local thistype this = thistype(0).next
            
            loop
                exitwhen this == 0
                
                if this.grap == true and this.cast == u then
                    return true
                endif
                
                set this = this.next
            endloop
            
            return false
        endmethod
        
        // Quick text tag for easy use.
        static method textTag takes real x, real y returns nothing
            local texttag tt = CreateTextTag ()
            
            call SetTextTagPos(tt,x,y,0.00)
            call SetTextTagPermanent(tt,false)
            call SetTextTagText(tt,TEXT_COLOUR + HEADSHOT_TEXT + &quot;|r&quot;,0.023)
            call SetTextTagVelocity(tt,Cos(1.57079) * 0.0355,Sin(1.57079) * 0.0355)
            call SetTextTagLifespan(tt,3.00)
            call SetTextTagFadepoint(tt,1.50)
            
            set tt = null
        endmethod
        
        // Filter for units.
        static method unitFilt takes nothing returns boolean
            return GetWidgetLife(GetFilterUnit()) &gt; 0.406 and IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE) == false
        endmethod
        
        // Filter for buildings.
        static method buildingFilt takes nothing returns boolean
            return GetWidgetLife(GetFilterUnit()) &gt; 0.406 and IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE) == true
        endmethod
        
        // The basic filter needed so the hook doesn&#039;t screw up.
        static method extraFilt takes unit caster, unit filter returns boolean
            return caster != filter and GetUnitTypeId(filter) != LINK_ID and GetUnitTypeId(filter) != HEAD_ID
        endmethod
        
        // Clean up the struct instance and finish the spell.
        method onDestroy takes nothing returns nothing          
            call this.list.destroy() // Destroy the unit list.
            
            // If there is a target, set it to a position infront of the caster. If you don&#039;t like this, make DISTANCE 0.00.
            if this.targ != null then
                call SetUnitX(this.targ,GetUnitX(this.cast) + DISTANCE * Cos(GetUnitFacing(this.cast) * bj_DEGTORAD))
                call SetUnitY(this.targ,GetUnitY(this.cast) + DISTANCE * Sin(GetUnitFacing(this.cast) * bj_DEGTORAD))
            endif
            
            if this.grap == true then
                call SetUnitPathing(this.cast,true)
            endif

            // Clear struct members.
            set this.cast = null
            set this.targ = null
            set this.head = null
            
            set this.ext  = true
            set this.hit  = false
            set this.grap = false
        endmethod
        
        // Set the new angle for bouncing.
        method setAngle takes real tempx, real tempy returns boolean
            local real ang1 = this.ang * bj_RADTODEG
            local real ang2 = ModuloReal(Atan2((GetUnitY(this.head) - tempy),(GetUnitX(this.head) - tempx)) * bj_RADTODEG,360.00)
            local real diff = RAbsBJ(ang2 - ang1)
            
            // Lots of math stuff <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite1" alt=":)" title="Smile    :)" loading="lazy" data-shortname=":)" />.
            if diff &gt;= 90.00 and diff &lt;= 270.00  then
                if ( ang2 &gt; 45.00 and ang2 &lt; 135.00 ) or ( ang2 &gt; 225.00 and ang2 &lt; 315.00 ) then
                    set this.ang = -ang1 * bj_DEGTORAD
                else
                    set this.ang = (180.00 - ang1) * bj_DEGTORAD
                endif
                         
                // Set the new travelling angle.
                set this.sin = Sin(this.ang)
                set this.cos = Cos(this.ang)
                
                return true
            endif 
            
            return false
        endmethod
        
        // Find any destructables in range of the head and bounce off them.
        static method enumDestructables takes nothing returns nothing
            local destructable dest = GetEnumDestructable()
            
            // Only bounce off alive destructables.
            if GetWidgetLife(dest) &gt; 0.406 then
                call thistype.Temp.setAngle(GetWidgetX(dest),GetWidgetY(dest))
                // Kill them if you want.
                if KILL_DESTS == true then
                    call KillDestructable(dest)
                else
                    call UnitDamageTarget(thistype.Temp.cast,dest,(DAMAGE(thistype.Temp.level) * DEST_DMG_PERC),true,false,A_TYPE,D_TYPE,W_TYPE)
                endif
            endif
            
            set dest = null
        endmethod
        
        // Searches for buildings to bounce off.
        method getAngle takes nothing returns nothing
            local unit temp = null
            
            // Group the buildings in range.
            call GroupEnumUnitsInRange(thistype.Group,GetUnitX(this.head),GetUnitY(this.head),BUILD_RADIUS,thistype.Build)
           
            loop
                set temp = FirstOfGroup(thistype.Group)
                exitwhen temp == null
                call GroupRemoveUnit(thistype.Group,temp)
                if ALLOW_BOUNCE then // If bouncing is allowed, then set the new angle.
                    exitwhen this.setAngle(GetUnitX(temp),GetUnitY(temp))
                else
                    // Else find out if grappling is allowed.
                    if ALLOW_GRAPPLE then
                        if this.grap == false then
                            if thistype.isGrappling(this.cast) == false then
                                // If grappling is allowed and the caster is not grappling already, make it grapple.
                                set this.hit  = true
                                set this.grap = true
                                call SetUnitPathing(this.cast,false)
                                call SetUnitTimeScale(this.head,0.00)
                            else
                                // If the caster is already grappling, but has two hooks that want to grapple, make one retract.
                                set this.hit = true
                            endif
                            exitwhen true
                        endif
                    endif
                endif
            endloop
            
            set temp = null
        endmethod
        
        // Sets the target unit.
        method setTarget takes unit target returns boolean
            set this.targ = target
            set this.hit  = true
            
            // Damage the target if it is an enemy.
            if IsUnitEnemy(this.targ,this.own) then
                call UnitDamageTarget(this.cast,this.targ,DAMAGE(this.level),true,false,A_TYPE,D_TYPE,W_TYPE)
                call DestroyEffect(AddSpecialEffectTarget(BLOOD_SFX,this.targ,BLOOD_POINT))
            endif
                    
            // Stop the hook from moving.
            call SetUnitTimeScale(this.head,0.00)
            
            return true
        endmethod
        
        // Searches for a target unit.
        method getTarget takes nothing returns nothing
            local real  headx = GetUnitX(this.head)
            local real  heady = GetUnitY(this.head)
            local unit  temp  = null
            local sound shot  = null
            
            // Group units in range.
            call GroupEnumUnitsInRange(thistype.Group,headx,heady,UNIT_RADIUS,thistype.Unit)
        
            loop
                set temp = FirstOfGroup(thistype.Group)
                exitwhen temp == null
                call GroupRemoveUnit(thistype.Group,temp)
                if thistype.extraFilt(this.cast,temp) == true and FilterUnits(this.cast,temp) == true then
                    if ALLOW_HEADSHOT then // If headshots are allowed continue on.
                        if this.hit == false then // If a unit hasn&#039;t been it continue on.
                            if thistype.isTarget(temp) == true then // If the possible unit is already a target continue.
                                // Play the sound.
                                set shot = CreateSound(HEADSHOT_SOUND,false,false,false,10,10,&quot;&quot;)
                                call StartSound(shot)
                                call KillSoundWhenDone(shot)
                                // Create a text tag.
                                if ABOVE_CASTER then
                                    call thistype.textTag(GetUnitX(this.cast),GetUnitY(this.cast))
                                else
                                    call thistype.textTag(headx,heady)
                                endif
                                // Kill the unit.
                                call UnitDamageTarget(this.cast,temp,1000000.00,true,false,A_TYPE,D_TYPE,W_TYPE)
                                call DestroyEffect(AddSpecialEffectTarget(BLOOD_SFX,temp,BLOOD_POINT))
                                set this.hit = true // Set hit to true
                            else
                                // If the unit isn&#039;t a target, make it one.
                                exitwhen this.setTarget(temp)
                            endif
                        endif
                    else
                        // If we aren&#039;t allowing headshots, then just find a suitable unit and make it the target.
                        if this.hit == false and thistype.isTarget(temp) == false then
                            exitwhen this.setTarget(temp)
                        endif
                    endif
                endif
            endloop
            
            set temp = null
            set shot = null
        endmethod
        
        // Retract the hook.
        method retract takes nothing returns boolean
            local Link    linkx = 0
            local integer count = 0
            local boolean exit  = false
            local unit    temp  = null
            local unit    prev  = null
            local real    casx  = GetUnitX(this.cast)
            local real    casy  = GetUnitY(this.cast)
            local real    temx  = 0.00
            local real    temy  = 0.00
            local real    newx  = 0.00
            local real    newy  = 0.00
            local real    ang2  = 0.00
            local real    dist  = 0.00
            
            // If there are no more chain links, finish the hook.
            if this.list.size == 0 then
                set exit = true
            else
                set temp = this.list.last.link
                set temx = GetUnitX(temp)
                set temy = GetUnitY(temp)
                    
                // Unfortunately needed to use SquareRoot() <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite3" alt=":(" title="Frown    :(" loading="lazy" data-shortname=":(" />.
                set dist = SquareRoot((temx - casx) * (temx - casx) + (temy - casy) * (temy - casy))
                
                // If the distance between the caster and the last link is too much, create more links.
                if dist &gt; this.speed then   
                    // Using a loop so the hooks work when great distances are covered in a short time.
                    loop
                        exitwhen dist &lt;= this.speed or count == UNITS_PER_INT// Create as many links as needed to fill the gap.
                        set ang2 = Atan2((casy - temy),(casx - temx))
                        set temx = temx + this.speed * Cos(ang2)
                        set temy = temy + this.speed * Sin(ang2)
                          
                        // Create a new chain link.
                        set linkx      = this.list.attach()
                        set linkx.link = CreateUnit(this.own,LINK_ID,temx,temy,(ang2 * bj_RADTODEG) + 180.00)
                            
                        // Set its height and size.
                        call UnitAddAbility(linkx.link,CROW_ID)
                        call UnitRemoveAbility(linkx.link,CROW_ID)
                        call SetUnitFlyHeight(linkx.link,FLY_HEIGHT,0.00)
                        call SetUnitScale(linkx.link,LINK_SCALE,LINK_SCALE,LINK_SCALE)
                            
                        // Set the new last link in the chain to the last created link.
                        set temp  = linkx.link
                        set temx  = GetUnitX(temp)
                        set temy  = GetUnitY(temp)
                        set dist  = dist - this.speed
                        set count = count + 1
                    endloop
                endif
                
                // If the caster is grappling, change its position.
                if this.grap == true then
                    set temx = GetUnitX(this.list.last.link)
                    set temy = GetUnitY(this.list.last.link)
                    set ang2 = Atan2((temy - casy),(temx - casx))
                    
                    // Set the caster&#039;s new position using SetUnitPosition() to stop extra casting.
                    call SetUnitPosition(this.cast,casx + this.speed * Cos(ang2),casy + this.speed * Sin(ang2))
                else
                    // If the caster is no grappling, check for a target unit.
                    if this.targ != null then
                        // If there is a target unit, set it&#039;s new position.
                        call SetUnitX(this.targ,GetUnitX(this.head))
                        call SetUnitY(this.targ,GetUnitY(this.head))
                        
                        if DISCARD_TARGET then
                            // Check if it is bead so it can be discarded.
                            if GetWidgetLife(this.targ) &lt; 0.406 then
                                set this.targ = null
                                call SetUnitTimeScale(this.head,TIME_SCALE)
                            endif
                        endif
                    else
                        // Otherwise, find a new unit.
                        call this.getTarget()
                    endif
                endif
                
                set linkx = this.list.last
                    
                // Chain links may bunch up after bouncing off of walls.
                // Sometimes the distance between every second chain is equivalent
                // to the distance between each chain, therefore we can remove the middle one.
                loop
                    exitwhen linkx == 0 or linkx.prev.prev == 0
                    
                    set temp = linkx.link
                    set temx = GetUnitX(temp)
                    set temy = GetUnitY(temp)
                
                    set prev = linkx.prev.prev.link
                    set newx = GetUnitX(prev)
                    set newy = GetUnitY(prev)
                
                    if ((temx - newx) * (temx - newx) + (temy - newy) * (temy - newy)) &lt;= (this.speed * this.speed) * 0.75 then
                        call this.list.detach(linkx.prev)
                        call SetUnitFacing(temp,(Atan2((newy - temy),(newx - temx)) * bj_RADTODEG) + 180.00)
                    endif
                    
                    set linkx = linkx.prev
                endloop
                
                set linkx = this.list.last
                
                // This is used to remove the unneeded links and to set their new position if they are needed.
                loop
                    exitwhen linkx == 0
                    
                    set temp = linkx.link
                    set temx = GetUnitX(temp)
                    set temy = GetUnitY(temp)
                    
                    // Find the angle that the chain link should be facing.
                    if temp == this.list.last.link then
                        set ang2 = Atan2((casy - temy),(casx - temx))
                    else
                        set ang2 = Atan2((GetUnitY(prev) - temy),(GetUnitX(prev) - temx))
                    endif
                    
                    if linkx == this.list.last and ((temx - casx) * (temx - casx) + (temy - casy) * (temy - casy)) &lt; this.speed * this.speed then
                        call this.list.detach(linkx)
                        // Detach the link if needed.
                    endif
                    
                    // Set the chain links new position.
                    if this.grap == false then
                        call SetUnitX(temp,temx + this.speed * Cos(ang2))
                        call SetUnitY(temp,temy + this.speed * Sin(ang2))
                    endif
                    
                    call SetUnitFacing(temp,(ang2 * bj_RADTODEG) + 180.00)

                    set prev  = temp
                    set linkx = linkx.prev
                endloop 
            endif 
            
            set temp = null
            set prev = null
            
            return exit
        endmethod
        
        // Extend the hook.
        method extend takes nothing returns boolean
            local Link    linkx = 0
            local integer count = 0
            local boolean exit  = false
            local unit    temp  = null
            local unit    next  = null
            local real    casx  = GetUnitX(this.cast)
            local real    casy  = GetUnitY(this.cast)
            local real    newx  = 0.00
            local real    newy  = 0.00
            local real    temx  = 0.00
            local real    temy  = 0.00
            local real    ang2  = 0.00
            local real    dist  = 0.00
            
            // If the max distance has been reached or a unit has been hit, stop extending.
            if this.dist &gt;= MAXDIST(this.level) or this.hit == true then
                set this.ext = false
                set exit     = true
            else
                set temp = this.list.last.link
                set temx = GetUnitX(temp)
                set temy = GetUnitY(temp)
                    
                // Unfortunately needed to use SqaureRoot() <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite3" alt=":(" title="Frown    :(" loading="lazy" data-shortname=":(" />.
                set dist = SquareRoot((temx - casx) * (temx - casx) + (temy - casy) * (temy - casy))
                    
                // If the distance between the caster and the last link is too much, create more links.
                if dist &gt; this.speed then 
                    // Using a loop so the hooks work when great distances are covered in a short time.
                    loop
                        exitwhen dist &lt;= this.speed or count == UNITS_PER_INT // Create as many links as needed to fill the gap.
                        set ang2 = Atan2((casy - temy),(casx - temx))
                        set temx = temx + this.speed * Cos(ang2)
                        set temy = temy + this.speed * Sin(ang2)
                        
                        // Create a new chain link.
                        set linkx      = this.list.attach()
                        set linkx.link = CreateUnit(this.own,LINK_ID,temx,temy,(ang2 * bj_RADTODEG) + 180.00)
                        
                        // Set its height and size.
                        call UnitAddAbility(linkx.link,CROW_ID)
                        call UnitRemoveAbility(linkx.link,CROW_ID)
                        call SetUnitFlyHeight(linkx.link,FLY_HEIGHT,0.00)
                        call SetUnitScale(linkx.link,LINK_SCALE,LINK_SCALE,LINK_SCALE)
                        
                        // Set the new last link of the chain the the last created link.
                        set temp  = linkx.link
                        set temx  = GetUnitX(temp)
                        set temy  = GetUnitY(temp)
                        set dist  = dist - this.speed
                        set count = count + 1
                    endloop
                endif
                
                set linkx = this.list.first
                    
                // Chain links may bunch up after bouncing off of walls.
                // Sometimes the distance between every second chain is equivalent
                // to the distance between each chain, therefore we can remove the middle one.
                loop
                    exitwhen linkx == 0 or linkx.next.next == 0
                    
                    set temp = linkx.link
                    set temx = GetUnitX(temp)
                    set temy = GetUnitY(temp)
                
                    set next = linkx.next.next.link
                    set newx = GetUnitX(next)
                    set newy = GetUnitY(next)
                    
                    if ((temx - newx) * (temx - newx) + (temy - newy) * (temy - newy)) &lt;= (this.speed * this.speed) * 0.75 then
                        call this.list.detach(linkx.next)
                        call SetUnitFacing(temp,Atan2((newy - temy),(newx - temx)) * bj_RADTODEG)
                    endif
                    
                    set linkx = linkx.next
                endloop
                
                set linkx = this.list.first

                // This loop moves all the current links in the chain.
                loop
                    exitwhen linkx == 0
                    
                    set temp = linkx.link
                    set temx = GetUnitX(temp)
                    set temy = GetUnitY(temp)
                    
                    // Set the new angle for each chain link.
                    if temp == this.head then
                        set ang2 = this.ang // If it is the head link, get the angle to the travelling angle.
                    else
                        set ang2 = Atan2((GetUnitY(next) - temy),(GetUnitX(next) - temx))
                    endif
                    
                    set newx = temx + this.speed * Cos(ang2)
                    set newy = temy + this.speed * Sin(ang2)
                    
                    // If the new point is outside the map, find a new point and make the hook bounce.
                    if temp == this.head then
                        if newx != thistype.safeX(newx) then
                            if this.ang &lt; 0.00 then
                                set this.ang = -1.57079 + (-1.57079 - this.ang)
                            else
                                set this.ang = 1.57079 - (this.ang - 1.57079)
                            endif
                            
                            set this.cos = Cos(this.ang)
                            set this.sin = Sin(this.ang)
                        elseif newy != thistype.safeY(newy) then
                            if this.ang &lt; 1.57079 then
                                set this.ang = 0.00 - this.ang
                            else
                                set this.ang = -this.ang
                            endif
                            
                            set this.cos = Cos(this.ang)
                            set this.sin = Sin(this.ang)
                        endif
                        
                        // Search for a target to hook
                        call this.getTarget()
                        
                        // Search for a building to bounce off.
                        call this.getAngle()
                        
                        if DEST_BOUNCE == true then
                            set thistype.Temp = this
                            call SetRect(thistype.Rectx,newx - DEST_RADIUS,newy - DEST_RADIUS,newx + DEST_RADIUS,newy + DEST_RADIUS)
                            call EnumDestructablesInRect(thistype.Rectx,null,function thistype.enumDestructables)
                        endif
                    endif
                    
                    // Set the new position of each hook.
                    call SetUnitX(temp,newx)
                    call SetUnitY(temp,newy)
                    call SetUnitFacing(temp,ang2 * bj_RADTODEG)
                    
                    set next  = temp
                    set linkx = linkx.next
                endloop
                
                // Reset the max distance of the hook.
                set this.dist = this.dist + this.speed
            endif
            
            set temp = null
            set next = null
            
            return exit
        endmethod
        
        // The handler method, makes things much easier.
        static method handler takes nothing returns nothing
            local thistype this = thistype(0).next
            
            loop
                exitwhen this == 0

                if this.list.size == 0 and this.ext == false then // If the hook is done.
                    set this.prev.next = this.next
                    set this.next.prev = this.prev
                    call this.destroy() // Clean up the struct.
                else
                    if this.ext == true then // If the hook is extending
                        call this.extend()   // Keep extending.
                    else                     // Else   
                        call this.retract()  // Retract it.
                    endif
                endif
                
                set this = this.next // Get the next struct instance.
            endloop
            
            // Pause the timer when it isn&#039;t needed.
            if thistype(0).next == 0 then
                call PauseTimer(thistype.Timer)
                call BJDebugMsg(&quot;Timer paused.&quot;)
            endif
        endmethod
        
        // Start of the hook, assembles struct members and stuff.
        static method actions takes nothing returns boolean
            local thistype this  = thistype.create()
            local location loc   = GetSpellTargetLoc()
            local real     locx  = GetLocationX(loc)
            local real     locy  = GetLocationY(loc)
            local real     casx  = 0.00
            local real     casy  = 0.00
            
            // Sets struct members.
            set this.cast  = GetTriggerUnit()
            set this.own   = GetOwningPlayer(this.cast)
            set casx       = GetUnitX(this.cast)
            set casy       = GetUnitY(this.cast)
            set this.ang   = Atan2((locy - casy),(locx - casx))
            set this.cos   = Cos(this.ang)
            set this.sin   = Sin(this.ang)
            set this.list  = List.create()
            set this.level = GetUnitAbilityLevel(this.cast,ABIL_ID)
            set this.speed = SPEED(this.level) * INTERVAL
            
            // Create the head of the chain.
            set this.head  = CreateUnit(this.own,HEAD_ID,casx + this.speed * this.cos,casy + this.speed * this.sin,this.ang * bj_RADTODEG)
            set this.list.attach().link = this.head
             
            // Set up the head unit.
            call SetUnitTimeScale(this.head,TIME_SCALE)
            call UnitAddAbility(this.head,CROW_ID)
            call UnitRemoveAbility(this.head,CROW_ID)
            call SetUnitFlyHeight(this.head,FLY_HEIGHT,0.00)
            call SetUnitScale(this.head,HEAD_SCALE,HEAD_SCALE,HEAD_SCALE)
            
            // Play a casting animation.
            call SetUnitAnimationByIndex(this.cast,ANIM_INDEX)
            
            // T32 implementation.
            set thistype(0).next.prev = this
            set this.next = thistype(0).next
            set thistype(0).next = this
            set this.prev = thistype(0)

            // Start the timer if it isn&#039;t already started.
            if thistype(0).next.next == 0 then
                call TimerStart(thistype.Timer,INTERVAL,true,function thistype.handler)
                call BJDebugMsg(&quot;Timer started.&quot;)
            endif
            
            call RemoveLocation(loc)
            set loc = null
            
            // Return a boolean value to work with GTrigger.
            return false
        endmethod
        
        // Needed this so that the sound worked first go.
        static method loadSound takes nothing returns nothing
            local sound shot = CreateSound(HEADSHOT_SOUND,false,false,false,10,10,&quot;&quot;)
            
            // Play the sound, but quietly.
            call SetSoundVolume(shot,0)
            call StartSound(shot)
            call KillSoundWhenDone(shot)
            
            // Using the static timer for this, seeing as no one will cast hook at 0.00 seconds game time.
            call PauseTimer(thistype.Timer)
            
            set shot = null
        endmethod
        
        // Initialisation method.
        static method onInit takes nothing returns nothing
            local unit dummy   = null
         
            // Set all the static struct members.
            set thistype.Timer = CreateTimer()
            set thistype.Group = CreateGroup()
            set thistype.Rectx = Rect(0.00,0.00,1.00,1.00)
            set thistype.Unit  = Filter(function thistype.unitFilt)
            set thistype.Build = Filter(function thistype.buildingFilt)
            
            set thistype.MaxX  = GetRectMaxX(bj_mapInitialPlayableArea) - 64.00
            set thistype.MaxY  = GetRectMaxY(bj_mapInitialPlayableArea) - 64.00
            set thistype.MinX  = GetRectMinX(bj_mapInitialPlayableArea) + 64.00
            set thistype.MinY  = GetRectMinY(bj_mapInitialPlayableArea) + 64.00
            
            // Needed to load the sound properly.
            call TimerStart(thistype.Timer,0.00,false,function thistype.loadSound)
            
            // Add a &quot;starts effect&quot; event to the spell.
            call GT_AddStartsEffectAction(function thistype.actions,ABIL_ID)
            
            // If preloading is allowed, then preload.
            if ALLOW_PRELOAD then
                // Load the &quot;chain&quot; unit and add the crow form ability to load that too.
                set dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),LINK_ID,0.00,0.00,0.00)
                call UnitAddAbility(dummy,CROW_ID)
                call RemoveUnit(dummy)
                
                // Load the &quot;head&quot; unit.
                set dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),HEAD_ID,0.00,0.00,0.00)
                call RemoveUnit(dummy)
                set dummy = null
            endif
        endmethod
        
    endstruct
    
endscope
 

Jesus4Lyf

Good Idea™
Reaction score
397
Lol! I was surprised to see you take the script out of T32 and embed it directly... Hm.
JASS:
if this.ext == true then // If the hook is extending

-->
JASS:
if this.ext then // If the hook is extending

Yeah yeah, picky. I know.

I didn't really read anything else. That's... a lot of code you got there. :p

Another thing:
JASS:
            // T32 implementation.
            set thistype(0).next.prev = this
            set this.next = thistype(0).next
            set thistype(0).next = this
            set this.prev = thistype(0)

            // Start the timer if it isn&#039;t already started.
            if thistype(0).next.next == 0 then
                call TimerStart(thistype.Timer,INTERVAL,true,function thistype.handler)
                call BJDebugMsg(&quot;Timer started.&quot;)
            endif

-->
JASS:
            // Start the timer if it isn&#039;t already started.
            if thistype(0).next == 0 then // One less array read.
                call TimerStart(thistype.Timer,INTERVAL,true,function thistype.handler)
                call BJDebugMsg(&quot;Timer started.&quot;)
            endif
            
            // T32 implementation.
            set thistype(0).next.prev = this
            set this.next = thistype(0).next
            set thistype(0).next = this
            set this.prev = thistype(0)

I mean, cool. You have the best algorithm. Can't hurt.

Implementing T32 directly would be nicer imo, but what can ya say. (It doesn't require timer pausing, runs effectively a do nothing condition in the background...)

It's all much of a muchness. If you have efficiency problems, I don't think that's where they are. :p
 

Kenny

Back for now.
Reaction score
202
Yeah yeah, picky. I know.

Thats been on the bottom of my list, but I'll get around to it.

And yeah, theres a lot of code, but a lot of it is just commenting and spacing and stuff.

Thanks for the tip to reduce the array reads.

Also I am probably going to implement T32 directly for the actual update, I was just fooling around, and the fact that onInit methods are privatised properly for modules annoys me.

*EDIT*

Actually, I need to use T32 internally for now, cause I have some small O(n) searches, so having T32 embed allows me to use .next and .prev easily without any extra work. So if it is okay with you, I might release another version with T32 embed in the spell, with credits given of course.
 

Kenny

Back for now.
Reaction score
202
Update.

Version 1.0.6:

Minor update. Spell no longer requires the Linked List Module. Now uses an internalised T32 system (Thanks Jesus4Lyf - Credits to go him for the system). Got rid of some maths and replaced it with native calls. Updated the test map.
 

Hatebreeder

So many apples
Reaction score
381
This is a damn fine spell, actually.
Can't actually see anything that could need some optimising.

it IS fun to play around with it, though i wouldn't use this in a AOS or something Code intense. since it could after all cause laggs.

Guess Pudge Wars is the way to go, eventhough this Spell seems to be more smoother than the one in Pudge Wars (don't ask me why, it just feels better).

Might as well think about adding a "On Damage", "On Cast" or something function, so that vJassers alike can make their own "Pudge Wars"... (But, in the end i guess it would be a Pudge Wars ripp off, wouldn't it?)
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top