View Single Post
Old 04-08-2011, 09:35 PM   #1 (permalink)
Registered User emjlr3
Hulk Smash
 
emjlr3's Avatar
 
Join Date: Mar 2005
Posts: 265
emjlr3 is on a distinguished road (72)emjlr3 is on a distinguished road (72)
Peon Power! [System] [Needs work] Slide System

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
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
Change Log
  • 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
Attached Files
File Type: mdx AirSlide.mdx (9.0 KB, 17 views)
File Type: mdx LandSlide.mdx (1.5 KB, 12 views)
File Type: mdx SeaSlide.mdx (2.0 KB, 10 views)
__________________


Would you like to create realistic missile deflections?
Looking to submit a spell or system? Try my Demo Map Template.
The Slide/Knockback Engine you should be using!

Last edited by emjlr3; 02-04-2012 at 02:01 PM.
emjlr3 is offline   Reply With Quote