Spell Pudge Wars Meat Hook Revisited

Kenny

Back for now.

Pudge Wars Meat Hook
Revisited
Version 1.1.2

General Information:

After reading over Trollvottel's Pudge Wars Meat Hook, I decided to attempt my own version of the spell to fix some of the bugs and stuff I found with the spell. This is the final product of my work, a totally rewritten script that uses similar methods to that of the Meat Hook in Pudge Wars (linked list to keep track of units). I also scabbed Trollvottel's bouncing formula.​

Requires:

  • GTrigger by Jesus4Lyf.
  • AIDS by Jesus4Lyf.
  • T32 by Jesus4Lyf.
  • AutoFly by Azlier (optional).
  • JassHelper. At least version 0.A.2.A, however the latest is preferable.

Details:

  • Spell is written in vJASS.
  • Spell is MUI/MPI.
  • Spell is leakless.
  • Spell generates very little lag (as much if not less than the latest Meat Hook from Pudge Wars). By similar lag to Pudge Wars, I mean a similar FPS drop (Actually about 2-3 more FPS than pudge wars).
  • Spell is highly configurable.

Description:

Launches a hook which will grab the first unit which comes in range of the hookhead. Will bounce off buildings and map boundaries.

Level 1 - Deals 100 damage, has 1200 maximum range.

Level 2 - Deals 200 damage, has 1600 maximum range.

Level 3 - Deals 300 damage, has 2000 maximum range.​

Screenshots:



Implementation:

JASS:
//------------------------------------------------------------------------------------\\
//                                                                                    \\
//  To Implement:                                                                     \\
//                                                                                    \\
//    - This version requires GTrigger by Jesus4Lyf. Either copy the trigger labelled \\
//      GT or create a new trigger in your map, convert it to custom script and paste \\
//      the script for GTrigger into it.                                              \\
//                                                                                    \\
//    - This version also requires T32 and AIDS by Jesus4Lyf. Follow the instructions \\
//      on how to implement them in the documentation for each system.                \\
//                                                                                    \\
//    - This version optionally requires AutoFly by Azlier. Just copy and paste the   \\
//      AutoFly library into your map and you will be able to adjust the hooks height \\
//      from the MeatHook library.  `                                                 \\
//                                                                                    \\
//    - Copy the MeatHookRevisited trigger below this, or make a new trigger in your  \\
//      map and name it MeatHookRevisited, then copy the script from this map to you  \\
//      own. You can actually name the trigger whatever you want. Eg: MeatHook.       \\
//                                                                                    \\
//    - Import the headshot.wav sound into your map from this map and don't change    \\
//      the path. The path should be: war3mapImported\headshot.wav                    \\
//                                                                                    \\
//    - Copy the 2 units and 1 spell needed for the spell into your map.              \\
//                                                                                    \\
//    - Once you have copied the spell and units, change the raw codes in the         \\
//      MeatHookRevisited trigger to the ones in your map, then change any of the     \\
//      configurables you want.                                                       \\
//                                                                                    \\
//    - Add the ability to a unit and enjoy <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite1" alt=":)" title="Smile    :)" data-shortname=":)" />.                                       \\
//                                                                                    \\
//------------------------------------------------------------------------------------\\

//------------------------------------------------------------------------------------\\
//                                                                                    \\
//  Credits:                                                                          \\
//                                                                                    \\
//    - Credits to Tossrock for the original concept behind the spell.                \\
//                                                                                    \\
//    - Credits to Vexorian for JassHelper, and the NewGen team for NewGen WE.        \\
//                                                                                    \\
//    - Credits to Trollvottel for some ideas for the spell and the test map.         \\
//                                                                                    \\
//    - Credits to Jesus4Lyf for GTrigger. It is awesome.                             \\
//                                                                                    \\
//    - Credits to Jesus4Lyf for T32.                                                 \\
//                                                                                    \\
//    - Credits to Jesus4Lyf for AIDS.                                                \\
//                                                                                    \\
//    - Credits to whoever made the headshot.wav sound (Probably from Unreal Tourny). \\
//                                                                                    \\
//    - And thanks to anyone who helped with this spell.                              \\
//                                                                                    \\
//------------------------------------------------------------------------------------\\


Configurable Descriptions:

JASS:
//------------------------------------------------------------------------------------\\
//                                                                                    \\
//  Config Descriptions:                                                              \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                              \\
//    Below is a description of all the constant globals and functions used in the    \\
//    Pudge Wars Meat Hook spell.                                                     \\
//                                                                                    \\
//  GLOBALS:                                                                          \\
// ¯¯¯¯¯¯¯¯¯                                                                          \\
//  Integers:                                                                         \\
// ¯¯¯¯¯¯¯¯¯¯                                                                         \\
//    - ABIL_ID        - Raw code of the hero ability 'Meat Hook'.                    \\
//                                                                                    \\
//    - HEAD_ID        - Raw code of the dummy unit used as the 'head' of the hook.   \\
//                                                                                    \\
//    - LINK_ID        - Raw code of the dummy unit used as the links in the chain.   \\
//                                                                                    \\
//    - LOCUST_ID      - Raw code of the locust ability.                              \\
//                                                                                    \\
//    - MAX_LINKS      - Max amount of links that can be created at any one time.     \\
//                                                                                    \\
//    - ANIM_INDEX     - Animation of the unit by index.                              \\
//                                                                                    \\
//    - UNITS_PER_INT  - How many links can be created per interval. Limit is put     \\
//                       in place to reduce lag if the caster moves a long distance   \\
//                       over a short period of time.                                 \\
//                                                                                    \\
//    - HEADSHOT_VOL   - The volume of the headshot sound when used (xxx/127).        \\
//                                                                                    \\
//  Reals:                                                                            \\
// ¯¯¯¯¯¯¯                                                                            \\
//    - MAX_HEIGHT     - Height units have to be under to be considered targets.      \\
//                                                                                    \\
//    - UNIT_RADIUS    - Radius to pick up units near the hook head.                  \\
//                                                                                    \\
//    - BUILD_RADIUS   - Radius to detect nearby buildings to bounce off.             \\
//                                                                                    \\
//    - DEST_RADIUS    - Radius to detect nearby destructables to bounce off.         \\
//                                                                                    \\
//    - TIME_SCALE     - Time scale (speed of animations) for the hook head.          \\
//                                                                                    \\
//    - HEAD_SCALE     - Scale size of the head of the chain.                         \\
//                                                                                    \\
//    - LINK_SCALE     - Scale size of the links of the chain.                        \\
//                                                                                    \\
//    - FLY_HEIGHT     - Height of the chain off the ground. Will only be set if you  \\
//                       have AutoFly in your map. If you don't you need to set the   \\
//                       height via object editor.                                    \\
//                                                                                    \\
//    - DEST_DMG_PERC  - Percentage of original damage (dealt to units) that will be  \\
//                       dealt to destructables if the user wishes for them to be     \\
//                       damaged. Set this to 0.00 to deal no damage.                 \\
//                                                                                    \\
//  Strings:                                                                          \\
// ¯¯¯¯¯¯¯¯¯                                                                          \\
//    - BLOOD_SFX      - Special effect played when an enemy is hit by a hook.        \\
//                                                                                    \\
//    - BLOOD_POINT    - Attachment point of the above special effect.                \\
//                                                                                    \\
//    - HEADSHOT_TEXT  - Text that is shown when a headshot occurs.                   \\
//                                                                                    \\
//    - TEXT_COLOUR    - The colour of the headshot text, in that hexidecimal format  \\
//                       or whatever it is.                                           \\
//                                                                                    \\
//    - HEADSHOT_SOUND - The sound that is played when a headshot occurs. Be sure to  \\
//                       import the custom sound into your map (and not change the    \\
//                       path) so that the sound will work.                           \\
//                                                                                    \\
//  Booleans:                                                                         \\
// ¯¯¯¯¯¯¯¯¯¯                                                                         \\
//    - ALLOW_PRELOAD  - Whether or not to allow preloading of special effects.       \\
//                                                                                    \\
//    - ALLOW_BOUNCE   - Whether or not to allow the hook to bounce off of buildings. \\
//                       If this is true, the hook cannot be used to grapple.         \\
//                                                                                    \\
//    - ALLOW_GRAPPLE  - Whether or not to allow the hook to grapple to building.     \\
//                       Cannot grapple if the hook is allowed to bounce.             \\
//                                                                                    \\
//    - ALLOW_HEADSHOT - Whether or not to allow headshots to occur.                  \\
//                                                                                    \\
//    - ABOVE_CASTER   - If true, the HEADSHOT! text will be above the caster, other- \\
//                       wise it will be above the dying unit.                        \\
//                                                                                    \\
//    - DISCARD_TARGET - If true, when the target unit of the hook dies, it will be   \\
//                       let go, and the hook can grab another unit. If false, when   \\
//                       the target dies, it will be dragged back to the caster.      \\
//                                                                                    \\
//    - DEST_BOUNCE    - Whether or not to allow the hook to bounce off of destruct-  \\
//                       ables.                                                       \\
//                                                                                    \\
//    - KILL_DESTS     - Whether or not to kill destructables that are hit by the     \\
//                       hook. If set to false, the destructables will be damaged     \\
//                       depending on DAMAGE() * DEST_DMG_PERC.                       \\
//                                                                                    \\
//  FUNCTIONS:                                                                        \\
// ¯¯¯¯¯¯¯¯¯¯¯                                                                        \\
//    - MAXDIST        - Maximum distance of the meat hook.                           \\
//                                                                                    \\
//    - DAMAGE         - Damage dealt to enemy units by the hook.                     \\
//                                                                                    \\
//    - SPEED          - Speed the hook travels per second (in wc3 units).            \\
//                                                                                    \\
//    - FilterUnits    - Custom filter function to filter out any units you don't     \\
//                       want to be hooked.                                           \\
//                                                                                    \\
//------------------------------------------------------------------------------------\\


