Spell Pudge Wars Meat Hook Revisited

Kenny

Back for now.
Reaction score
202
Bump.

Can't actually see anything that could need some optimising.

Actually, there was something that could be optimised (and maybe still is).

Turned this:

JASS:
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 IsUnitInRange(temp,next,(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


Into this:

JASS:
if linkx.next.next != 0 then
    set dumm = linkx.next.next.link
                    
    if IsUnitInRange(temp,dumm,(this.speed * 0.75)) then
        call this.list.detach(linkx.next)
        call SetUnitFacing(temp,(Atan2((GetUnitY(dumm) - temy),(GetUnitX(dumm) - temx)) * bj_RADTODEG) + 180.00)
    endif
endif


Notice that there is no more O(n) search (the loop) for this part anymore. It is done in the other loop now.

This actually increased efficiency quite a bit (able to run even more hooks at once). This is because I was stupidly doing 2 O(n) type searches for the "chain links" per interval, when I could have only been doing 1. So yeah, I will update this when I get the chance. The new version will be far more efficient. :D

Also:

Please comment on this someone. Some hard work went into it. :)
 

DubDub

New Member
Reaction score
0
Is there any way to filter out the caster's allies from being pulled?

*edit* I used the function you put in but there are too many units in my map to put in, one by one. I'm not familiar enough with jass, so I haven't figured out a way to filter out Ally units only.
 

DubDub

New Member
Reaction score
0
JASS:
    private function FilterUnits takes unit caster, unit filter returns boolean
        return GetUnitFlyHeight(filter) < MIN_HEIGHT and GetUnitAbilityLevel(filter,LOCUST_ID) < 1
    endfunction

Add IsUnitEnemy() to that function?

Thanks for the reply.
Now how exactly do I add that in? :confused:
I've got an idea as to how it works but I'm still a bit lost. Any help is appreciated.

*edit* I think I have it figured out
JASS:
    private function FilterUnits takes unit caster, unit filter returns boolean
        return GetUnitFlyHeight(filter) < MIN_HEIGHT and GetUnitAbilityLevel(filter,LOCUST_ID) < 1 and IsUnitEnemy(caster, GetOwningPlayer(filter))
    endfunction


Is this correct?
 

Kenny

Back for now.
Reaction score
202
Is this correct?

Almost. You have to switch the 'caster' and 'filter' units around in the function you added.

Try this:

JASS:
private function FilterUnits takes unit caster, unit filter returns boolean
    return GetUnitFlyHeight(filter) < MIN_HEIGHT and GetUnitAbilityLevel(filter,LOCUST_ID) < 1 and IsUnitEnemy(filter,GetOwningPlayer(caster)) == true
endfunction


EDIT:

Oh and...

Version 1.0.7 released. Updated to include the major efficiency gains I discussed above.
 

Executor

I see you
Reaction score
57
JASS:
// The handler method, makes things much easier.
        static method handler takes nothing returns nothing
            local thistype this = thistype(0).next // First struct instance. T32 implementation.
            
            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 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't needed.
            if thistype(0).next == 0 then
                call PauseTimer(thistype.Timer)
            endif
        endmethod


I think this would be a little improvement:

JASS:
// The handler method, makes things much easier.
        static method handler takes nothing returns nothing
            local thistype this = thistype(0).next // First struct instance. T32 implementation.

            // Pause the timer when it isn't needed.
            if thistype(0).next == 0 then
                call PauseTimer(thistype.Timer)
                return
            endif           
            
            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 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
        endmethod


Well I know this case is very seldom, but in the case the timer has to pause it will directly return without entering the loop and checking the condition.
 

Trollvottel

never aging title
Reaction score
262
JASS:
            if thistype(0).next == 0 then
                call PauseTimer(thistype.Timer)
                return
            endif


well, if you do so, you could do this aswell:


JASS:
            if this == 0 then
                call PauseTimer(thistype.Timer)
                return
            endif


BUt you wont make anything better with the change anyway. if put the check in front, the timer will expire once more :/....
 

game_war48

New Member
Reaction score
5
I think I found a bug.
Please download and view this replay.
 

Attachments

  • MeatHookBug.w3g
    32.5 KB · Views: 365

Kenny

Back for now.
Reaction score
202
Bump.

I unfortunately cannot view the replay, and I haven't got any information on the bug. So I can't do much about that for now.

From my tests everything seems to be working as designed. Although I would like a better function for finding the bouncing angle of the hook.
 

Jesus4Lyf

Good Idea™
Reaction score
397
I was having a lot of fun with this.
And then...
SUDDENLY PANCAKE.
And the fun was no more... :(
 

Attachments

  • faillink.JPG
    faillink.JPG
    193.2 KB · Views: 409

Kenny

Back for now.
Reaction score
202
Thanks for bringing that to my attention. However, I cannot for the life of me replicate that bug.

Can you give me any more information on what was happening when it occurred?

The error message is what confuses me the most. There should be no problem with id allocation...
 

Kenny

Back for now.
Reaction score
202
> assume you're leaking links. ;)

Yep. You are right. My friend was the one to help me with the linked list type stuff (as I didn't know much about them back when I did this). He said he knew how it was done in Pudge Wars. He obviously doesn't. :p

I'm looking for a way to fix the linked list struct while still keeping the same interface and usage (Keep the attach and detach methods working the same).

So far... I am clueless. :p But I'll get there, I hope.

Edit:

Meh, pretty sure I figured out a solution to the bug. Just running some tests now.

This seems to be working fine:

JASS:
.
    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.
        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.size == 0 then
                set this.head = curr
            elseif this.size == 1 then
                set curr.prev = this.head
                set this.head.next = curr
            elseif this.size > 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.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.prev == 0 then
                set this.head = curr.next
                if curr.next != 0 then
                    set curr.next.prev = 0
                endif
            else
                set curr.prev.next = curr.next
            endif
            
            // Checking if the current link is the head (above) or last (below) link.
            if curr.next == 0 then
                set this.last = curr.prev
                if curr.prev != 0 then
                    set curr.prev.next = 0
                endif
            else
                set curr.next.prev = curr.prev
            endif
            
            // 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


I will probably wait to update this (even though it is important), just in case T32 is updated to include some changes that could really benefit this spell.
 

BRUTAL

I'm working
Reaction score
118
I managed to bug the spell, though i tinkered with it like setting the max distance to 80'000 and maybe like the DISTANCE and INTERVAL globals. (Though i'm not entirely sure if i touched those two variables at that time.)
Anyways here are some screen shots.
bug1p.jpg

In this one, i casted the spell after a while of playing around with it, and eventually the chain units were all gone from the hook so it was just the head moving as you can see.
bug2.jpg

Then the head just stopped at one point, and then the footmen started attacking it.
I just remembered i took away locust from the head unit, maybe that contributed?

So after watching that head unit for a while, i went to cast the spell and what happened was that only the head unit was created
bug3m.jpg

but chain units were only created when i moved the abom around, so the head unit wasn't even moving. (This happened more then once, as you can see there is two head units in the picture.)
And here is just another screen shot of the abom walking around more
bug4x.jpg
 

Romek

Super Moderator
Reaction score
963
You should add a sentence or so next to each configurable, to explain what it does.

Also, perhaps a [ljass]MAX_LINKS[/ljass] which determines the size of the links struct?
8190 seems like it'd be hit quite often.

@ Brutal:
That's probably due to the link limit. :p
 

Kenny

Back for now.
Reaction score
202
@ Brutal:

That is probably due to the bug that Jesus4Lyf discovered.

Link structs weren't being properly destroyed, causing the struct to hit the allocation limit if you used the spell too much.

This bug has been fixed already and I will be releasing a new version soon. :)

@ Romek:

You should probably check the configurables section which can be found above the script for the spell in the first post. :)

