- Joined
- Jun 20, 2011
- Messages
- 249
JASS:
library Missile /* v2.0.1
*/uses/*
*/ CTL /* hiveworkshop.com/forums/jass-resources-412/snippet-constant-timer-loop-32-a-201381/
*/ AdvLoc /* hiveworkshop.com/forums/submissions-414/snippet-lacking-loc-209322/
*/ optional WorldBounds /* hiveworkshop.com/forums/jass-functions-413/snippet-worldbounds-180494/
*/ optional MissileRecycler /* hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
Credits:
- emjlr3 for the deflect angle equation
- AceHart for the parabola equation
**********************************************************************/
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_RAW_CODE = 'e000'
endglobals
/**********************************************************************
*
* struct Missile
*
* static method create takes real originX, real originY, real originZ...
* ...real angle, real distance, real targetZ returns Missile
* - The angle is in radians.
* - 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 slide
* - The amount of distance it has covered.
* AdvLoc origin
* - The missile's origin Loc
* AdvLoc impact
* - Where the missile will aim to.
*
* 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 rough 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.
*
* (optional)
* static method onMissile takes Missile this, Missile hit, real range returns boolean
* - Runs whenever the missile encounters another missile.
* - If returns true the missile is destroyed.
*
* (optional)
* static method onRemove takes Missile this returns nothing
* - Runs whenever the missile is deallocated.
*
* 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.
*
**********************************************************************/
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
AdvLoc impact
AdvLoc origin
real slide
readonly real x
readonly real y
readonly real z
private real cA //current angle
private effect fx //effect
private string fP //model
private real dS
readonly unit dummy
readonly group unitsHit
boolean recycle
boolean wantDestroy
unit target
unit source
real collision
real height
real turn
real open
real damage
real speed
real acceleration
integer data
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)*origin.distance
endmethod
method operator curve takes nothing returns real
return Atan(open/origin.distance)
endmethod
method operator arc= takes real value returns nothing
set height=Tan(value)*origin.distance/4
endmethod
method operator arc takes nothing returns real
return Atan(4*height/origin.distance)
endmethod
method operator scale= takes real v returns nothing
call SetUnitScale(dummy,v,v,v)
set dS=v
endmethod
method operator scale takes nothing returns real
return dS
endmethod
static method createEx takes unit whichUnit, AdvLoc o, AdvLoc i 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 x = o.x
set y = o.y
set z = o.z
set origin = o
set impact = i
set cA = origin.angle
set slide=0
set dummy=whichUnit
call MoveLocation(Loc.global,o.x,o.y)
call SetUnitFlyHeight(dummy,o.z-GetLocationZ(Loc.global),0)
call MissileList.base.insertNode(this)
return this
endmethod
static method createLoc takes AdvLoc o, AdvLoc i returns thistype
call AdvLoc.link(o,i)
static if LIBRARY_MissileRecycler then
return createEx(GetRecycledMissile(o.x,o.y,o.z,o.angle*bj_RADTODEG),o,i)
else
return createEx(CreateUnit(Player(15),DUMMY_RAW_CODE,o.x,o.y,o.angle*bj_RADTODEG),o,i)
endif
endmethod
static method create takes real ox, real oy, real oz, real a, real d, real iz returns thistype
local AdvLoc o = AdvLoc.create(ox,oy,oz)
local AdvLoc i = AdvLoc.create(ox+d*Cos(a),oy+d*Sin(a),iz)
call AdvLoc.link(o,i)
static if LIBRARY_MissileRecycler then
return createEx(GetRecycledMissile(o.x,o.y,o.z,o.angle*bj_RADTODEG),o,i)
else
return createEx(CreateUnit(Player(15),DUMMY_RAW_CODE,o.x,o.y,o.angle*bj_RADTODEG),o,i)
endif
endmethod
method bounce takes nothing returns nothing
call origin.move(x,y,z)
set slide=0
endmethod
method deflect takes real tx, real ty returns nothing
local real a = 2*Atan2(ty-y,tx-x)+bj_PI-cA
call impact.move(x+(origin.distance-slide)*Cos(a),y+(origin.distance-slide)*Sin(a),impact.z)
call this.bounce()
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 impact.unlock()
call origin.unlock()
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 h
local real tx
local real ty
local real ox
local real oy
local AdvLoc o
loop
exitwhen head
set o = origin
set ox = o.x
set oy = o.y
set h = height
if target!=null and GetUnitTypeId(target)!=0 then
call impact.move(GetUnitX(target),GetUnitY(target),GetUnitFlyHeight(target))
set a = Atan2(impact.y-y,impact.x-x)
set slide = origin.distance-SquareRoot((impact.x-x)*(impact.x-x)+(impact.y-y)*(impact.y-y))
else
set a = o.angle
set target = null
endif
if turn!=0 and not(Cos(cA-a)>=Cos(turn)) then
if Sin(a-cA)>=0 then
set cA = cA+turn
else
set cA = cA-turn
endif
else
set cA = a
endif
set d = o.distance
set s = slide+speed
set slide = s
call SetUnitFacing(dummy,cA*bj_RADTODEG)
set tx = x+speed*Cos(cA)
set ty = y+speed*Sin(cA)
set speed = speed + acceleration
set x = tx
set y = ty
if h!=0 or o.slope!=0 then
call MoveLocation(Loc.global,tx,ty)
set z = 4*h*s*(d-s)/(d*d)+o.slope*s+o.z
call SetUnitFlyHeight(dummy,z-GetLocationZ(Loc.global),0)
call SetUnitAnimationByIndex(dummy,R2I(Atan(origin.slope)-Atan((8*h*s-4*d*h)/(d*d))*bj_RADTODEG)+90)
endif
if open!=0 then
set a = 4*open*s*(d-s)/(d*d)
set tx = tx+a*Cos(cA+1.57)
set ty = ty+a*Sin(cA+1.57)
call SetUnitFacing(dummy,(cA+Atan(-(8*open*s-4*d*open)/(d*d)))*bj_RADTODEG)
endif
static if LIBRARY_WorldBounds then
if tx>WorldBounds.maxX or tx<WorldBounds.minX or ty>WorldBounds.maxY or ty<WorldBounds.minY then
call destroy()
else
call SetUnitX(dummy,tx)
call SetUnitY(dummy,ty)
endif
else
call SetUnitX(dummy,tx)
call SetUnitY(dummy,ty)
endif
if s>=d then
set recycle = true
endif
set this = next
endloop
endmethod
endstruct
globals
private integer ACTIVE = 0
private integer SIZE = 0
private trigger FIRE = CreateTrigger()
private integer array STACK
private integer array INSTANCES
private TimerGroup32 array TIMER
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 TIMER[STACK[SIZE]].stop()
if ACTIVE==0 then
return
endif
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
call TIMER[index].start()
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
module MissileStruct
private static method missileTerminate takes Missile this returns nothing
static if thistype.onRemove.exists then
call onRemove(this)
endif
call this.terminate()
call StopPeriodic(thistype.typeid)
endmethod
static method unpin takes Missile this returns nothing
call this.removeNode()
call StopPeriodic(thistype.typeid)
endmethod
static if thistype.onMissile.exists then
private static method onMissileLoop takes Missile this returns nothing
local Missile node = MissileList.base
local real ox = this.x
local real oy = this.y
loop
set node = node.next
exitwhen MissileList(node).head
loop
exitwhen node.head
if node!=this and onMissile(this,node,SquareRoot((ox-node.x)*(ox-node.x)+(oy-node.y)*(oy-node.y))) then
call missileTerminate(this)
endif
set node = MissileList(node).next
endloop
endloop
endmethod
endif
//***********************************************************************
// First it takes the struct's first instance, which is the next to the
// head (NODE[thistype.typeid].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[thistype.typeid].next
call this.move()
loop
exitwhen this.head
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
static if thistype.onCollide.exists then
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
endif
static if thistype.onMissile.exists then
call onMissileLoop(this)
endif
static if thistype.onPeriod.exists then
if thistype.onPeriod(this) then
call missileTerminate(this)
endif
endif
set this = this.next
endloop
set u=null
endmethod
static method launch takes Missile this returns nothing
call StartPeriodic(thistype.typeid)
call NODE[thistype.typeid].insertNode(this)
endmethod
private static method onInit takes nothing returns nothing
set NODE[thistype.typeid] = Missile.createNode()
set TIMER[thistype.typeid] = TimerGroup32.create(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
implement MissileStruct
static method onExpire takes nothing returns nothing
local real x = GetUnitX(FOOTMAN)
local real y = GetUnitY(FOOTMAN)
local real a = GetRandomReal(-bj_PI,bj_PI)
local Missile new = Missile.create(x,y,65,x+1000*Cos(a),y+1000*Sin(a),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)
else
call TimerStart(t,0.005,true,function thistype.onExpire)
endif
set b = not b
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
-onDestructable method
-Improve onMissile's efficiency
-Math improvements
Last edited by a moderator: