• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[System] Slide System

Level 12
Joined
Mar 28, 2005
Messages
160
Slide System 1.c



The goal here was to make an all encompassing, end all be all Slide/Knockback System. I think I have created that, minus any glaring slights on my behalf.

The main issue I had with the extended knockback system is that you can't have multiple concurrent slides on a single unit - I alleviate this issue by calculating the vector components of the current slide and the updated slide, which allows me to implement all slides created into a single instance (which also increases system performance). Also, a few of the scripting techniques in the previous are a little archaic (ex. no static ifs), that, along with its heavy external system requirements, makes for a less then desirable system.

A newer system is Bribe's well received Knockback 2D, which, being written in GUI, is inherently slower, and doesn't/cannot allow for the same amount of functionality found here.


We will need to implement the slideStruct module where we want to use it (see Demo).

Wrappers for the struct's methods are inbound, in the not to distant future (GUI friendly).

Test map in route also....a Demo trigger is included in the interim.

Shared methods include: OnLoop (on every timer interval), OnCollision (on unit collision), OnCliff (on cliff collision), OnRest (on slide end). All methods take "slide s", except for OnCollision, which takes "slide s, unit t", and return nothing.

The syntax may need to be tweaked a bit, but I think it fits pretty good.

Critiques and other thoughts welcome. I humbly submit for consideration.

Requirements:
  • UnitAlive native
  • A Dummy caster
  • A Dummy tree destruction ability (as seen in Knockback 2D)

Script:
JASS:
//=========================\\
//==System made by emjlr3==\\
//==Version 1.c, 02/04/12==\\
//=========================\\
library SlideSystem

//== CONFIGURATION ==\\
globals    
    // bounce off terrain?
    private constant    boolean         BOUNCE  = true
    // collide with other units?
    private constant    boolean         COLL    = true
    // allow movement during slides?
    private constant    boolean         MOVE    = true
    // destroy trees in slide path?
    private constant    boolean         TREES   = true
    // tree destruction ability rawcode
    private constant    integer         ABIL    = 'tree'
    // rawcode of your map's caster dummy
    private constant    integer         DUMMY   = 'dumy'
    // conserved slide velocity/TIMEOUT
    private constant    real            FRICT   = .96
    // momentum conserved upon collision, what's loss is transfered to the colliding unit, if it exists
    private constant    real            LOSS    = .75
    // collision radius
    private constant    real            RADIUS  = 128.
    // minimum slide velocity
    private constant    real            STOP    = 3.0
    // periodic timer interval
    private constant    real            TIMEOUT = .031250000
    
    // we will configure this global below
    private             string array    SFX
endglobals
private function setupSlideSFX takes nothing returns nothing
    // the system assumes the array placements are unchanged
    // i speculate as to terrain types using known means; this isn't fool proof
    // if you are interested in snow/ice/lava slide sfx, let me know
    
    // ground slide sfx
    set SFX[1]="war3mapImported\\LandSlide.mdx" 
    // water slide sfx
    set SFX[2]="war3mapImported\\SeaSlide.mdx"
    // air slide sfx
    set SFX[3]="war3mapImported\\AirSlide.mdx" 
    // slide collision sfx
    set SFX[4]="Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl"
endfunction

//== NO TOUCHING PAST THIS POINT ==\\
native UnitAlive takes unit u returns boolean
globals
    public              group           SLIDING = CreateGroup()
    private             group           ENUM    = CreateGroup()
    private             hashtable       HT      = InitHashtable()
    private constant    integer         LVL     = IMaxBJ(4,R2I(RADIUS/32.))   
    private             location        LOC     = Location(0.,0.)
    private             location        LOC2    = Location(0.,0.)
    private             real            RSTOP   = STOP*TIMEOUT
    private             timer           T       = CreateTimer()
    private             unit            DUM     = null
endglobals

struct slide
    thistype next
    thistype prev
    
    unit    u
    player  owner
    real    xP
    real    yP
    real    xV
    real    yV
    real    ang
    real    dist=0.         // total distance slid
    real    time=0.         // total duration slid
    effect  sfx
    group   g
    integer gt=0            // ground type (numbers correspond to SFX type)
    boolean remove=false
    
    method destroy takes nothing returns nothing
        // update linked list
        set .prev.next=.next
        set .next.prev=.prev
        // clean up
        call GroupRemoveUnit(SLIDING,.u)
        call DestroyEffect(.sfx)
        call SetUnitPathing(.u,true)
        call DestroyGroup(.g)
        call .deallocate()
    endmethod
    
    method getDeflection takes unit t returns boolean
        local thistype s
        local real a
        local real d  
        local real l
        local real r
        local real x
        local real dx
        local real y
        local real dy
        local real z
        
        call MoveLocation(LOC,.xP,.yP)
        set z=GetLocationZ(LOC)+GetUnitFlyHeight(.u) // slider zP                    
        set x=GetUnitX(t)
        set y=GetUnitY(t)
        call MoveLocation(LOC2,x,y)
        // collision detected
        if RAbsBJ(z-(GetLocationZ(LOC2)+GetUnitFlyHeight(t)))<=RADIUS*2. then
            // currently we can only collide once/target
            call GroupAddUnit(.g,t)
                        
            // account for momentum loss
            set l=SquareRoot(.xV*.xV+.yV*.yV)
            set r=l*LOSS
            set l=(l-r)/TIMEOUT
                        
            //calculate angular relationship
            set dx=x-.xP
            set dy=y-.yP
            set a=Atan2(dy,dx)
            
            // update vector components
            set .ang=2.*a+bj_PI-.ang
            set .xV=r*Cos(.ang)
            set .yV=r*Sin(.ang)
            
            // create collision effect between two units
            set r=SquareRoot(dx*dx+dy*dy)/2.
            call DestroyEffect(AddSpecialEffect(SFX[4],.xP+r*Cos(a),.yP+r*Sin(a)))
            
            // slide target
            if IsUnitType(t,UNIT_TYPE_STRUCTURE)==false then
                set s=thistype.createSlide(t,l*Cos(a),l*Sin(a))
                call GroupAddUnit(s.g,.u)
                return true
            endif
        endif
        
        return false
    endmethod
    method findCliffCollision takes nothing returns boolean
        local real array r
                
        call MoveLocation(LOC,.xP,.yP)
        // height at or above which the slider will collide with the given terrain loc
        set r[1]=GetLocationZ(LOC)+GetUnitFlyHeight(.u)-(RADIUS/2.)
        set r[2]=.ang-.7854 // bj_PI/4.
        set r[3]=.ang+.7854
        // loop in a quarter circle
        loop
            exitwhen r[2]>r[3] // stop after the quarter circle
            set r[4]=.xP+(RADIUS/2.)*Cos(r[2])
            set r[5]=.yP+(RADIUS/2.)*Sin(r[2])
            call MoveLocation(LOC,r[4],r[5])
            set r[6]=GetLocationZ(LOC)
            // collision found, and better than the last
            if r[6]>=r[1] and r[6]>r[9] then
                // update dynamic array members
                set r[7]=r[4] // x
                set r[8]=r[5] // y
                set r[9]=r[6] // z
            endif
            // continue to loop, bj_PI/16.
            set r[2]=r[2]+.1963 
        endloop
        call MoveLocation(LOC,.xP,.yP)
        // we have a cliff collision
        if r[9]>0. and r[9]-GetLocationZ(LOC)>=15. then
            // create collision effect where we strike
            call DestroyEffect(AddSpecialEffect(SFX[4],r[7],r[8]))
            // update velocity vectors
            static if BOUNCE then
                set .ang=2.*Atan2(r[8]-.yP,r[7]-.xP)+bj_PI-.ang
                set r[1]=SquareRoot(.xV*.xV+.yV*.yV)*LOSS
                set .xV=r[1]*Cos(.ang)
                set .yV=r[1]*Sin(.ang)
            else
                set .xV=0.
                set .yV=0.
            endif
            
            return true
        endif
        
        return false
    endmethod
    static method getSlideSFX takes thistype this, real x, real y returns nothing
        local string s=""
        
        if IsUnitType(.u,UNIT_TYPE_FLYING)==true and GetUnitFlyHeight(.u)>0.0 then
            // air
            if .gt!=3 then
                set .gt=3
                set s=SFX[3]
            endif
        else
            // land                                                stairs don't have any pathing...
            if IsTerrainPathable(x,y,PATHING_TYPE_FLOATABILITY) or not IsTerrainPathable(x,y,PATHING_TYPE_ANY) then
                if .gt!=1 then
                    set .gt=1
                    set s=SFX[1]
                endif
            // sea
            elseif not IsTerrainPathable(x,y,PATHING_TYPE_WALKABILITY) then
                if .gt!=2 then
                    set .gt=2
                    set s=SFX[2]                    
                endif
            endif
        endif
        // new sfx to be added
        if s!="" then
            // old sfx removal
            if .sfx!=null then
                call DestroyEffect(.sfx)
            endif
            // new sfx creation
            set .sfx=AddSpecialEffectTarget(s,.u,"origin")
        endif
    endmethod    
    method movement takes nothing returns nothing
        local real d  
        local real fh
        
        // update positioning
        set fh=GetUnitFlyHeight(.u)
        set .xP=GetUnitX(.u)+.xV
        set .yP=GetUnitY(.u)+.yV
        // allow movement
        static if MOVE then
            call SetUnitX(.u,.xP)
            call SetUnitY(.u,.yP)  
        else
        // don't allow movement
            call SetUnitPosition(.u,.xP,.yP)
        endif
            
        // various updateable values
        set .time=.time+TIMEOUT
        set d=SquareRoot(.xV*.xV+.yV*.yV) // distance traveled
        set .dist=.dist+d
            
        // update velocity
        if fh<=0. then
            set .xV=.xV*FRICT
            set .yV=.yV*FRICT 
        endif
            
        // update slide SFX
        call thistype.getSlideSFX(this,.xP,.yP)
            
        // tree destruction
        // kudos to Bribe for this neat trick
        static if TREES then
            if fh<=200. then
                call SetUnitAbilityLevel(DUM,ABIL,LVL)
                call IssuePointOrder(DUM,"flamestrike",.xP,.yP)
            endif
        endif
        
        // check for slide end based on velocity
        if d<=RSTOP then  
            set .remove=true
        endif
    endmethod
    
    implement slideStruct    
    
    //== FUNCTIONALITY METHODS ==\\
    // retrieve the units current slide information
    static method unitGetSlide takes unit u returns thistype
        if IsUnitInGroup(u,SLIDING) then
            return LoadInteger(HT,GetHandleId(u),0)
        else
            debug call BJDebugMsg(SCOPE_PREFIX+" unitGetSlide Error: "+GetUnitName(u)+" is not sliding.")
            return 0
        endif
    endmethod
    // is the unit currently sliding?
    static method isUnitSliding takes unit u returns boolean
        return IsUnitInGroup(u,SLIDING)
    endmethod
    // stop the unit from sliding
    static method unitStopSlide takes unit u returns boolean
        local thistype this
        if IsUnitInGroup(u,SLIDING) then
            set this=LoadInteger(HT,GetHandleId(u),0)
            set .remove=true
            return true
        else
            debug call BJDebugMsg(SCOPE_PREFIX+" unitStopSlide Error: "+GetUnitName(u)+" is not sliding.")
            return false
        endif
    endmethod
    // set custom slide velocity
    static method setVelocity takes thistype this, real x, real y returns nothing
        set .xV=x*TIMEOUT
        set .yV=y*TIMEOUT
    endmethod
    // add to current slide velocity
    static method addVelocity takes thistype this, real x, real y returns nothing
        set .xV=x*TIMEOUT+.xV
        set .yV=y*TIMEOUT+.yV
    endmethod
    
    //== INITIALIZATION ==\\
    static method onInit takes nothing returns nothing
        // this is the dummy unit used for tree destruction
        set DUM=CreateUnit(Player(15),DUMMY,0.,0.,0.)
        call UnitAddAbility(DUM,ABIL)
        call UnitAddAbility(DUM,'Aloc')
        call SetUnitPathing(DUM,false)
        call SetUnitInvulnerable(DUM,true)       
        call PauseUnit(DUM,false)
        
        // stores our user defined SFX strings
        call setupSlideSFX()
        
        // preload effects
        call Preload(SFX[1])
        call Preload(SFX[2])
        call Preload(SFX[3])
        call Preload(SFX[4])
    endmethod
endstruct

module slideStruct
    static method iterate takes nothing returns nothing
        local slide s=slide(0).next
        local unit t
       
        loop
            exitwhen s==0
            
            // check for slide end
            if not UnitAlive(s.u) or s.remove then 
                static if thistype.onRest.exists then
                    call thistype.onRest(s)
                endif
                // destroy the slide struct
                call s.destroy()
            else        
                // update positioning
                call s.movement()
                // cliff detection
                if s.findCliffCollision() then
                    static if thistype.onCliff.exists then
                        call thistype.onCliff(s)
                    endif
                endif
                // unit collision detection
                static if COLL then
                    call GroupEnumUnitsInRange(ENUM,s.xP,s.yP,RADIUS,null)
                    call GroupRemoveUnit(ENUM,s.u)
                    loop
                        set t=FirstOfGroup(ENUM)
                        exitwhen t==null
                        call GroupRemoveUnit(ENUM,t)
                        
                        if not IsUnitInGroup(t,s.g) and UnitAlive(t) then 
                            if s.getDeflection(t) then
                                // unit t was collided with
                                call GroupAddUnit(s.g,t)
                                static if thistype.onCollision.exists then
                                    call thistype.onCollision(s,t)
                                endif
                            endif
                        endif
                    endloop
                endif
                
                // do other neat stuffs???
            endif
            
            static if thistype.onLoop.exists then
                call thistype.onLoop(s)
            endif
            
            set s=s.next
        endloop
        
        // struct list empty
        if FirstOfGroup(SLIDING)==null then
            call PauseTimer(T)
        endif
    endmethod
    
    //== CREATION ==\\
    static method createSlide takes unit u, real x, real y returns slide
        local slide s
        
        // struct list empty
        if FirstOfGroup(SLIDING)==null then
            call TimerStart(T,TIMEOUT,true,function thistype.iterate)
        endif
        
        if IsUnitInGroup(u,SLIDING) then
            // had previously been sliding
            set s=LoadInteger(HT,GetHandleId(u),0)
        else
            // hadn't been previously sliding
            set s=slide.create()
            // we can access our slide struct from the sliding unit
            call SaveInteger(HT,GetHandleId(u),0,s)
            
            set s.u=u    
            // removes pathing glitches
            call SetUnitPathing(u,false)
            set s.owner=GetOwningPlayer(u)     
            // so we know this unit is sliding
            call GroupAddUnit(SLIDING,u)
            // establish our initial slide sfx
            call slide.getSlideSFX(s,x,y)
            // collision group
            set s.g=CreateGroup()
            
            // update linked list
            set slide(0).next.prev=s
            set s.next=slide(0).next
            set slide(0).next=s
            set s.prev=slide(0)
        endif
        
        // update position and velocities
        set s.xV=s.xV+x*TIMEOUT
        set s.yV=s.yV+y*TIMEOUT
        set s.xP=GetUnitX(u)
        set s.yP=GetUnitY(u)        
        set s.ang=Atan2((s.yP+s.yV)-s.yP,(s.xP+s.xV)-s.xP)
        
        return s
    endmethod
endmodule