Also I will probably add the [ljass]MAX_LINKS[/ljass] size definition in the next update.

You would be surprised about reaching the limit too. In all practical circumstances, it is near impossible.

You would need something like forty or fifty 5000 range hooks running at once.

By then, you have already created enough units (all of which are moving) to make your computer lag like crazy.

Anywho, some of the changes in the next version:

- Added a MAX_LINKS constant global in case someone needs to change it.
- Fixed the link allocation problem.
- Re-write of the linked list needed for the chain units. It is now more efficient for adding units, and doesn't bug when detaching.
- Slightly changed the movement of the chain. It now looks like it is actually moving. This is just a visual change, but testing by others is welcome to make sure it works properly.
- Improved visual effects when the hook bounces at certain angles. This is a major improvement for me as I always hated when that problem came up.
- I was also thinking about getting rid of the [ljass]GetSpellTargetLoc()[/ljass] in the spell, but then it wouldn't be compatible with pre 1.23b warcraft or whatever it is.

Edit:

I've been working on optimising the linked list implementation I'm using. The old one my friend developed was pretty hopeless. The one I have now is far more efficient, and it actually seems to work.

New implementation:

JASS:
...

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

...
 

Silencer

New Member
Reaction score
0
Is it possible to remove the "iswalkable" check? So i can hook over a river, like in pudge wars?

And is it possible to allow only hook in an area?
 

Kenny

Back for now.
Reaction score
202
>Is it possible to remove the "iswalkable" check? So i can hook over a river, like in pudge wars?

There is no "iswalkable" check. The spell uses destructables to check for cliffs and stuff.

The only problem with going over rivers like in Pudge Wars, is that this version will follow the curvature of the terrain, making it looks weird.

I am still trying to find a way to fix that.

>And is it possible to allow only hook in an area?

Maybe. But it is unlikely that it will become a feature of this spell, as it is too obscure.

