Dirac
22710180
- Reaction score
- 147
JASS:
library Missile /* v1.2.0
*/uses/*
*/ LinkedListModule /* thehelper.net/forums/showthread.php/168775-LinkedListModule
*/ optional WorldBounds /* hiveworkshop.com/forums/jass-functions-413/snippet-worldbounds-180494/
*/ optional MissileRecycler /* hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
**********************************************************************/
globals
//***********************************************************************
// This dummy must use Vexorian's dummy model and it's movement type
// should be "Hover" if you want to correctly move over water, otherwise
// use "None". The dummy must also have Crow Form. If you don't have a
// dummy unit in your map then use the one MissileRecycler provides.
private constant integer DUMMY_ID = 'e000'
endglobals
/**********************************************************************
*
* struct Missile
*
* static method create takes real originX, real originY, real originZ...
* ...real angle, real distance, real targetZ returns Missile
* - Missiles have the following values to be set by you.
* - real speed
* - real turn
* - real open / curve
* - real height / arc
* - real acceleration
* - real collision
* - unit target
* - unit source
* - string model
* - integer data
*
* real x
* real y
* real z
* - The missile's position
*
* real distance
* - The amount of distance the Missile will travel.
* real slide
* - The amount of distance is left to travel.
* real angle
* - Facing angle of the missile. (Radians)
*
* method deflect takes real x, real y returns nothing
* - Deflects the missile from the target point, changing
* - it's course.
*
* method bounce takes nothing returns nothing
* - Bounces the missile from it's current Z position.
* - Useful when assigning targets to missiles that were
* - already active, it fixes the rought Z position change.
*
***********************************************************************
*
* (optional)
* static method onCollide takes Missile this, unit justHit returns boolean
* - Runs every time the missile collides with something.
* - If returns true the missile is destroyed.
*
* (optional)
* static method onPeriod takes Missile this returns boolean
* - Runs every period.
* - If returns true the missile is destroyed.
*
* (optional)
* static method onFinish takes Missile this returns boolean
* - Runs whenever the missile finishes it's course.
* - If returns true the missile is destroyed.
*
* module MissileStruct
*
* static method launch takes Missile toLaunch returns nothing
*
***********************************************************************
*
* DISCLAIMER:
*
* - Missile's default owner is Player(15) and this is meant to
* - not be modifiable.
*
* - For curved missiles the "x" and "y" coordinates are only
* - correct in the moment of launch or impact, but during it's
* - air time they ignore the curve it has.
*
* - This system can support up to 600 proyectiles with complex
* - mathematics (curve, arc, collision) before it lags.
*
* - If you wish to enumerate across all proyecitles you must
* - learn how to use the LinkedListModule. This system provides
* - a struct that keeps track of all active proyectiles called
* - "MissileList", start looping from "MissileList.base.next".
*
**********************************************************************/
globals
private constant real HALF_PI = bj_PI/2
private location GetZ = Location(0,0)
endglobals
struct MissileList extends array
implement LinkedList
endstruct
struct Missile extends array
//***********************************************************************
// Why LinkedList? In order to reach a high level of dynamism i'm basing
// this system on iterating through multiple linked lists, each struct
// has one and each struct iterates through it.
implement LinkedList
real x
real y
real z
private real oX //origin x
private real oY //origin y
private real oZ //origin z
real slide
real distance
private real cA //current angle
private real fZ //z slope
private real tZ //target z
private real sX //speed x vector
private real sY //speed y vector
private real sM //speed module
private real aX //acceleration x vector
private real aY //acceleration y vector
private real aM //acceleration module
private effect fx //effect
private string fP //model
readonly unit dummy
readonly group unitsHit
boolean recycle
boolean wantDestroy
unit target
unit source
real collision
real heightv
real turn
real open
integer data
method operator speed= takes real value returns nothing
set sX=value*Cos(cA)
set sY=value*Sin(cA)
set sM=value
endmethod
method operator speed takes nothing returns real
return sM
endmethod
method operator acceleration= takes real value returns nothing
set aX=value*Cos(cA)
set aY=value*Sin(cA)
set aM=value
endmethod
method operator acceleration takes nothing returns real
return aM
endmethod
method operator angle= takes real value returns nothing
set cA=value
set sX=sM*Cos(value)
set sY=sM*Sin(value)
set aX=aM*Cos(value)
set aY=aM*Sin(value)
if target!=null and distance!=0 then
call MoveLocation(GetZ,x+(distance-slide)*Cos(value),y+(distance-slide)*Sin(value))
set fZ=(tZ+GetLocationZ(GetZ)-z)/distance
endif
endmethod
method operator angle takes nothing returns real
return cA
endmethod
method operator model= takes string path returns nothing
call DestroyEffect(fx)
set fP=path
set fx=AddSpecialEffectTarget(path,dummy,"origin")
endmethod
method operator model takes nothing returns string
return fP
endmethod
method operator curve= takes real value returns nothing
set open=Tan(value)*distance
endmethod
method operator curve takes nothing returns real
return Atan(open/distance)
endmethod
method operator arc= takes real value returns nothing
set height=Tan(value)*distance
endmethod
method operator arc takes nothing returns real
return Atan(height/distance)
endmethod
static method createEx takes unit whichUnit, real ox, real oy, real oz, real a, real d, real tz returns thistype
local thistype this=allocate()
set source=null
set target=null
set acceleration=0
set height=0
set turn=0
set open=0
set unitsHit=CreateGroup()
set collision=0
set recycle=false
set wantDestroy=false
set fP=""
set sM=0
set sX=0
set sY=0
call MoveLocation(GetZ,ox,oy)
set oX=ox
set oY=oy
set oZ=oz+GetLocationZ(GetZ)
set distance=d
set angle=a
set slide=0
set x=oX
set y=oY
set z=oZ
call MoveLocation(GetZ,ox+d*Cos(a),oy+d*Sin(a))
set tZ=tz
if d!=0 then
set fZ=(tz+GetLocationZ(GetZ)-oZ)/d
endif
set dummy=whichUnit
call SetUnitFlyHeight(dummy,oz,0)
call MissileList.base.insertNode(this)
return this
endmethod
static method create takes real ox, real oy, real oz, real a, real d, real z returns thistype
static if LIBRARY_MissileRecycler then
return createEx(GetRecycledMissile(ox,oy,oz,a*bj_RADTODEG),ox,oy,oz,a,d,z)
else
return createEx(CreateUnit(Player(15),DUMMY_ID,ox,oy,a*bj_RADTODEG),ox,oy,oz,a,d,z)
endif
endmethod
method bounce takes nothing returns nothing
set oX=GetUnitX(dummy)
set oY=GetUnitY(dummy)
call MoveLocation(GetZ,oX+distance*Cos(angle),oY+distance*Sin(angle))
set fZ=(tZ+GetLocationZ(GetZ)-z)/distance
set oZ=z
set x=oX
set y=oY
set slide=0
endmethod
method deflect takes real tx, real ty returns nothing
set distance=distance-slide
set angle=2*Atan2(ty-y,tx-x)+bj_PI-cA
call this.bounce()
call SetUnitFacing(dummy,cA*bj_RADTODEG)
endmethod
method destroy takes nothing returns nothing
set wantDestroy=true
endmethod
method terminate takes nothing returns nothing
call DestroyEffect(fx)
call DestroyGroup(unitsHit)
set recycle=false
set fx=null
static if LIBRARY_MissileRecycler then
call RecycleMissile(dummy)
else
call RemoveUnit(dummy)
endif
call MissileList(this).removeNode()
call this.removeNode()
call this.deallocate()
endmethod
method move takes nothing returns nothing
local real a
local real d
local real s
local real tx
local real ty
local real ox
local real oy
loop
exitwhen this.head
set ox=this.oX
set oy=this.oY
if this.target!=null and not(IsUnitType(this.target,UNIT_TYPE_DEAD)) and GetUnitTypeId(this.target)!=0 then
set tx=GetUnitX(this.target)
set ty=GetUnitY(this.target)
set a=Atan2(ty-this.y,tx-this.x)
call MoveLocation(GetZ,tx,ty)
if this.turn!=0 and not(Cos(this.cA-a)>=Cos(this.turn)) then
if Sin(a-this.cA)>=0 then
set this.angle=this.cA+this.turn
else
set this.angle=this.cA-this.turn
endif
else
set this.angle=a
endif
set d=SquareRoot((tx-ox)*(tx-ox)+(ty-oy)*(ty-oy))
set s=SquareRoot((this.x-ox)*(this.x-ox)+(this.y-oy)*(this.y-oy))
set this.distance=d
set this.slide=s
set this.fZ=(GetLocationZ(GetZ)+GetUnitFlyHeight(this.target))/d
call SetUnitFacing(this.dummy,this.cA*bj_RADTODEG)
else
set this.target=null
set this.slide=this.slide+this.sM
set d=this.distance
set s=this.slide
endif
set tx=this.x
set ty=this.y
set this.x=tx+this.sX
set this.y=ty+this.sY
if this.open!=0 then
set a=4*this.open*s*(d-s)/(d*d)
set tx=tx+a*Cos(this.cA+HALF_PI)
set ty=ty+a*Sin(this.cA+HALF_PI)
call SetUnitFacing(this.dummy,(this.cA+Atan(-(8*this.open*s-4*d*this.open)/(d*d)))*bj_RADTODEG)
endif
if this.height!=0 or this.fZ!=0 then
set this.z=4*this.height*s*(d-s)/(d*d)+this.oZ+this.fZ*s
call MoveLocation(GetZ,tx,ty)
call SetUnitFlyHeight(this.dummy,this.z-GetLocationZ(GetZ),0)
call SetUnitAnimationByIndex(this.dummy,R2I((Atan(-(8*this.height*s-4*d*this.height)/(d*d))-Atan(-this.fZ))*bj_RADTODEG)+90)
endif
set this.sX=this.sX+this.aX
set this.sY=this.sY+this.aY
static if LIBRARY_WorldBounds then
if IsPointInRegion(WorldBounds.worldRegion,tx,ty) then
call SetUnitX(this.dummy,tx)
call SetUnitY(this.dummy,ty)
else
call this.destroy()
endif
else
call SetUnitX(this.dummy,tx)
call SetUnitY(this.dummy,ty)
endif
if s>=d then
set this.recycle=true
endif
set this=this.next
endloop
endmethod
endstruct
//***********************************************************************
// This part of the code might be confusing to some. It's very similar
// to how CTL works. Never the less it deserves an explanation.
globals
private integer COUNT = 0
private integer ACTIVE = 0
private integer SIZE = 0
private timer TIMER = CreateTimer()
private trigger FIRE = CreateTrigger()
private boolexpr array METHOD
private integer array STACK
private integer array INSTANCES
private triggercondition array COND
private Missile array NODE
endglobals
//***********************************************************************
// This function runs periodically. Can you see the trigger evaluation
// at the end? If you've read T32 then you know exactly what it does.
// The loop above is for cleaning up, the SIZE variable keeps track of
// how many instances have been deallocated by the user, if higher than
// 0 then some of them need to be removed. STACK[SIZE] stores the value
// of the deallocated instances.
private function Execute takes nothing returns nothing
loop
exitwhen SIZE==0
set ACTIVE=ACTIVE-1
set SIZE=SIZE-1
set INSTANCES[STACK[SIZE]]=INSTANCES[STACK[SIZE]]-1
if INSTANCES[STACK[SIZE]]==0 then
call TriggerRemoveCondition(FIRE,COND[STACK[SIZE]])
endif
if ACTIVE==0 then
call PauseTimer(TIMER)
return
endif
endloop
call TriggerEvaluate(FIRE)
endfunction
//***********************************************************************
// Adds a new instance to the given struct index (This system attaches
// indexes to every struct you implement MissileStruct to) If the amount
// of INSTANCES[index] was 0 then it adds the struct's iterate method to
// the FIRE trigger for it's evaluation. ACTIVE keeps track of all
// allocated instances, if it was 0 that means the timer isn't even
// running yet, it needs to be started.
private function StartPeriodic takes integer index returns nothing
if INSTANCES[index]==0 then
set COND[index]=TriggerAddCondition(FIRE,METHOD[index])
endif
if ACTIVE==0 then
call TimerStart(TIMER,0.03125,true,function Execute)
endif
set ACTIVE=ACTIVE+1
set INSTANCES[index]=INSTANCES[index]+1
endfunction
//***********************************************************************
// Adds the struct's index to the stack to clear it in the Execute
// function above.
private function StopPeriodic takes integer index returns nothing
set STACK[SIZE]=index
set SIZE=SIZE+1
endfunction
//***********************************************************************
// Gives the struct an index. COUNT keeps track of the amount of structs
// with the module implementation. NODE[] is the head of the struct's
// linked list of missiles.
private function IndexStruct takes code func returns integer
set COUNT=COUNT+1
set NODE[COUNT]=Missile.createNode()
set METHOD[COUNT]=Filter(func)
return COUNT
endfunction
module MissileStruct
private static integer INDEX = 0
private static method missileTerminate takes Missile this returns nothing
call this.terminate()
call StopPeriodic(INDEX)
endmethod
//***********************************************************************
// First it takes the struct's first instance, which is the next to the
// head (NODE[INDEX].next) and starts iterating through it until it hits
// the head again. Then checks if the missile is marked for recycling,
// if so it runs the methods and destroys the missile.
private static method missileIterate takes nothing returns nothing
local unit u
local Missile this=NODE[INDEX].next
call this.move()
loop
exitwhen this.head
static if thistype.onPeriod.exists then
if thistype.onPeriod(this) then
call missileTerminate(this)
endif
endif
if this.wantDestroy then
static if thistype.onFinish.exists then
call thistype.onFinish(this)
endif
call missileTerminate(this)
else
if this.recycle then
static if thistype.onCollide.exists then
static if thistype.onFinish.exists then
if this.target==null then
if thistype.onFinish(this) then
call missileTerminate(this)
else
set this.recycle=false
endif
elseif thistype.onCollide(this,this.target) then
call missileTerminate(this)
else
set this.recycle=false
endif
else
if this.target==null then
call missileTerminate(this)
elseif thistype.onCollide(this,this.target) then
call missileTerminate(this)
else
set this.recycle=false
endif
endif
else
static if thistype.onFinish.exists then
if thistype.onFinish(this) then
call missileTerminate(this)
else
set this.recycle=false
endif
else
call missileTerminate(this)
endif
endif
else
if this.collision!=0 then
call GroupEnumUnitsInRange(bj_lastCreatedGroup,this.x,this.y,this.collision,null)
loop
set u=FirstOfGroup(bj_lastCreatedGroup)
exitwhen u==null
if not(IsUnitInGroup(u,this.unitsHit)) and u!=this.target and thistype.onCollide(this,u) then
call missileTerminate(this)
endif
call GroupAddUnit(this.unitsHit,u)
call GroupRemoveUnit(bj_lastCreatedGroup,u)
endloop
endif
endif
endif
set this=this.next
endloop
set u=null
endmethod
static method launch takes Missile this returns nothing
call StartPeriodic(INDEX)
call NODE[INDEX].insertNode(this)
endmethod
private static method onInit takes nothing returns nothing
set INDEX=IndexStruct(function thistype.missileIterate)
endmethod
endmodule
endlibrary
JASS:
struct TESTER extends array
static integer COUNT = 0
static unit FOOTMAN = null
static timer t = CreateTimer()
static boolean b = false
static method onCollide takes Missile this, unit hit returns boolean
if hit!=this.source then
call this.deflect(GetUnitX(hit),GetUnitY(hit))
endif
return false
endmethod
static method onFinish takes Missile this returns boolean
call DestroyEffect(AddSpecialEffect(this.model,this.x,this.y))
return true
endmethod
implement MissileStruct
static method onExpire takes nothing returns nothing
local Missile new = Missile.create(GetUnitX(FOOTMAN),GetUnitY(FOOTMAN),65,GetRandomReal(0,bj_PI*2),1000,0)
set new.speed=10
set new.model="abilities\\weapons\\WyvernSpear\\WyvernSpearMissile.mdl"
set new.height=100
set new.source=FOOTMAN
set new.collision=80
call launch(new)
endmethod
static method onEsc takes nothing returns nothing
if b then
call PauseTimer(t)
set b=false
else
call TimerStart(t,0.005,true,function thistype.onExpire)
set b=true
endif
endmethod
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)
set FOOTMAN=CreateUnit(Player(0),'hfoo',0,0,270)
endmethod
endstruct