endlibrary
JASS:
struct TESTER
    private static timer t=CreateTimer()
    private static boolean b=false
    
    static method onRest takes slide s returns nothing
        if GetUnitTypeId(s.u)=='hfoo' then
            call RemoveUnit(s.u)
        endif
    endmethod
    
    implement slideStruct
    
    private static method onExpire takes nothing returns nothing
        local real r=GetRandomReal(-500.,500)
        local real face=GetRandomReal(0.,360.)
        local unit dum=CreateUnit(Player(0),'hfoo',GetStartLocationX(0)+r,GetStartLocationY(0)+r,face)
        
        set face=face*bj_DEGTORAD
        set r=GetRandomReal(-750.,750.)
        call thistype.createSlide(dum,r*Cos(face),r*Sin(face))
        
        set dum=null
    endmethod
    
    private static method onEsc takes nothing returns nothing
        if b then
            call PauseTimer(t)
        else
            call TimerStart(t,0.25,true,function thistype.onExpire)
        endif
        set b=not b
    endmethod
    
    private static method onInit takes nothing returns nothing
        local trigger trig=CreateTrigger()
        call TriggerRegisterPlayerEvent(trig,Player(0),EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(trig,function thistype.onEsc)
        call RemoveUnit(CreateUnit(Player(15),'hfoo',0.,0.,0.))
    endmethod    
endstruct

  • v1.c - Removed GroupUtils and AutoIndex requirements, now uses a Hashtable for data storage to units
  • v1.b - Updated to use modules (removes bloat code), added collision effects
  • v1.a - Rewrote from scratch, changed system requirements, updated cliff detection algorithm, now uses straight vector movement calcs., changed tree destruction method (Thnx Bribe), now can only collide once/unit/slidevent, allows momentum conservation on collision, still uses interfaces :(
  • v1. - Initial Release
 

Attachments

  • AirSlide.mdx
    9 KB · Views: 162
  • LandSlide.mdx
    1.5 KB · Views: 149
  • SeaSlide.mdx
    2 KB · Views: 135
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
So that we can keep everyone updated on both forums ;D. Changed a little bit of the post to make you read it again ; p (velocity based on unit facing rather than set unit.x.velocity).

Good idea, but this can undergo a lot of optimization. Whenever you deal with periodic stuff, you need to optimize the hell out of it ; ).

TimerQueue for rehit
This should have like 0 groups, be better on lists.

A unit's velocity should just be set (set unit.x.velocity = -5; set unit.y.velocity = -3). Actually, velocity should just be based on facing, so it'd just be set unit.velocity = -5 or w/e.

This way people can control exactly how they want to do it. You should split this into a myriad of resources... all of it in one resource is just a huge jumbled mess. The most basic should be the setting unit velocity. From there, users can make it so that a unit's velocity gets set when they go on a certain terrain or when a knock back occurs or w/e.

From there, you can go up a level with things like rehit and all of these other options you have. Having them all together is just a terrible idea. It just limits the usability of the resource and makes people have to write identical code to do nearly identical things >.>. This is why modularity is just so important.

It looks like you put a lot of work into this lib, but you should just scrap it and start over.

One interesting thing is that if you just have a general sliding lib, you could run like anything off of it from projectiles to sliding on terrain in maze maps to knockback spells to w/e.
 
I suggest adding list of the wrappers (maybe at the top of the code or on a separate trigger), and add spaces in between to make them easier to read...

so that user's won't really need to look at the code, and to prevent accidental errors, especially since those wrappers are aimed at GUI users...

something like this

JASS:
/*
==Wrapper Functions==

Create a new instance for a unit, deceleration=0 to use the DECEL global
->function UnitStartSlide takes unit slider, real angle, real velocity, real deceleration returns slide

Retrieve the units current slide information
->function UnitGetSlide takes unit slider returns slide

Is the unit currently sliding?
->function IsUnitSliding takes unit slider returns boolean

Stop the unit from sliding immediately
->function UnitStopSlide takes unit slider returns boolean

Incase you need to set a custom slide effect mid slide
Use "" to restore automatic sfx updates
->function UnitSetSlideEffect takes unit slider, string sfx returns boolean

Kinematics

Slide a unit for a specific duration, given the initial velocity
->function UnitSlideTimed takes unit slider, real angle, real velocity, real time returns slide

Slide a unit over a specific distance, given the initial velocity
->function UnitSlideDistance takes unit slider, real angle, real velocity, real distance returns slide

Remove a unit from sliders collision group instantly
->function RemoveTargetFromSlide takes slide d, unit target returns boolean

Remove a unit from sliders collision group after REHITTIME duration
->function RemoveTargetFromSlideTimed takes slide d, unit target returns boolean
*/

Just one more thing, not sure if some of those returns are really needed...
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Using Static-if's will actually decrease map file size because it crops out the unwanted blocks during compilation instead of leaving everything in the war3map.j file.

Hmm, last time i checked the code which is not used, because of a false evaluation of a static if is kept as comment lines instead of code lines. (for jasshelper at least, it won't probably the case with the luck parser :p )

EDIT : Not sure and not tested but i think i've read Vexorian said that Zinc doesn't keep comments (maybe in Zinc documentation)
 
This system is actually VERY useful ^^
I have this hero call "Cosmic Netherbeast" (coming soon in TDA v1.08)
He's also known as "The King of All Knockbacks"
His spells kept bugging (units moving like crazy when knocked twice with different angles)
This system was the solution!
Thanks man!
Btw, can I edit this system a bit to fit my map?
There are a few things that I want to change to increase performance and reduce
handle use (Nestharus taught me how to use linked lists ^^)
 
an update in the not to distant future will probably include such a change, as I have proven to myself that they are indeed faster than stacks

if you want to wait, that is - in the mean time, the alternative is inexplicably slower...

Nah, I'll just fix my current knockback system to completely Overwrite knockbacks
for now, and if an update ever comes up, I'll implement this system for enhanced
awesomeness ^^
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Feedback:

  • GetHandleId(x) - 0x100000 for use in arrays is potentially hazardous. You would need fewer than 8191 handles in the map in order for this to not return a bad value. To compensate, Table indexing or Unit Indexing would be viable alternatives.
  • camelCased API is JASS convention. I can understand not camelcasing something obvious like "unitid", but some of these names are pretty long and could be clearer to read.
  • .execute() is slower than .evaluate(), is there a reason why you set it up like that?
  • I advise a default 0.03125 timeout instead of a 0.5, 1) as it holds the best ratio of realism/efficiency in-game
  • You could also mention that this system, by default, needs some custom imported artwork to work properly.
 
Level 12
Joined
Mar 28, 2005
Messages
160
GetHandleId(x) - 0x100000 for use in arrays is potentially hazardous. You would need fewer than 8191 handles in the map in order for this to not return a bad value. To compensate, Table indexing or Unit Indexing would be viable alternatives.

out of principal I refuse to limit this system by employing one of the many indexers available, thereby requiring the user to implement one of them - and which one would I use - they all work just fine and dandy, which would each user prefer? why not just use extended arrays and remove the problem?

camelCased API is JASS convention. I can understand not camelcasing something obvious like "unitid", but some of these names are pretty long and could be clearer to read.

never heard of it until now, will update

.execute() is slower than .evaluate(), is there a reason why you set it up like that?

only because thats how I had seen it done - if .evaluate works just the same, then I can just as easily switch it over

I advise a default 0.03125 timeout instead of a 0.5, 1) as it holds the best ratio of realism/efficiency in-game

.05 is more efficient and plenty for slide type spells with short movements - I don't see that being a big deal?

You could also mention that this system, by default, needs some custom imported artwork to work properly.

while this is not entirely true, its worth mentioning the slide effects add to the whole package for aesthetics sake
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
GetLocationZ is asynchronous. You can use it to set a dummy unit's pitch, flyheight, scale or vertex color... but nothing that could affect its x/y or otherwise do something that you couldn't safely use in GetLocalPlayer blocks.

Loooooong time ago (more than 2-3 years i think) i tested to display reals values of GetLocationZ in some points with a friend: i had the lowest graphic options and him the highest.

Periodically it displayed the values of random point of the map (one earthquake spell was permanent, and was applied on the whole map)

Guess what, we had the same results, but we used both the same OS : windows xp (can't ensure it would work with mac or whatever else)

PS : I used R2SW with a decent precision, like 6 numbers under the digit, and we took screenshots of the text displayed for both of us.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Mac users especially have a problem with the return value of GetLocationZ. In fact, Blizzard themselves even mentions it in common.j "warning this is not guaranteed synchronous".

But that's not like they're updating correctly the common.j though, hell the last time i checked the common.j, it had still several commented functions (not yet implented), and don't tell me they plan to add them ...

So is it an urban legend or do you really know mac users which have experimented a such thing ?
 
Level 10
Joined
Jul 12, 2009
Messages
318
So is it an urban legend or do you really know mac users which have experimented a such thing ?
The map didn't use GetLocationZ (so I cannot vouch for that) but when I played WC3 on a PowerPC Mac on BNet with a friend using Windows XP, I had vastly differently deformed terrain than he did (ie. I had a huge spire sticking from the ground, he did not) from a spell that used terrain deformations.
[edit] This was an older version. I have not played on a PPC Mac for a while.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Full Review:

This needs to have better identity for its methods. "removetargetfromslidetimed" is just unreadable. Please use camelCasing to make each word stand out. "removeTargetFromSlideTimed" is much more readable.

You should declare the UnitAlive method in your own script. JassHelper deletes duplicate native declarations.

You only need "== true" with IsUnitType if it is a single return from a boolexpr, since it is not, please delete the "== true" as it is superfluous.

CONSTANTS_CASED_LIKE_THIS NOTLIKETHIS so that each word stands out.

GroupEnumUnitsInRange with a null filter + FirstOfGroup loop has been proven to be at least 2x faster. Since this is a periodic system it needs as much efficiency as can be squeezed. FirstOfGroup loops are much easier to manage though, so this shouldn't be an issue.

IsTerrainWalkable is not a good check as it can put units with larger than 16 collision in very bad situations. In Knockback 2D I used a custom Inferno ability to check collision for 4 different collision sizes, which is definitely enough and certainly better than what you have here.

Instead of using a filter for TREEBOOL just use the code argument. You don't need the filter, and this way you save a handle and maybe even some efficiency who knows.

You should use .evaluate() instead of .execute() for speed, and even then it's sucky on performance because function interfaces never win any benchmarks.
 
Level 12
Joined
Mar 28, 2005
Messages
160
been working on a complete overall of this

the script is radically different, and should address all of the aforementioned short comings

here is a preview

JASS:
library SlideSystem needs GroupUtils, AutoIndex

//== CONFIGURATION ==\\
globals    
    // bounce off terrain?
    private constant    boolean         BOUNCE  = true
    // bounce off other units?
    private constant    boolean         COLL    = true
    // allow movement during slides?
    private constant    boolean         MOVE    = true
    // destroy trees in slide path?
    private constant    boolean         TREES   = true
    // tree destruction ability rawcode
    private constant    integer         ABIL    = 'tree'
    // rawcode of your map's caster dummy
    private constant    integer         DUMMY   = 'dumy'
    // conserved slide velocity/TIMEOUT
    private constant    real            FRICT   = .96
    // momentum conserved upon collision, what's loss is transfered to the colliding unit, if it exists
    private constant    real            LOSS    = .75
    // collision radius
    private constant    real            RADIUS  = 128.
    // minimum slide velocity
    private constant    real            STOP    = 3.0
    // periodic timer interval
    private constant    real            TIMEOUT = .03125
    
    // we will configure this global below
    private             string array    SFX
endglobals
private function setupSlideSFX takes nothing returns nothing
    // the system assumes the array placements are unchanged
    // i speculate as to terrain types using known means; this isn't fool proof
    // if you are interested in snow/ice/lava slide sfx, let me know
    
    // ground slide sfx
    set SFX[1]="war3mapImported\\LandSlide.mdx" 
    // water slide sfx
    set SFX[2]="war3mapImported\\SeaSlide.mdx"
    // air slide sfx
    set SFX[3]="war3mapImported\\AirSlide.mdx" 
    // slide collision sfx
    set SFX[4]="Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl" 
endfunction

//== NO TOUCHING PAST THIS POINT ==\\
native UnitAlive takes unit u returns boolean
globals
    public              group           G       = CreateGroup()
    private             group           ENUM    = CreateGroup()
    private constant    integer         LVL     = IMaxBJ(4,R2I(RADIUS/32.))   
    public              integer array   SLIDES
    private             location        LOC     = Location(0.,0.)
    private             location        LOC2    = Location(0.,0.)
    private             real            RSTOP   = STOP*TIMEOUT
    private             timer           T       = CreateTimer()
    private             unit            DUM     = null
endglobals

private interface face        
    method onCreate takes slide s returns nothing defaults nothing
    method onCliff takes slide s returns nothing defaults nothing
    method onCollision takes slide s1, unit target returns nothing defaults nothing
    method onEnd takes slide s returns nothing defaults nothing
    method onLoop takes slide s returns nothing defaults nothing
endinterface
struct slide extends face
    private thistype next
    private thistype prev
    
    readonly    unit    u
    readonly    player  owner
    readonly    real    xP
    readonly    real    yP
    public      real    xV
    public      real    yV
    readonly    real    ang
    readonly    real    dist=0.    // total distance slid
    readonly    real    time=0.    // total duration slid
    private     effect  sfx=null
    readonly    group   g
    private     integer gt=0    // ground type (numbers correspond to SFX type)
    private     boolean remove=false
    
    private method destroy takes nothing returns nothing
        // execute onEnd method
        if .onEnd.exists then
            call .onEnd.evaluate(this)
        endif
        
        // clean up
        call GroupRemoveUnit(G,.u)
        call DestroyEffect(.sfx)
        call SetUnitPathing(.u,true)
        call ReleaseGroup(.g)
        call .deallocate()
    endmethod
    
    private static method findCliffCollision takes thistype this returns nothing
        local real array r
                
        call MoveLocation(LOC,.xP,.yP)
        // height at or above which the slider will collide with the given terrain loc
        set r[1]=GetLocationZ(LOC)+GetUnitFlyHeight(.u)-(RADIUS/2.)
        set r[2]=.ang-.7854 // bj_PI/4.
        set r[3]=.ang+.7854
        // loop in a quarter circle
        loop
            exitwhen r[2]>r[3] // stop after the quarter circle
            set r[4]=.xP+(RADIUS/2.)*Cos(r[2])
            set r[5]=.yP+(RADIUS/2.)*Sin(r[2])
            call MoveLocation(LOC,r[4],r[5])
            set r[6]=GetLocationZ(LOC)
            // collision found, and better than the last
            if r[6]>=r[1] and r[6]>r[9] then
                // update dynamic array members
                set r[7]=r[4] // x
                set r[8]=r[5] // y
                set r[9]=r[6] // z
            endif
            // continue to loop, bj_PI/16.
            set r[2]=r[2]+.1963 
        endloop
        call MoveLocation(LOC,.xP,.yP)
        // we have a cliff collision
        if r[9]>0. and r[9]-GetLocationZ(LOC)>=15. then
            // create collision effect where we strike
            call DestroyEffect(AddSpecialEffect(SFX[4],r[7],r[8]))
            // update velocity vectors
            static if BOUNCE then
                set .ang=2.*Atan2(r[8]-.yP,r[7]-.xP)+bj_PI-.ang
                set r[1]=SquareRoot(.xV*.xV+.yV*.yV)*LOSS
                set .xV=r[1]*Cos(.ang)
                set .yV=r[1]*Sin(.ang)
            else
                set .xV=0.
                set .yV=0.
            endif
            
            // execute onCliff method
            if .onCliff.exists then
                call .onCliff.evaluate(this)
            endif
        endif
    endmethod
    private static method getSlideSFX takes thistype this, real x, real y returns nothing
        local string s=""
        
        if IsUnitType(.u,UNIT_TYPE_FLYING)==true and GetUnitFlyHeight(.u)>0.0 then
            // air
            if .gt!=3 then
                set .gt=3
                set s=SFX[3]
            endif
        else
            // land                                                stairs don't have any pathing...
            if IsTerrainPathable(x,y,PATHING_TYPE_FLOATABILITY) or not IsTerrainPathable(x,y,PATHING_TYPE_ANY) then
                if .gt!=1 then
                    set .gt=1
                    set s=SFX[1]
                endif
            // sea
            elseif not IsTerrainPathable(x,y,PATHING_TYPE_WALKABILITY) then
                if .gt!=2 then
                    set .gt=2
                    set s=SFX[2]                    
                endif
            endif
        endif
        // new sfx to be added
        if s!="" then
            // old sfx removal
            if .sfx!=null then
                call DestroyEffect(.sfx)
            endif
            // new sfx creation
            set .sfx=AddSpecialEffectTarget(s,.u,"origin")
        endif
    endmethod
    
    private static method movement takes nothing returns nothing
        local thistype this=thistype(0).next
        local integer id
        local thistype s
        local unit t
        local real a
        local real d  
        local real fh
        local real l
        local real r
        local real x
        local real dx
        local real y
        local real dy
        local real z
                
        loop
            exitwhen this==0
            set id=GetUnitId(.u)
            
            // update positioning
            set fh=GetUnitFlyHeight(.u)
            set .xP=GetUnitX(.u)+.xV
            set .yP=GetUnitY(.u)+.yV
            // allow movement
            static if MOVE then
                call SetUnitX(.u,.xP)
                call SetUnitY(.u,.yP)  
            else
            // don't allow movement
                call SetUnitPosition(.u,.xP,.yP)
            endif
            
            // various updateable values
            set .time=.time+TIMEOUT
            set d=SquareRoot(.xV*.xV+.yV*.yV) // distance traveled
            set .dist=.dist+d
            
            // update velocity
            if fh<=0. then
                set .xV=.xV*FRICT
                set .yV=.yV*FRICT 
            endif
            
            // update slide SFX
            call thistype.getSlideSFX(this,.xP,.yP)
            
            // tree destruction
            // kudos to Bribe for this neat trick
            static if TREES then
                if fh<=200. then
                    call SetUnitAbilityLevel(DUM,ABIL,LVL)
                    call IssuePointOrder(DUM,"flamestrike",.xP,.yP)
                endif
            endif
            
            // cliff detection
            call thistype.findCliffCollision(this)
            
            // unit collision detection
            static if COLL then
                call MoveLocation(LOC,.xP,.yP)
                set z=GetLocationZ(LOC)+fh // slider zP
                // grab units in RADIUS
                call GroupEnumUnitsInRange(ENUM,.xP,.yP,RADIUS,null)
                call GroupRemoveUnit(ENUM,.u)
                loop
                    set t=FirstOfGroup(ENUM)
                    exitwhen t==null
                    call GroupRemoveUnit(ENUM,t)
                    
                    set x=GetUnitX(t)
                    set y=GetUnitY(t)
                    call MoveLocation(LOC2,x,y)
                    // collision detected
                    if not IsUnitInGroup(t,.g) and UnitAlive(t) and RAbsBJ(z-(GetLocationZ(LOC2)+GetUnitFlyHeight(t)))<=RADIUS*2. then
                        // currently we can only collide once/target
                        call GroupAddUnit(.g,t)
                        
                        // account for momentum loss
                        set l=SquareRoot(.xV*.xV+.yV*.yV)
                        set r=l*LOSS
                        set l=(l-r)/TIMEOUT
                        
                        //calculate angular relationship
                        set dx=x-.xP
                        set dy=y-.yP
                        set a=Atan2(dy,dx)
                        
                        // slide target
                        if IsUnitType(t,UNIT_TYPE_STRUCTURE)==false then
                            set s=slide.create(t,l*Cos(a),l*Sin(a))
                            call GroupAddUnit(s.g,.u)
                        endif
                        
                        // update vector components
                        set .ang=2.*a+bj_PI-.ang
                        set .xV=r*Cos(.ang)
                        set .yV=r*Sin(.ang)
                        
                        // create collision effect between two units
                        set r=SquareRoot(dx*dx+dy*dy)/2.
                        call DestroyEffect(AddSpecialEffect(SFX[4],.xP+r*Cos(a),.yP+r*Sin(a)))
                        
                        // execute onCollision method
                        if .onCollision.exists then
                            call .onCollision.evaluate(this,t)
                        endif
                    endif
                endloop
            endif
            
            // execute onLoop method
            if .onLoop.exists then
                call .onLoop.evaluate(this)
            endif
            
            // check for slide end
            if not UnitAlive(.u) or d<=RSTOP or .remove then  
                set .prev.next=.next
                set .next.prev=.prev
                call .destroy()
            endif
            
            set this=this.next
        endloop
        
        // struct list empty
        if FirstOfGroup(G)==null then
            call PauseTimer(T)
        endif
    endmethod
    
    //== CREATION ==\\
    static method create takes unit u, real x, real y returns thistype
        local thistype this
        
        // struct list empty
        if FirstOfGroup(G)==null then
            call TimerStart(T,TIMEOUT,true,function thistype.movement)
        endif
        
        if IsUnitInGroup(u,G) then
            // had previously been sliding
            set this=SLIDES[GetUnitId(u)]
        else
            // hadn't been previously sliding
            set this=thistype.allocate()
            // we can access our slide struct from the sliding unit
            set SLIDES[GetUnitId(u)]=this
            
            set .u=u    
            // removes pathing glitches
            call SetUnitPathing(.u,false)
            set .owner=GetOwningPlayer(u)
            // so we know this unit is sliding
            call GroupAddUnit(G,u)
            // establish our initial slide sfx
            call thistype.getSlideSFX(this,x,y)
            // collision group
            set .g=NewGroup()
            
            // update linked list
            set thistype(0).next.prev=this
            set .next=thistype(0).next
            set thistype(0).next=this
            set .prev=thistype(0)
        endif
        
        // update position and velocities
        set .xV=.xV+x*TIMEOUT
        set .yV=.yV+y*TIMEOUT
        set .xP=GetUnitX(u)
        set .yP=GetUnitY(u)        
        set .ang=Atan2((.yP+.yV)-.yP,(.xP+.xV)-.xP)
        
        // execute onCreate method
        if .onCreate.exists then
            call .onCreate.evaluate(this)
        endif
        
        return this
    endmethod
    
    //== FUNCTIONALITY METHODS ==\\
    // retrieve the units current slide information
    static method unitGetSlide takes unit u returns thistype
        if IsUnitInGroup(u,G) then
            return SLIDES[GetUnitId(u)]
        else
            debug call BJDebugMsg(SCOPE_PREFIX+" unitGetSlide Error: "+GetUnitName(u)+" is not sliding.")
            return 0
        endif
    endmethod
    // is the unit currently sliding?
    static method isUnitSliding takes unit u returns boolean
        return IsUnitInGroup(u,G)
    endmethod
    // stop the unit from sliding immediately
    static method unitStopSlide takes unit u returns boolean
        local thistype this
        if IsUnitInGroup(u,G) then
            set this=SLIDES[GetUnitId(u)]
            set .remove=true
            return true
        else
            debug call BJDebugMsg(SCOPE_PREFIX+" unitStopSlide Error: "+GetUnitName(u)+" is not sliding.")
            return false
        endif
    endmethod
    // set custom slide velocity
    static method setVelocity takes thistype this, real x, real y returns nothing
        set .xV=x*TIMEOUT
        set .yV=y*TIMEOUT
    endmethod
    // add to current slide velocity
    static method addVelocity takes thistype this, real x, real y returns nothing
        set .xV=x*TIMEOUT+.xV
        set .yV=y*TIMEOUT+.yV
    endmethod
    
    //== INITIALIZATION ==\\
    static method onInit takes nothing returns nothing
        // this is the dummy unit used for tree destruction
        set DUM=CreateUnit(Player(15),DUMMY,0.,0.,0.)
        call UnitAddAbility(DUM,ABIL)
        call UnitAddAbility(DUM,'Aloc')
        call SetUnitPathing(DUM,false)
        call SetUnitInvulnerable(DUM,true)       
        call PauseUnit(DUM,false)
        
        // stores our user defined SFX strings
        call setupSlideSFX()
        
        // preload effects
        call Preload(SFX[1])
        call Preload(SFX[2])
        call Preload(SFX[3])
        call Preload(SFX[4])
    endmethod
endstruct

endlibrary
its fairly close to completion

does not quite have the functionality the last did, but I think its actually better for it

if you have a minute, take a look, and let me know if there are any items I need to address before re-submitting
 
Level 6
Joined
Jun 20, 2011
Messages
249
So a trigger evaluation is run everytime a slide is created, steps on a cliff, hits an unit, ends and every period? Not to mention that this might increase your map size by 1mb just by adding it because of the code duplication it causes. It may lower your fps by 30 when dealing with 20 instances at the time.
This looks like a very good system to be submitted at the helper
 
Level 12
Joined
Mar 28, 2005
Messages
160
open to constructive criticism

but paltry insults simply for the sake of it doesn't help

i'd gladly switch to modules if I knew how I could make it work

btw this runs at over 30 instances at max fps (the only dips I see are due to the slide sfx), not the code - with no sfx I can get to 100 easy
 
Last edited:
Level 7
Joined
Oct 11, 2008
Messages
304
@emjlr3

You're still using interface.

To main thing for 'module' (ignoring the fact of initializing) is to use if someMethod.exists then.

To abuse this, you should implement the module (which use the exist thing) in the struct that you'll use the missile.

Example:

JASS:
module SlideModule
    if this.onUnit.exists then
        call this.onUnit()
    endif
endmodule

struct test
    /********************************************************************
     onUnit exist, so it will be called like any other function at Jass */
    private method onUnit takes nothing returns nothing
    endmethod

    implement SlideModule
endstruct
 
Level 12
Joined
Mar 28, 2005
Messages
160
hmm, well, update again

seems to work as needed, and now uses modules to remove bloat code, thoughts?

JASS:
library SlideSystem needs GroupUtils, AutoIndex

//== CONFIGURATION ==\\
globals    
    // bounce off terrain?
    private constant    boolean         BOUNCE  = true
    // collide with other units?
    private constant    boolean         COLL    = true
    // allow movement during slides?
    private constant    boolean         MOVE    = true
    // destroy trees in slide path?
    private constant    boolean         TREES   = true
    // tree destruction ability rawcode
    private constant    integer         ABIL    = 'tree'
    // rawcode of your map's caster dummy
    private constant    integer         DUMMY   = 'dumy'
    // conserved slide velocity/TIMEOUT
    private constant    real            FRICT   = .96
    // momentum conserved upon collision, what's loss is transfered to the colliding unit, if it exists
    private constant    real            LOSS    = .75
    // collision radius
    private constant    real            RADIUS  = 128.
    // minimum slide velocity
    private constant    real            STOP    = 3.0
    // periodic timer interval
    private constant    real            TIMEOUT = .031250000
    
    // we will configure this global below
    private             string array    SFX
endglobals
private function setupSlideSFX takes nothing returns nothing
    // the system assumes the array placements are unchanged
    // i speculate as to terrain types using known means; this isn't fool proof
    // if you are interested in snow/ice/lava slide sfx, let me know
    
    // ground slide sfx
    set SFX[1]="war3mapImported\\LandSlide.mdx" 
    // water slide sfx
    set SFX[2]="war3mapImported\\SeaSlide.mdx"
    // air slide sfx
    set SFX[3]="war3mapImported\\AirSlide.mdx" 
    // slide collision sfx
    set SFX[4]="Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl"
endfunction

//== NO TOUCHING PAST THIS POINT ==\\
native UnitAlive takes unit u returns boolean
globals
    public              group           SLIDING = CreateGroup()
    private             group           ENUM    = CreateGroup()
    private constant    integer         LVL     = IMaxBJ(4,R2I(RADIUS/32.))   
    public              integer array   SLIDES
    private             location        LOC     = Location(0.,0.)
    private             location        LOC2    = Location(0.,0.)
    private             real            RSTOP   = STOP*TIMEOUT
    private             timer           T       = CreateTimer()
    private             unit            DUM     = null
endglobals

struct slide
    thistype next
    thistype prev
    
    unit    u
    player  owner
    real    xP
    real    yP
    real    xV
    real    yV
    real    ang
    real    dist=0.         // total distance slid
    real    time=0.         // total duration slid
    effect  sfx
    group   g
    integer gt=0            // ground type (numbers correspond to SFX type)
    boolean remove=false
    
    method destroy takes nothing returns nothing
        // update linked list
        set .prev.next=.next
        set .next.prev=.prev
        // clean up
        call GroupRemoveUnit(SLIDING,.u)
        call DestroyEffect(.sfx)
        call SetUnitPathing(.u,true)
        call ReleaseGroup(.g)
        call .deallocate()
    endmethod
    
    method getDeflection takes unit t returns boolean
        local thistype s
        local real a
        local real d  
        local real l
        local real r
        local real x
        local real dx
        local real y
        local real dy
        local real z
        
        call MoveLocation(LOC,.xP,.yP)
        set z=GetLocationZ(LOC)+GetUnitFlyHeight(.u) // slider zP                    
        set x=GetUnitX(t)
        set y=GetUnitY(t)
        call MoveLocation(LOC2,x,y)
        // collision detected
        if RAbsBJ(z-(GetLocationZ(LOC2)+GetUnitFlyHeight(t)))<=RADIUS*2. then
            // currently we can only collide once/target
            call GroupAddUnit(.g,t)
                        
            // account for momentum loss
            set l=SquareRoot(.xV*.xV+.yV*.yV)
            set r=l*LOSS
            set l=(l-r)/TIMEOUT
                        
            //calculate angular relationship
            set dx=x-.xP
            set dy=y-.yP
            set a=Atan2(dy,dx)
            
            // update vector components
            set .ang=2.*a+bj_PI-.ang
            set .xV=r*Cos(.ang)
            set .yV=r*Sin(.ang)
            
            // create collision effect between two units
            set r=SquareRoot(dx*dx+dy*dy)/2.
            call DestroyEffect(AddSpecialEffect(SFX[4],.xP+r*Cos(a),.yP+r*Sin(a)))
            
            // slide target
            if IsUnitType(t,UNIT_TYPE_STRUCTURE)==false then
                set s=thistype.createSlide(t,l*Cos(a),l*Sin(a))
                call GroupAddUnit(s.g,.u)
                return true
            endif
        endif
        
        return false
    endmethod
    method findCliffCollision takes nothing returns boolean
        local real array r
                
        call MoveLocation(LOC,.xP,.yP)
        // height at or above which the slider will collide with the given terrain loc
        set r[1]=GetLocationZ(LOC)+GetUnitFlyHeight(.u)-(RADIUS/2.)
        set r[2]=.ang-.7854 // bj_PI/4.
        set r[3]=.ang+.7854
        // loop in a quarter circle
        loop
            exitwhen r[2]>r[3] // stop after the quarter circle
            set r[4]=.xP+(RADIUS/2.)*Cos(r[2])
            set r[5]=.yP+(RADIUS/2.)*Sin(r[2])
            call MoveLocation(LOC,r[4],r[5])
            set r[6]=GetLocationZ(LOC)
            // collision found, and better than the last
            if r[6]>=r[1] and r[6]>r[9] then
                // update dynamic array members
                set r[7]=r[4] // x
                set r[8]=r[5] // y
                set r[9]=r[6] // z
            endif
            // continue to loop, bj_PI/16.
            set r[2]=r[2]+.1963 
        endloop
        call MoveLocation(LOC,.xP,.yP)
        // we have a cliff collision
        if r[9]>0. and r[9]-GetLocationZ(LOC)>=15. then
            // create collision effect where we strike
            call DestroyEffect(AddSpecialEffect(SFX[4],r[7],r[8]))
            // update velocity vectors
            static if BOUNCE then
                set .ang=2.*Atan2(r[8]-.yP,r[7]-.xP)+bj_PI-.ang
                set r[1]=SquareRoot(.xV*.xV+.yV*.yV)*LOSS
                set .xV=r[1]*Cos(.ang)
                set .yV=r[1]*Sin(.ang)
            else
                set .xV=0.
                set .yV=0.
            endif
            
            return true
        endif
        
        return false
    endmethod
    static method getSlideSFX takes thistype this, real x, real y returns nothing
        local string s=""
        
        if IsUnitType(.u,UNIT_TYPE_FLYING)==true and GetUnitFlyHeight(.u)>0.0 then
            // air
            if .gt!=3 then
                set .gt=3
                set s=SFX[3]
            endif
        else
            // land                                                stairs don't have any pathing...
            if IsTerrainPathable(x,y,PATHING_TYPE_FLOATABILITY) or not IsTerrainPathable(x,y,PATHING_TYPE_ANY) then
                if .gt!=1 then
                    set .gt=1
                    set s=SFX[1]
                endif
            // sea
            elseif not IsTerrainPathable(x,y,PATHING_TYPE_WALKABILITY) then
                if .gt!=2 then
                    set .gt=2
                    set s=SFX[2]                    
                endif
            endif
        endif
        // new sfx to be added
        if s!="" then
            // old sfx removal
            if .sfx!=null then
                call DestroyEffect(.sfx)
            endif
            // new sfx creation
            set .sfx=AddSpecialEffectTarget(s,.u,"origin")
        endif
    endmethod    
    method movement takes nothing returns nothing
        local real d  
        local real fh
        
        // update positioning
        set fh=GetUnitFlyHeight(.u)
        set .xP=GetUnitX(.u)+.xV
        set .yP=GetUnitY(.u)+.yV
        // allow movement
        static if MOVE then
            call SetUnitX(.u,.xP)
            call SetUnitY(.u,.yP)  
        else
        // don't allow movement
            call SetUnitPosition(.u,.xP,.yP)
        endif
            
        // various updateable values
        set .time=.time+TIMEOUT
        set d=SquareRoot(.xV*.xV+.yV*.yV) // distance traveled
        set .dist=.dist+d
            
        // update velocity
        if fh<=0. then
            set .xV=.xV*FRICT
            set .yV=.yV*FRICT 
        endif
            
        // update slide SFX
        call thistype.getSlideSFX(this,.xP,.yP)
            
        // tree destruction
        // kudos to Bribe for this neat trick
        static if TREES then
            if fh<=200. then
                call SetUnitAbilityLevel(DUM,ABIL,LVL)
                call IssuePointOrder(DUM,"flamestrike",.xP,.yP)
            endif
        endif
        
        // check for slide end based on velocity
        if d<=RSTOP then  
            set .remove=true
        endif
    endmethod
    
    implement slideStruct    
    
    //== FUNCTIONALITY METHODS ==\\
    // retrieve the units current slide information
    static method unitGetSlide takes unit u returns thistype
        if IsUnitInGroup(u,SLIDING) then
            return SLIDES[GetUnitId(u)]
        else
            debug call BJDebugMsg(SCOPE_PREFIX+" unitGetSlide Error: "+GetUnitName(u)+" is not sliding.")
            return 0
        endif
    endmethod
    // is the unit currently sliding?
    static method isUnitSliding takes unit u returns boolean
        return IsUnitInGroup(u,SLIDING)
    endmethod
    // stop the unit from sliding
    static method unitStopSlide takes unit u returns boolean
        local thistype this
        if IsUnitInGroup(u,SLIDING) then
            set this=SLIDES[GetUnitId(u)]
            set .remove=true
            return true
        else
            debug call BJDebugMsg(SCOPE_PREFIX+" unitStopSlide Error: "+GetUnitName(u)+" is not sliding.")
            return false
        endif
    endmethod
    // set custom slide velocity
    static method setVelocity takes thistype this, real x, real y returns nothing
        set .xV=x*TIMEOUT
        set .yV=y*TIMEOUT
    endmethod
    // add to current slide velocity
    static method addVelocity takes thistype this, real x, real y returns nothing
        set .xV=x*TIMEOUT+.xV
        set .yV=y*TIMEOUT+.yV
    endmethod
    
    //== INITIALIZATION ==\\
    static method onInit takes nothing returns nothing
        // this is the dummy unit used for tree destruction
        set DUM=CreateUnit(Player(15),DUMMY,0.,0.,0.)
        call UnitAddAbility(DUM,ABIL)
        call UnitAddAbility(DUM,'Aloc')
        call SetUnitPathing(DUM,false)
        call SetUnitInvulnerable(DUM,true)       
        call PauseUnit(DUM,false)
        
        // stores our user defined SFX strings
        call setupSlideSFX()
        
        // preload effects
        call Preload(SFX[1])
        call Preload(SFX[2])
        call Preload(SFX[3])
        call Preload(SFX[4])
    endmethod
endstruct

module slideStruct
    static method iterate takes nothing returns nothing
        local slide s=slide(0).next
        local unit t
       
        loop
            exitwhen s==0
            
            // check for slide end
            if not UnitAlive(s.u) or s.remove then 
                static if thistype.onRest.exists then
                    call thistype.onRest(s)
                endif
                // destroy the slide struct
                call s.destroy()
            else        
                // update positioning
                call s.movement()
                // cliff detection
                if s.findCliffCollision() then
                    static if thistype.onCliff.exists then
                        call thistype.onCliff(s)
                    endif
                endif
                // unit collision detection
                static if COLL then
                    call GroupEnumUnitsInRange(ENUM,s.xP,s.yP,RADIUS,null)
                    call GroupRemoveUnit(ENUM,s.u)
                    loop
                        set t=FirstOfGroup(ENUM)
                        exitwhen t==null
                        call GroupRemoveUnit(ENUM,t)
                        
                        if not IsUnitInGroup(t,s.g) and UnitAlive(t) then 
                            if s.getDeflection(t) then
                                // unit t was collided with
                                call GroupAddUnit(s.g,t)
                                static if thistype.onCollision.exists then
                                    call thistype.onCollision(s,t)
                                endif
                            endif
                        endif
                    endloop
                endif
                
                // do other neat stuffs???
            endif
            
            static if thistype.onLoop.exists then
                call thistype.onLoop(s)
            endif
            
            set s=s.next
        endloop
        
        // struct list empty
        if FirstOfGroup(SLIDING)==null then
            call PauseTimer(T)
        endif
    endmethod
    
    //== CREATION ==\\
    static method createSlide takes unit u, real x, real y returns slide
        local slide s
        
        // struct list empty
        if FirstOfGroup(SLIDING)==null then
            call TimerStart(T,TIMEOUT,true,function thistype.iterate)
        endif
        
        if IsUnitInGroup(u,SLIDING) then
            // had previously been sliding
            set s=SLIDES[GetUnitId(u)]
        else
            // hadn't been previously sliding
            set s=slide.create()
            // we can access our slide struct from the sliding unit
            set SLIDES[GetUnitId(u)]=s
            
            set s.u=u    
            // removes pathing glitches
            call SetUnitPathing(u,false)
            set s.owner=GetOwningPlayer(u)     
            // so we know this unit is sliding
            call GroupAddUnit(SLIDING,u)
            // establish our initial slide sfx
            call slide.getSlideSFX(s,x,y)
            // collision group
            set s.g=NewGroup()
            
            // update linked list
            set slide(0).next.prev=s
            set s.next=slide(0).next
            set slide(0).next=s
            set s.prev=slide(0)
        endif
        
        // update position and velocities
        set s.xV=s.xV+x*TIMEOUT
        set s.yV=s.yV+y*TIMEOUT
        set s.xP=GetUnitX(u)
        set s.yP=GetUnitY(u)        
        set s.ang=Atan2((s.yP+s.yV)-s.yP,(s.xP+s.xV)-s.xP)
        
        return s
    endmethod
endmodule

endlibrary
and a demo

JASS:
struct TESTER
    private static timer t=CreateTimer()
    private static boolean b=false
    
    static method onRest takes slide s returns nothing
        if GetUnitTypeId(s.u)=='hfoo' then
            call RemoveUnit(s.u)
        endif
    endmethod
    
    implement slideStruct
    
    private static method onExpire takes nothing returns nothing
        local real r=GetRandomReal(-500.,500)
        local real face=GetRandomReal(0.,360.)
        local unit dum=CreateUnit(Player(0),'hfoo',GetStartLocationX(0)+r,GetStartLocationY(0)+r,face)
        
        set face=face*bj_DEGTORAD
        set r=GetRandomReal(-750.,750.)
        call thistype.createSlide(dum,r*Cos(face),r*Sin(face))
        
        set dum=null
    endmethod
    
    private static method onEsc takes nothing returns nothing
        if b then
            call PauseTimer(t)
        else
            call TimerStart(t,0.25,true,function thistype.onExpire)
        endif
        set b=not b
    endmethod
    
    private static method onInit takes nothing returns nothing
        local trigger trig=CreateTrigger()
        call TriggerRegisterPlayerEvent(trig,Player(0),EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(trig,function thistype.onEsc)
        call RemoveUnit(CreateUnit(Player(15),'hfoo',0.,0.,0.))
    endmethod    
endstruct
 
Level 6
Joined
Jun 20, 2011
Messages
249
Well it looks good but, I agree with Laiev, the requirements are a bit outdated.
Also no need for GroupUtils, it seems that, for some reason, Rising Dusk (and pretty much everyone else) forgot about the bj_lastCreatedGroup initialized group global which works just fine for temporal group iteration.
Besides that group recycling isn't really worth it.

You used my TESTER struct, Dirac is inspired
 
Level 12
Joined
Mar 28, 2005
Messages
160
is AutoIndex bad? - Bribe wanted an indexer, I picked one

hell I was wrong by not having one, and now I am wrong after picking one, rofl - whats the flavor of the week?

Besides that group recycling isn't really worth it.
when I used to make WC3 maps, it was all the rage

I didn't think bj_lastCreatedGroup was an array....

You used my TESTER struct, Dirac is inspired
it was elegant, and simple :)
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
is AutoIndex bad? - Bribe wanted an indexer, I picked one

Well, here are the reasons people at THW don't really use AutoIndex or AIDS
AutoIndex is the most cumbersome and slowest of all of the indexing systems, but it comes with a poorly coupled AutoEvent and nice modules unlike AIDS, which is why many people prefer it. It also allows units to be retrieved before going out of scope. It also has static ifs in the module to make code more optimal. It also hooks RemoveUnit in order to catch unit removal.

AIDS is an indexing system created by jesus4lyf that provides locks and is much faster than AutoIndex. However, AIDS can't retrieve units before they go out of scope and has textmacro API rather than a module one. It also has no static ifs in the module, making the code rather sloppy.

Both run on the undefend ability in order to work. Undefend runs twice, once for death and once for removal.

Now, there are also pros and cons to the unit indexer here. For example, it doesn't deindex paused units (meaning that you should unpause units before removing them, which is good practice anyways >.>). A more serious problem is that units with default autocast abilities will autocast before they are even indexed (this problem was reported to me, but I need to investigate it). Fixing this problem is relatively simple though ; ).


The unit indexer here takes advantage of the fact that undefend runs twice, one for death and once for removal, and that the undefend ability does not exist on the unit for the second run =). This is the main reason why it's so much faster : P.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Oh well, never thought about when you remove an unit while it was paused.
If it doesn't fire deindex it should be at least stated in the documentation, i mean it's a serious bug and not that much obvious, plus i don't really see a problem with removing a paused unit.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Nestharus, you exaggerated the problem.