More updates that will come with the next version:

- The spell will probably now require: GTrigger, T32 and AIDS. These systems make things far more efficient.
- Removed and inlined 4 functions from the spell. Checking for already targeted units and already grappling heroes is now O(1) complexity.
- Fixed a bug with grappling and the new chain movement I've been working on.
- Removed some useless stuff.
- Far more efficient list attaching and detaching.

Also, just for the fun of it, I wrote up this:

JASS:
library MeatHookReq

//! external ObjectMerger w3u ushd hreq unam "Retracted Hook" uabi Aloc umvs 0 umvh 0 ushu "" umdl "Dummy.mdl" ufoo 0 ucol 0 uhpm 1000000 usid 0 usin 0

    globals
        private constant integer HERO_ID       = 'Hpal' // Raw code for the hero that is using meat hook.
        private constant integer REQUIRED_UNIT = 'hreq' // Raw code of the unit made by the above object merger line.
    endglobals
    
    private function HeroFilter takes unit filter returns boolean
        return GetUnitTypeId(filter) == HERO_ID
    endfunction

    //---------------------------------------------------------------------\\
    //                                                                     \\
    //  DO NOT TOUCH PAST THIS POINT UNLESS YOU KNOW WHAT YOUR ARE DOING!  \\
    //                                                                     \\
    //---------------------------------------------------------------------\\
    
    struct HookReq extends array
        //! runtextmacro AIDS()
        
        private unit req
        
        private static method AIDS_filter takes unit u returns boolean
            return HeroFilter(u)
        endmethod
        
        private method AIDS_onCreate takes nothing returns nothing
            set this.req = CreateUnit(GetOwningPlayer(this.unit),REQUIRED_UNIT,0.00,0.00,0.00)
        endmethod
        
        private method AIDS_onDestroy takes nothing returns nothing
            call RemoveUnit(this.req)
            set this.req = null
        endmethod
        
        method remove takes nothing returns nothing
            call SetUnitOwner(this.req,Player(PLAYER_NEUTRAL_PASSIVE),true)
        endmethod
        
        method add takes nothing returns nothing
            call SetUnitOwner(this.req,GetOwningPlayer(this.unit),true)
        endmethod
        
    endstruct
    
endlibrary


If you import this library, along with the object merger line. And then put the object merger unit as a requirement for the Meat Hook spell, it will act as the requirement in Pudge Wars. Making it so that you cannot cast another hook until one retracts.

However, the above library is only MPI, I don't think there is any reliable way to make it MUI.

More criticisms and ideas are welcome. If you have found anything that I may have missed, feel free to tell me.
 

Kenny

Back for now.
Reaction score
202
I have finally had time to update this. The next version will require:

- GTrigger by Jesus4Lyf.
- T32 by Jesus4Lyf.
- AIDS by Jesus4Lyf.
- AutoFly by Azlier (optional).
- JassHelper 0.A.2.9 (Latest version).

If anyone has anything they want added to this, now is the time to tell me, so that I can put them in this update.

So far, this version looks to be the final version unless something is found or I wish to change something.

Comments and criticisms are always welcomed, as usual.

Edit:

New code:

- Managed to shave 100 lines off.
- Much more efficient.
- Looks cooler.
- 180 degree bounces work correctly.
- Awesome.

JASS:
//------------------------------------------------------------------------------------\\
//                         Pudge Wars Meat Hook [v1.1.0]                              \\
//                                   By kenny!                                        \\
//                            Constructed using vJASS                                 \\
//            Requires NewGen WE, GT, AIDS, T32 & 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 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.25
        private constant real       LINK_SCALE       = 1.75
        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        = "Objects\\Spawnmodels\\Human\\HumanBlood\\BloodElfSpellThiefBlood.mdl"
        private constant string     BLOOD_POINT      = "origin"
        private constant string     HEADSHOT_TEXT    = "HEADSHOT!"
        private constant string     TEXT_COLOUR      = "|c00ff0000"
        private constant string     HEADSHOT_SOUND   = "war3mapImported\\headshot.wav"
        
        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) < 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     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.
        
        // 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 + "|r",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
        
        // 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
                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 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
        
        // 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
        
        // 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 this.grap == false 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&#039;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 this.hit == false then // If a unit hasn&#039;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 StartSound(shot)
                                call KillSoundWhenDone(shot)
                                // Create a text tag.
                                static if ABOVE_CASTER then
                                    call thistype.textTag(GetUnitX(this.cast),GetUnitY(this.cast))
                                else
                                    call thistype.textTag(GetUnitX(this.head),GetUnitY(this.head))
                                endif
                                // 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&#039;t a target, make it one.
                                call this.setTarget(filt)
                            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 IsTarget[filt] == 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&#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))
                    
                    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 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 
            
            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;= MAXDIST(this.level) 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.
                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
            
            // 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
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top