Script:

JASS:
//------------------------------------------------------------------------------------\\
//                         Pudge Wars Meat Hook [v1.1.2]                              \\
//                                   By kenny!                                        \\
//                            Constructed using vJASS                                 \\
//            Requires NewGen WE, GT, AIDS, T32 &amp; AutoFly (optional).                 \\
//------------------------------------------------------------------------------------\\

library MeatHook requires GT, AIDS, T32, optional AutoFly

    globals
        private constant integer    ABIL_ID          = 'A000'
        private constant integer    HEAD_ID          = 'h000'
        private constant integer    LINK_ID          = 'h001'
        private constant integer    LOCUST_ID        = 'Aloc'
        private constant integer    MAX_LINKS        = 8191
        private constant integer    ANIM_INDEX       = 2
        private constant integer    UNITS_PER_INT    = 10
        private constant integer    HEADSHOT_VOL     = 120
        
        private constant real       MAX_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.00
        private constant real       LINK_SCALE       = 1.25
        private constant real       FLY_HEIGHT       = 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;|cFFFF0000&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_HEADSHOT   = true
        private constant boolean    DEST_BOUNCE      = true
        private constant boolean    ALLOW_GRAPPLE    = false
        private constant boolean    ABOVE_CASTER     = false
        private constant boolean    DISCARD_TARGET   = false
        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; MAX_HEIGHT and GetUnitAbilityLevel(filter,LOCUST_ID) == 0
    endfunction
    
    //---------------------------------------------------------------------\\
    //                                                                     \\
    //  DO NOT TOUCH PAST THIS POINT UNLESS YOU KNOW WHAT YOUR ARE DOING!  \\
    //                                                                     \\
    //---------------------------------------------------------------------\\
    
    // To make peoples happy. To bad there is no WidgetAlive native.
    native UnitAlive takes unit id returns boolean
    
    private struct Link[MAX_LINKS] // Individual links in the linked list for units.

        thistype next = 0     // Next link in the list.
        thistype prev = 0     // Previous link in the list.
        unit     link = null  // The actual chain link.
        
        // Reset all the members for this struct instance.
        private method onDestroy takes nothing returns nothing
            // Remove the chain link.
            call RemoveUnit(this.link)
            set this.link = null
        endmethod
        
    endstruct
   
    //---------------------------------------------------------------------\\
    private struct List // Linked list to hold all the units that make the chain.

        Link    head = 0 // First link in the list.
        Link    last = 0 // Last link in the list.
        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.head == 0 then
                // If there is nothing in the first, set the new link to the head.
                set this.head = curr
            else
                // Otherwise, attach the new link to the end of the list.
                set curr.prev = this.last
                set this.last.next = curr
            endif

            // Setting the rest of this link and incrementing list size.
            set this.last = curr
            set this.size = this.size + 1
        
            return curr
        endmethod

        // Detach a link from the list.
        method detach takes Link curr returns nothing
            // Removing a link from the list.            
            if curr.next == 0 then
                // If we are removing the last node, reset last.
                set this.last = curr.prev
            else
                set curr.next.prev = curr.prev
            endif
            
            set curr.prev.next = curr.next
            
            // Destroy the link.
            call curr.destroy()
        
            // Decrement size of list.
            set this.size = this.size - 1
        endmethod
        
        // Destroy the list.
        private method onDestroy takes nothing returns nothing
            // Just to make sure no links are left.
            loop
                exitwhen this.size == 0
                call this.detach(this.last)
            endloop
        endmethod
    
    endstruct
    
    //---------------------------------------------------------------------\\
    private struct IsTarget extends array // To check if a unit is already a target.
        //! runtextmacro AIDS()
        
        integer level // This gets incremented by 1 every time a unit becomes a target, and decremented when discarded.
        
        private static method AIDS_filter takes unit u returns boolean
            return GetUnitAbilityLevel(u,LOCUST_ID) == 0
        endmethod
        
        private method AIDS_onCreate takes nothing returns nothing
            set this.level = 0 // Initialise the variable.
        endmethod
        
        private method AIDS_onDestroy takes nothing returns nothing
            set this.level = 0 // Set it to 0 upon death.
        endmethod
        
    endstruct
    
    //---------------------------------------------------------------------\\
    private struct IsGrappling extends array // To check if a unit is already grappling.
        //! runtextmacro AIDS()
        
        integer level // This gets incremented by 1 every time a unit starts grappling, and decremented when it finishes.
        
        private static method AIDS_filter takes unit u returns boolean
            return GetUnitAbilityLevel(u,LOCUST_ID) == 0
        endmethod
        
        private method AIDS_onCreate takes nothing returns nothing
            set this.level = 0 // Initialise the variable.
        endmethod
        
        private method AIDS_onDestroy takes nothing returns nothing
            set this.level = 0 // Set it to 0 upon death.
        endmethod
        
    endstruct
   
    //---------------------------------------------------------------------\\
    private struct Hook // Struct that takes care of the rest.
    
        // Instance members.
        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     max   = 0.00  // Maximum distance of the hook.
        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  ret   = false // If the hook is extending.
        boolean  hit   = false // If a unit has been hit.
        boolean  grap  = false // If the caster is grappling.
        
        // Static members.
        static thistype tempData  = 0    // Temp struct needed for data transfer.
        static rect     enumRect  = null // Rect for bouncing off destructables.
        static group    enumGroup = null // Group for enumerating units and buildings.
        static code     enumDests = null // Filter function for finding destructables.
        static boolexpr enumUnits = null // Filter function for finding units.
        static boolexpr enumBuild = null // Filter function for finding buildings.
        
        static real     worldMaxX = 0.00 // Map boundaries.
        static real     worldMaxY = 0.00 // Map boundaries.
        static real     worldMinX = 0.00 // Map boundaries.
        static real     worldMinY = 0.00 // Map boundaries.
        
        // Clean up the struct instance and finish the spell.
        method destroy 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't like this, make DISTANCE 0.00.
            if this.targ != null then
                set IsTarget[this.targ].level = IsTarget[this.targ].level - 1
            endif
            
            if this.grap then
                set IsGrappling[this.cast].level = IsGrappling[this.cast].level - 1
                call SetUnitPathing(this.cast,true)
            endif

            // Clear struct members.
            set this.cast = null
            set this.targ = null
            set this.head = null
            
            set this.ret  = false
            set this.hit  = false
            set this.grap = false
            
            call this.deallocate()
        endmethod
        
        // Quick text tag for easy use.
        method setTextTag takes nothing returns nothing
            local texttag text = CreateTextTag()
            
            static if ABOVE_CASTER then
                call SetTextTagPos(text,GetUnitX(this.cast),GetUnitY(this.cast),0.00)
            else
                call SetTextTagPos(text,GetUnitX(this.head),GetUnitY(this.head),0.00)
            endif
            
            call SetTextTagPermanent(text,false)
            call SetTextTagText(text,TEXT_COLOUR + HEADSHOT_TEXT + &quot;|r&quot;,0.023)
            call SetTextTagVelocity(text,Cos(1.57079) * 0.0355,Sin(1.57079) * 0.0355)
            call SetTextTagLifespan(text,3.00)
            call SetTextTagFadepoint(text,1.50)
            
            set text = null
        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    :)" 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
        
        // Sets the target unit.
        method setTarget takes unit target returns boolean
            set this.targ = target
            set this.hit  = true
            
            set IsTarget[this.targ].level = IsTarget[this.targ].level + 1
            
            // 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
        
        // Find any destructables in range of the head and bounce off them.
        static method getDest takes nothing returns nothing
            local thistype     this = thistype.tempData
            local destructable dest = GetEnumDestructable()
            
            // Only bounce off alive destructables.
            if GetWidgetLife(dest) &gt; 0.406 then
                call this.setAngle(GetWidgetX(dest),GetWidgetY(dest))
                // Kill them if you want.
                static if KILL_DESTS then
                    call KillDestructable(dest)
                else
                    call UnitDamageTarget(this.cast,dest,(DAMAGE(this.level) * DEST_DMG_PERC),true,false,A_TYPE,D_TYPE,W_TYPE)
                endif
            endif
            
            set dest = null
        endmethod
        
        // Searches for buildings to bounce off.
        static method getBuild takes nothing returns boolean
            local thistype this = thistype.tempData
            local unit     filt = GetFilterUnit()
            
            if UnitAlive(filt) and IsUnitType(filt,UNIT_TYPE_STRUCTURE) == true and GetUnitTypeId(filt) != LINK_ID and GetUnitTypeId(filt) != HEAD_ID then
                if this.cast != filt and FilterUnits(this.cast,filt) then
                    static if ALLOW_BOUNCE then // If bouncing is allowed, then set the new angle.
                        call this.setAngle(GetUnitX(filt),GetUnitY(filt))
                    else
                        // Else find out if grappling is allowed.
                        static if ALLOW_GRAPPLE then
                            if not this.grap then
                                if IsGrappling[this.cast].level == 0 then
                                    // If grappling is allowed and the caster is not grappling already, make it grapple.
                                    set IsGrappling[this.cast].level = IsGrappling[this.cast].level + 1
                                    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
                            endif
                        endif
                    endif
                endif
            endif
            
            set filt = null
            
            return false
        endmethod
        
        // Searches for units to become the hook's target.
        static method getUnit takes nothing returns boolean
            local thistype this = thistype.tempData
            local unit     filt = GetFilterUnit()
            local sound    shot = null
            
            if UnitAlive(filt) and IsUnitType(filt,UNIT_TYPE_STRUCTURE) == false and GetUnitTypeId(filt) != LINK_ID and GetUnitTypeId(filt) != HEAD_ID then
                if this.cast != filt and FilterUnits(this.cast,filt) then
                    static if ALLOW_HEADSHOT then // If headshots are allowed continue on.
                        if not this.hit then // If a unit hasn't been it continue on.
                            if IsTarget[filt].level != 0 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 SetSoundVolume(shot,HEADSHOT_VOL)
                                call StartSound(shot)
                                call KillSoundWhenDone(shot)
                                // Create a text tag.
                                call this.setTextTag()
                                // Kill the unit.
                                call UnitDamageTarget(this.cast,filt,1000000.00,true,false,A_TYPE,D_TYPE,W_TYPE)
                                call DestroyEffect(AddSpecialEffectTarget(BLOOD_SFX,filt,BLOOD_POINT))
                                set this.hit = true // Set hit to true
                            else
                                // If the unit isn't a target, make it one.
                                call this.setTarget(filt)
                            endif
                        endif
                    else
                        // If we aren't allowing headshots, then just find a suitable unit and make it the target.
                        if not this.hit and IsTarget[filt].level == 0 then
                            call this.setTarget(filt)
                        endif
                    endif
                endif
            endif
            
            set filt = null
            
            return false
        endmethod
        
        // Retract the hook.
        method retract takes nothing returns nothing
            local Link    linkx = 0
            local integer count = 0
            local unit    temp  = this.list.last.link
            local unit    prev  = null
            local unit    dumm  = null
            local real    casx  = GetUnitX(this.cast)
            local real    casy  = GetUnitY(this.cast)
            local real    temx  = GetUnitX(temp)
            local real    temy  = GetUnitY(temp)
            local real    newx  = 0.00
            local real    newy  = 0.00
            local real    ang2  = 0.00
            local real    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 * 2.00) and not this.grap then   
                // Using a loop so the hooks work when great distances are covered in a short time.
                loop
                    exitwhen dist &lt;= (this.speed * 2.00) 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 * 2.00) * Cos(ang2)
                    set temy = temy + (this.speed * 2.00) * 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 SetUnitScale(linkx.link,LINK_SCALE,LINK_SCALE,LINK_SCALE)
                    
                    static if LIBRARY_AutoFly then
                        call SetUnitFlyHeight(linkx.link,FLY_HEIGHT,0.00)
                    endif
                        
                    // 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 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'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's new position.
                    call SetUnitX(this.targ,GetUnitX(this.head))
                    call SetUnitY(this.targ,GetUnitY(this.head))
                    
                    static if DISCARD_TARGET then
                        // Check if it is bead so it can be discarded.
                        if not UnitAlive(this.targ) then
                            set IsTarget[this.targ].level = IsTarget[this.targ].level - 1
                            set this.targ = null
                            call SetUnitTimeScale(this.head,TIME_SCALE)
                        endif
                    endif
                else
                    // Otherwise, find a new unit.
                    call GroupEnumUnitsInRange(thistype.enumGroup,GetUnitX(this.head),GetUnitY(this.head),UNIT_RADIUS,thistype.enumUnits)
                endif
            endif
            
            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)
            
                if linkx.prev.prev != 0 then
                    set dumm = linkx.prev.prev.link
                    if IsUnitInRange(temp,dumm,(this.speed * 1.50)) then
                        call this.list.detach(linkx.prev)
                        call SetUnitFacing(temp,(Atan2((GetUnitY(dumm) - temy),(GetUnitX(dumm) - temx)) * bj_RADTODEG) + 180.00)
                    endif
                endif
                
                // 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 IsUnitInRange(temp,this.cast,this.speed) then
                    call this.list.detach(linkx)
                    // Detach the link if needed.
                endif
                
                // Set the chain links new position.
                if not this.grap 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 
            
            set temp = null
            set prev = null
            set dumm = null
        endmethod
        
        // Extend the hook.
        method extend takes nothing returns nothing
            local Link    linkx = 0
            local integer count = 0
            local unit    temp  = this.list.last.link
            local unit    next  = null
            local unit    dumm  = null
            local real    casx  = GetUnitX(this.cast)
            local real    casy  = GetUnitY(this.cast)
            local real    temx  = GetUnitX(temp)
            local real    temy  = GetUnitY(temp)
            local real    newx  = 0.00
            local real    newy  = 0.00
            local real    ang2  = 0.00
            local real    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 * 2.00) then 
                // Using a loop so the hooks work when great distances are covered in a short time.
                loop
                    exitwhen dist &lt;= (this.speed * 2.00) 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 * 2.00) * Cos(ang2)
                    set temy = temy + (this.speed * 2.00) * 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 SetUnitScale(linkx.link,LINK_SCALE,LINK_SCALE,LINK_SCALE)
                    
                    static if LIBRARY_AutoFly then
                        call SetUnitFlyHeight(linkx.link,FLY_HEIGHT,0.00)
                    endif                    
                    
                    // 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.head

            // 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)
                
                if linkx.next.next != 0 then
                    set dumm = linkx.next.next.link
                    if IsUnitInRange(temp,dumm,(this.speed * 1.50)) then
                        call this.list.detach(linkx.next)
                        call SetUnitFacing(temp,(Atan2((GetUnitY(dumm) - temy),(GetUnitX(dumm) - temx)) * bj_RADTODEG) + 180.00)
                    endif
                endif
                
                // 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 &gt; thistype.worldMaxX or newx &lt; thistype.worldMinX 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 &gt; thistype.worldMaxY or newy &lt; thistype.worldMinY 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 units, buildings and destructables.
                    set thistype.tempData = this
                    call GroupEnumUnitsInRange(thistype.enumGroup,newx,newy,UNIT_RADIUS,thistype.enumUnits)
                    call GroupEnumUnitsInRange(thistype.enumGroup,newx,newy,BUILD_RADIUS,thistype.enumBuild)
                    
                    static if DEST_BOUNCE then
                        call SetRect(thistype.enumRect,newx - DEST_RADIUS,newy - DEST_RADIUS,newx + DEST_RADIUS,newy + DEST_RADIUS)
                        call EnumDestructablesInRect(thistype.enumRect,null,thistype.enumDests)
                    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
            
            // If the max distance has been reached or a unit has been hit, stop extending.
            if this.dist &gt;= this.max or this.hit then
                set this.ret = true
            else
                set this.dist = this.dist + this.speed
            endif
            
            set temp = null
            set next = null
            set dumm = null
        endmethod
        
        // The handler method, makes things much easier.
        method periodic takes nothing returns nothing
            if this.list.size == 0 then // If the hook is done.
                call this.stopPeriodic()
                call this.destroy()     // Clean up the struct.
            else
                if this.ret then        // If the hook is retracting.
                    call this.retract() // Keep retracting.
                else                    // Otherwise.  
                    call this.extend()  // Extend it.
                endif
            endif
        endmethod
        
        implement T32x
        
        // Start of the hook, assembles struct members and stuff.
        static method actions takes nothing returns boolean
            local thistype this = thistype.allocate()
            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((GetSpellTargetY() - casy),(GetSpellTargetX() - 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) * T32_PERIOD
            set this.max   = MAXDIST(this.level)
            
            // 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 SetUnitScale(this.head,HEAD_SCALE,HEAD_SCALE,HEAD_SCALE)
            
            static if LIBRARY_AutoFly then
                call SetUnitFlyHeight(this.head,FLY_HEIGHT,0.00)
            endif
            
            // Play a casting animation.
            call SetUnitAnimationByIndex(this.cast,ANIM_INDEX)
            
            // Add this instance to T32.
            call this.startPeriodic()
            
            // 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 timer time = GetExpiredTimer()
            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(time)
            call DestroyTimer(time)
            
            set time = null
            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.enumRect  = Rect(0.00,0.00,1.00,1.00)
            set thistype.enumGroup = CreateGroup()
            set thistype.enumDests = function thistype.getDest
            set thistype.enumUnits = Filter(function thistype.getUnit)
            set thistype.enumBuild = Filter(function thistype.getBuild)
            
            set thistype.worldMaxX = GetRectMaxX(bj_mapInitialPlayableArea) - 64.00
            set thistype.worldMaxY = GetRectMaxY(bj_mapInitialPlayableArea) - 64.00
            set thistype.worldMinX = GetRectMinX(bj_mapInitialPlayableArea) + 64.00
            set thistype.worldMinY = GetRectMinY(bj_mapInitialPlayableArea) + 64.00
            
            // Add a &quot;starts effect&quot; event to the spell.
            call GT_AddStartsEffectAction(function thistype.actions,ABIL_ID)
            
            // Needed to load the sound properly.
            call TimerStart(CreateTimer(),0.00,false,function thistype.loadSound)
            
            // If preloading is allowed, then preload.
            static 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 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
    
endlibrary


Notes:

I think this spell is finally 100% complete, unless more bugs are found, or I want to change something.

Changelog:

  • Version 1.0.0 - Initial release. [5th August, 2009]
  • Version 1.0.1 - Minor update. Added configurable descriptions, updated implementation, updated the headshot sound to not use gg_snd_headshot. [5th August 2009]
  • Version 1.0.2 - Minor update. The spell -should- now work with the 1.24 patch. Also fixed a bug found by CrackUps involving grappling and pathing. [10th August 2009]
  • Version 1.0.3 - Minor update. Rewrote the retract method for the spell (removed about 20 lines). Added another configurable option. [10th August 2009]
  • Version 1.0.4 - Minor update. Added sound preloading so that the headshot sound works first go. Thanks for the motivation BlackRose. [12th August 2009]
  • Version 1.0.5 - Minor update. Fixed some small bugs found with the spell and added the option for the hook to bounce off of destructables. Also updated configurables descriptions, and some very minor script changes. [18th August 2009]
  • 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. [3rd September 2009]
  • Version 1.0.7 - Pretty major update. Reduced the amount of work that is needed by the spell by getting rid of one of the O(n) complexity searches every interval. Making it far more efficient and friendly for larger games. [18th September 2009]
  • Version 1.1.0 - Major update. Made all the changes that were in the last pages of this thread, including: actually making the linked list implementation work, efficiency updates, cleaning up the code, requiring more libraries and all that stuff.
  • Version 1.1.1 - Minor update. Fixed a small bug found by INCINERATE. [6th January 2010]
  • Version 1.1.2 - Minor update. Added an extra configurable option for sound. Added a struct member for max distance to reduce function calls. [20th January 2010]
 

Attachments

Tom_Kazansky

--- wraith it ! ---
it's great that you have fixed the bug with sudden movement (Blink), +rep :D

I only test the spell but the code, I think I will pass, too much for me :rolleyes:
 

Kenny

Back for now.
it's great that you have fixed the bug with sudden movement (Blink), +rep :D
Thanks for the kind words. There were quite a few things like that I wanted to fix. Hence the submission.

I only test the spell but the code, I think I will pass, too much for me
I think around 15 - 20 percent of the code is just comments and spacing to make it easier to read, haha. But still it is a bit of a mission to read through it.

Lol. Local Sound instead of global ^_^ There is my +1 Post now
I will be doing that as soon as there is time. :D
 

D.K.

New Member
I can't test the map for some reason... I tried creating it and it says "game not found." Maybe the new patch is preventing this??
 

Kenny

Back for now.
Have you tried opening it using NewGen World Editor, and testing it from there? I have given the map to quite a few people now and no one has had this problem. Although it may just be that the new patch could be causing this...
 

Kenny

Back for now.
No it doesn't. I gave it a try, but I could not get it to work successfully (only bounced correctly every second or third time it hit a wall for some odd reason).

Question for all potential readers:

Do you think I should allow users to define the "head" and "link" scale size and fly height in the script itself, or make them do it in the OE?

I personally love having everything in front of me in the script, but I am leaning towards making users use the OE.
 

D.K.

New Member
Have you tried opening it using NewGen World Editor, and testing it from there? I have given the map to quite a few people now and no one has had this problem. Although it may just be that the new patch could be causing this...
Yep, i tried it with newgen and even tried implementing it into a blank map o.o. Anyone else tried it?
 

cleeezzz

The Undead Ranger.
Patch 1.24 messed it up, i fixed it to the request of a friend. Kenny!, tell me if you want me to remove it.

(I did not change anything about your code, just the structure a tiny bit so it doesn't return false positive on return bug)
 

GetTriggerUnit-

DogEntrepreneur
You explain why do you destroy a handle in a struct, but you don't explain what the constant is. I would have loved having explainations.
 

Kenny

Back for now.
@ Cleeezzz:

That is on my to do list. I can't use 1.24, and I think it is a hopeless attempt at fixing things, so it got put back to the end of my list, but I will get around to it.

Also I will just delete the handle counter, it has no purpose in the map.

@ GetTriggerUnit-:

By "the constant" do you mean all the configurable constant globals? If so, then there is a whole section on them, just read the first post. Otherwise, please explain to me what you mean.
 

cleeezzz

The Undead Ranger.
mmm so do you want me to remove it?

>and I think it is a hopeless attempt at fixing things

hmm.. but isn't that the only way to fix it? (unless i misunderstood what you were saying)
 

GetTriggerUnit-

DogEntrepreneur
I would like that you explain every constant with //explain what they change

I was confused because you explained why you destroy x thing in the struct but you don't explain the constants.

Thats all.
 

Kenny

Back for now.
@ GetTriggerUnit-:

You mean this configurables section that can be found in the first post?

@ Cleeezzz:

You can remove it. I will upload a version that works with 1.24 soon.

Also, I meant that it was a poor ATTEMPT. Although the only way to fix the problems was to update warcraft with a new patch, it was poorly executed.

@ Everyone:

Anyone got any comments on the spell/script/anything to do with this thread?
 

Kenny

Back for now.
Takes up too much room (script would also be going on 1000 lines..), and the configuration section becomes messy. I prefer reading through clean config sections. And I only commented the rest of the script by request from a few people, so they could understand it better.
 

CrackUps

New Member
If you've got the grapple setting set to true, then throw a hook and blink behind a building you can get stuck. Image attached to show you what I mean

 

Kenny

Back for now.
Thanks for the find CrackUps, I never really tested grapple to find that out. +rep.

Version 1.0.2 is out. Spell should now work with the 1.24 patch, and the bug pointed out by CrackUps is fixed.
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • The Helper The Helper:
    Have been talking to a childhood friend who is an Artist and is interested in doing some game music. He has a studio in his house. Yes!
  • The Helper The Helper:
    Going to try and get my friend into the homebrew game indie market he is real interested
  • Varine Varine:
    Does reforged require the port forwarding? Is that even still a thing?
  • The Helper The Helper:
    I do not think that is a thing anymore
  • The Helper The Helper:
    Battle.net is different now and I do not think they are using the same network code
  • The Helper The Helper:
    I am sure they are not
  • tom_mai78101 tom_mai78101:
    Lately, news aren't as enticing as they seemed. Or maybe I'm getting old and desensitized.
  • jonas jonas:
    I think news are more serious recently
  • tom_mai78101 tom_mai78101:
    More serious doesn't necessarily mean it's more interesting. :(
  • The Helper The Helper:
    I find that news.google.com has lots of interesting science stuff going on and the Space X stuff with Starship is super cool too
  • The Helper The Helper:
    plus the boring company and physics stuff happening now but I am more into the science now but there is still some crazy news I see out there using Reddit as a news source though and aggregate services like news.google and stuff and put AP in there I always read the news and yes sometimes it is not as interesting as others
  • The Helper The Helper:
    At some point I am going to look at Xenforo and see if it integrates with Hubspot at all. Would love to look at forum under a full marketing account in Hubspot since I have access to that stuff with my current gig
  • Ghan Ghan:
    Kind of surprised it wouldn't have its own forum product offering.
  • Ghan Ghan:
    That said, when Blizzard switched forum software, they went with Discourse, not xenForo.
  • jonas jonas:
    I think news.google shows you personalized news
  • midnight8 midnight8:
    any of the peeps in here run youtube channels? Figure of all the peeps in this forum, got to be some other geeks like me with channels
  • Ghan Ghan:
    I can say I've uploaded a YouTube video.
    +1
  • The Helper The Helper:
    I can say I know someone that uploaded a youtube video :)
  • jonas jonas:
    A friend of mine has a channel where he uploads some of the crazy machines he has built. Like when he plugged an electric motor into a 3d printed gearbox, attached it to his bycicle and then drove 50mph with it.
  • The Helper The Helper:
    You should post a link to his channel in General Discussion I would definitely check it out
  • The Helper The Helper:
    how hard is it to put google analytics on the site?
  • Ghan Ghan:
    Likely not hard. I think we've done it in the past. Anything in particular you want to see?
  • The Helper The Helper:
    I remember we are already getting stats

    Members online

    No members online now.

    Affiliates

    Hive Workshop NUON Dome
    Top