Spell Pudge Wars Meat Hook Revisited

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. :)
 
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.
 
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?
 
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.
 
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.
 
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 :/....
 
I think I found a bug.
Please download and view this replay.
 

Attachments

  • MeatHookBug.w3g
    32.5 KB · Views: 381
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.
 
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: 422
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...
 
> 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.
 
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
 
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
 
@ 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

...
 
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?
 
>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.
 
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.
  • Ghan Ghan:
    Hopefully this won't cause problems.
  • Ghan Ghan:
    Hmm
  • Ghan Ghan:
    I have converted the Headline News forum to an Article type forum. It will now show the top 20 threads with more detail of each thread.
  • Ghan Ghan:
    See how we like that.
  • The Helper The Helper:
    I do not see a way to go past the 1st page of posts on the forum though
  • The Helper The Helper:
    It is OK though for the main page to open up on the forum in the view it was before. As long as the portal has its own URL so it can be viewed that way I do want to try it as a regular forum view for a while
  • Ghan Ghan:
    Yeah I'm not sure what the deal is with the pagination.
  • Ghan Ghan:
    It SHOULD be there so I think it might just be an artifact of having an older style.
  • Ghan Ghan:
    I switched it to a "Standard" article forum. This will show the thread list like normal, but the threads themselves will have the first post set up above the rest of the "comments"
  • The Helper The Helper:
    I don't really get that article forum but I think it is because I have never really seen it used on a multi post thread
  • Ghan Ghan:
    RpNation makes more use of it right now as an example: https://www.rpnation.com/news/
  • The Helper The Helper:
  • The Helper The Helper:
    What do you think Tom?
  • tom_mai78101 tom_mai78101:
    I will have to get used to this.
  • tom_mai78101 tom_mai78101:
    The latest news feed looks good
  • The Helper The Helper:
    I would like to see it again like Ghan had it the first time with pagination though - without the pagination that view will not work but with pagination it just might...
  • The Helper The Helper:
    This drink recipe I have had more than a few times back in the day! Mind Eraser https://www.thehelper.net/threads/cocktail-mind-eraser.194720/
  • The Helper The Helper:
    Happy Thursday!
  • Ghan Ghan:
    I'm not so sure, there's still a lot of Thursday left for it to become unhappy.
    +3
  • The Helper The Helper:
    still a few hours
  • The Helper The Helper:
    Its Friday!!!!
    +1
  • Ghan Ghan:
    Thursday 7/10 would Thursday again
    +3
  • The Helper The Helper:
    New forum coming soon going to be for my friend Saylor who is blind and he is doing motivational speaking and has an internet radio show and podcast. I am going to put a forum to promote him and other inspirational speaker stuff going to have some good stuff in it.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top