Removing a paused unit fires the Undefend ability. It's when you remove a paused unit inside a transport that it doesn't.

Although, heroes that are paused inside a transport and then removed will fire the undefend event, so it's only non-hero units that are paused (with PauseUnit) that are in a transport that are removed (with RemoveUnit) that it bugs (that's 4 crazy conditions, almost like winning the lottery).

But yes the default-on autocast ability will bug the system as well, if you try to call GetUnitUserData on that unit, because it will still be 0.
 
Level 12
Joined
Mar 28, 2005
Messages
160
by the Unit Indexer here, you are in fact referring to your resource?

seems like a lot to get the system up and running...and you see, this returns to my initial concerns about the real original problem....

three systems, certain people like what ever....which to use? no one seems to fully grasp which actually is the best, at least not the people actually making maps, that is

I think I may go back and just use hashtables

since I don't need them periodically, it shouldn't matter anyways....


btw, if we don't pause units, how do we pause them? a custom stun?
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Well the only way to have a paused unit a unit in a transport is to pause him with PauseUnit WHILE he is already loaded, because paused/stunned units cannot be loaded into transports, and you can't target a loaded unit for a spell.

I recommend using a hashtable (or a Table) unless you need onUnitIndex/Deindex or index locking utility. Unit Indexers are good but until Nestharus has a more stable version there is no "best" indexer imo. I have a system called iUnit which is loaded into my own map which has all the pros of the existing indexers with none of the cons, but I don't want to release it because it would be yet ANOTHER unit indexing system.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
A problem with Flamestrike is that it doesn't fire immediately, so if you have 2 units being knocked back into trees, only one of them will get it at once. Which is why in Knockback 2D I only fired it once per instance per unit being knocked back :/

The way to make it work faster is to use UnitRemoveAbility, right after casting the flamestrike. So it would need to use UnitAddAbility/IssuePointOrder/UnitRemoveAbility.
 
Well, all this could use is some optimizations, but I'm not going to let that stop it from being approved somewhere.
Usually, systems like these don't belong in such a section, and deserve more attention, so I would totally recommend posting this in the Spells section so it can get the attention it deserves.

Some minor improvements:
- Reduce the number of groups you're using
- Use only one location handle
- Use constant strings instead of that string array for storing special effects because it reduces code size while yielding the same results and allowing you to get rid of those constant globals with an optimizer of some sort.
- Make the preloading optional
- Make the struct extend an array so you can do allocation/deallocation faster by using an index from the linked list arrays as a sentinel value (used for storing the last recycled instance for example)

The last one is totally optional :p

edit
Oh, and wouldn't it be better to use methods instead of static methods that have thistype this as an argument? :eek:
 
Magtheridon96 said:
Usually, systems like these don't belong in such a section, and deserve more attention, so I would totally recommend posting this in the Spells section so it can get the attention it deserves.

Agreed. I don't see where a sliding system fits into the JASS section.

I don't want to graveyard this though. Lets give it a bit and see if the author if willing to update it.
 
Top