- 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
Last edited: