- Joined
- Aug 3, 2004
- Messages
- 2,891
Currently this spell does not detect obstacles or changes in cliff height so ground units can end up in the middle of trees or in deep water.
JASS:
//*****************************************************************
//* REPELLING FIELD
//* Created by Archmage Owenalacaster
//*
//* Allows units with the specified buff to knockback enemies.
//*
//* requires: -SmoothMovSys
//* -ProjSys
//* -BoolexprUtils
//* -GroupUtils
//* -TimerUtils
//*****************************************************************
scope RepellingField initializer Init
globals
private constant integer BUFF_ID = 'BM07'
private constant real MANA_PER_KNOCK = 1.0
private constant real TIMER_INTERVAL = 0.2
private constant real KNOCK_RANGE = 700.0
private constant real KNOCK_MAX_DIST = 800.0
private constant real KNOCK_SPEED = 700.0
private constant real KNOCK_HEIGHT = 0.0
private constant string KNOCK_FX = "Abilities\\Spells\\Items\\SpellShieldAmulet\\SpellShieldCaster.mdl"
private constant string KNOCK_ATTACH = "origin"
endglobals
//===========================================================================
// END OF CALIBRATION SECTION
//===========================================================================
globals
private constant string STOP = "stop"
private group RepelGroup = CreateGroup()
private boolexpr RepelFilter = null
private boolexpr RepelEffect = null
private unit RepelUnit = null
endglobals
private function RepelFilterCond takes nothing returns boolean
return GetUnitAbilityLevel(GetFilterUnit(),BUFF_ID) >= 1
endfunction
private function RepelEffectCond takes nothing returns boolean
local unit u = GetFilterUnit()
if IsUnitEnemy(u,GetOwningPlayer(RepelUnit)) and GetWidgetLife(u) > 0.405 and GetUnitState(RepelUnit,UNIT_STATE_MANA) >= MANA_PER_KNOCK and GetUnitMoveSpeed(u) > 0 and not(IsUnitType(u,UNIT_TYPE_STRUCTURE) or IsUnitType(u,UNIT_TYPE_SNARED) or IsUnitType(u,UNIT_TYPE_MAGIC_IMMUNE) or IsUnitProjectile(u)) then
call SetUnitState(RepelUnit,UNIT_STATE_MANA,GetUnitState(RepelUnit,UNIT_STATE_MANA) - MANA_PER_KNOCK)
call DestroyEffect(AddSpecialEffectTarget(KNOCK_FX,u,KNOCK_ATTACH))
call KnockBack(u,KNOCK_MAX_DIST-GetUnitsDist(RepelUnit,u),GetUnitsAngle(RepelUnit,u),KNOCK_HEIGHT,KNOCK_SPEED,function Null,function Null,false)
call IssueImmediateOrder(u,STOP)
endif
set u = null
return false
endfunction
private function RepelEnemies takes nothing returns nothing
set RepelUnit = GetEnumUnit()
call GroupEnumUnitsInRange(ENUM_GROUP,GetUnitX(RepelUnit),GetUnitY(RepelUnit),KNOCK_RANGE,RepelEffect)
set RepelUnit = null
endfunction
private function TimerAction takes nothing returns nothing
local integer index = 0
loop
call GroupEnumUnitsOfPlayer(RepelGroup,Player(index),RepelFilter)
call ForGroup(RepelGroup,function RepelEnemies)
call GroupClear(RepelGroup)
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
endfunction
private function Init takes nothing returns nothing
call TimerStart(NewTimer(),TIMER_INTERVAL,true,function TimerAction)
set RepelFilter = Condition(function RepelFilterCond)
set RepelEffect = Condition(function RepelEffectCond)
endfunction
endscope
JASS:
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
set tT[0]=CreateTimer()
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
JASS:
//******************************************************************************************
//*
//* Smooth movement script 2.0 By Moyack. 2009.
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//*
//* If you use this script, please be nice and give me credits.
//*
//* This library has been designed to simulate movement defined by kinematic parameters.
//*
//* You only need to define the Projectile unit, the position and velocity vectors,
//* and the time the Projectile will fly. The rest is managed, controlled and destroyed
//* automatically by the library.
//*
//* Additionally, you can define custom "events" when the Projectile starts, moves and ends,
//* very convenient to set and control the Projectile behaviour.
//*
//* How to use:
//* ¯¯¯¯¯¯¯¯¯¯¯
//* This library has two data types, the vector and the Projectile structs.
//*
//* Vector Struct: This struct allows you to group data in the way (X, Y, Z). The vector
//* type allows the following methods:
//*
//* - create( real x, real y, real z) returns vector
//* - setdata(real x, real y, real z) returns nothing
//* - destroy() returns nothing
//*
//* The first method allocates a new vector variable and sets at the same time data on it.
//* The second method allows you to change the data of the vector. The third method destroys
//* the vector. Example:
//*
//* function VecStuff takes nothing returns nothing
//* local vector v = vector.create(0., 10., 20.) // creates a vector [0, 10, 20]
//* call v.setdata(15., 20., -10.) // changes the values in the vector v to [15, 20, -10]
//* call v.destroy() // destroys the vector, freeing the memory used.
//* endfunction
//*
//* Projectile Struct: This one is the one in charge to do the nice things. This structs
//* allows the following methods:
//*
//* Aknowledges:
//* ¯¯¯¯¯¯¯¯¯¯¯¯
//* - Mad[Lion] because he proposed the problem that inspire me to make this library.
//* - Pipedream because he had the same apporach, but opposite :) and for the idea of
//* partial factoring.
//* - Vexorian, PitzerMike and all the people involved in the development of vJASS.
//* without this tool, this idea would be still just that: an idea.
//*
//******************************************************************************************
library SmoothMovSys
//***************************************************************************************************************
//* Configuration globals
globals
private constant real dt = 0.022 // sets the timer value
endglobals
//***************************************************************************************************************
//* End configuration globals
globals
private unit LastProj = null // Stores temporally the last Projectile created / destroyed
private vector StartPos // Stores the start position of the Projectile.
private vector StartVel // Stores the start velocity of the Projectile.
private vector LastPos // Stores the last position of the Projectile.
private vector LastVel // Stores the last velocity of the Projectile.
private Projectile MovingProj // Stores the evaluating Projectile. Only for use when
endglobals // it's called on the CodeMove function
struct vector // struct used to manage vectors LOL!!!
real x = 0.
real y = 0.
real z = 0.
static method create takes real x, real y, real z returns vector
local vector v = vector.allocate()
set v.x = x
set v.y = y
set v.z = z
return v
endmethod
method setdata takes real x, real y, real z returns nothing
set .x = x
set .y = y
set .z = z
endmethod
endstruct
private function GetA takes real x0, real x1, real v0, real v1, real tf returns real
return (tf * (v1-v0) - 2 * (x1 - x0 - v0*tf)) / (tf*tf*tf)
endfunction
private function GetB takes real x0, real x1, real v0, real v1, real tf returns real
return (3 * (x1 - x0 - v0*tf) - tf * (v1 - v0)) / (tf*tf)
endfunction
private function GetX takes real t, real A, real B, real C, real D returns real
return D + t*(C + t*(B + t*A)) // thanks Pipedream for the idea :)
endfunction
private function GetV takes real t, real A, real B, real C returns real
return (3*A*t + 2*B)*t + C // thanks Pipedream for the idea :)
endfunction
struct Projectile
private static region reg = null
private static trigger TRG = CreateTrigger()
static group ProjGroup = CreateGroup()
private static integer counter = 0
private static Projectile array Projectiles
private integer i
unit p // Projectile unit
vector pi // initial and final position coordinates
vector pf
vector vi // initial and final velocity coordinates
vector vf
real tf = 0. // time that the Projectile will take to reach the final point
vector vp // variable position and velocity vars
vector vv
real t = 0.
vector A // Constant Values used for calculation stuff
vector B
// Miscelaneous data
boolean DV // this flag indicates if the Projectile is destroyed with the input vectors
boolean pU // this flag indicates if the unit used as projectile will be paused or not
trigger CodeMove //Stores the function name that will be evaluated every time any Projectile is moving
trigger CodeEnd //Stores the function name used when the Projectile dies.
method SetData takes unit p, vector pi, vector pf, vector vi, vector vf, real t, boolean DV, boolean pU returns nothing
local real Ax = GetA(pi.x, pf.x, vi.x, vf.x, t)
local real Ay = GetA(pi.y, pf.y, vi.y, vf.y, t)
local real Az = GetA(pi.z, pf.z, vi.z, vf.z, t)
local real Bx = GetB(pi.x, pf.x, vi.x, vf.x, t)
local real By = GetB(pi.y, pf.y, vi.y, vf.y, t)
local real Bz = GetB(pi.z, pf.z, vi.z, vf.z, t)
set .p = p
call UnitAddAbility(p, 'Amrf')
call UnitRemoveAbility(p, 'Amrf')
call GroupAddUnit(Projectile.ProjGroup, p)
if pU then
call PauseUnit(p, true)
endif
set .pi = pi
set .pf = pf
set .vi = vi
set .vf = vf
set .A = vector.create(Ax, Ay, Az)
set .B = vector.create(Bx, By, Bz)
set .vp = vector.create(0., 0., 0.)
set .vv = vector.create(0., 0., 0.)
if t <= 0. then
set .tf = dt
else
set .tf = t
endif
set .DV = DV
set .pU = pU
endmethod
private method update takes nothing returns nothing
local real x
local real y
local real z
if .CodeMove != null then
set MovingProj = this
call TriggerExecute(.CodeMove)
endif
set .t = .t + dt
set x = GetX(.t, .A.x, .B.x, .vi.x, .pi.x)
set y = GetX(.t, .A.y, .B.y, .vi.y, .pi.y)
set z = GetX(.t, .A.z, .B.z, .vi.z, .pi.z)
call .vp.setdata(x, y, z)
set x = GetV(.t, .A.x, .B.x, .vi.x)
set y = GetV(.t, .A.y, .B.y, .vi.y)
set z = GetV(.t, .A.z, .B.z, .vi.z)
call .vv.setdata(x, y, z)
endmethod
method SetStartCode takes code start returns nothing
if start != null then
call TriggerClearActions(Projectile.TRG)
set LastProj = .p
call StartPos.setdata(.pi.x, .pi.y, .pi.z)
call StartVel.setdata(.vi.x, .vi.y, .vi.z)
call LastPos.setdata(.pf.x, .pf.y, .pf.z)
call LastVel.setdata(.vf.x, .vf.y, .vf.z)
call TriggerAddAction(Projectile.TRG, start)
call TriggerExecute(Projectile.TRG)
endif
endmethod
method SetMoveCode takes code move returns nothing
if .CodeMove == null then
set .CodeMove = CreateTrigger()
else
call TriggerClearActions(.CodeMove)
endif
call TriggerAddAction(.CodeMove, move)
endmethod
method SetEndCode takes code end returns nothing
if .CodeEnd == null then
set .CodeEnd = CreateTrigger()
else
call TriggerClearActions(.CodeEnd)
endif
call TriggerAddAction(.CodeEnd, end)
endmethod
method GetVelocity takes nothing returns vector
local vector v = vector.create(0,0,0)
local real x = GetV(.t, .A.x, .B.x, .vi.x)
local real y = GetV(.t, .A.y, .B.y, .vi.y)
local real z = GetV(.t, .A.z, .B.z, .vi.z)
call v.setdata(x, y, z)
return v
endmethod
method GetPosition takes nothing returns vector
return vector.create(GetUnitX(.p), GetUnitY(.p), GetUnitFlyHeight(.p))
endmethod
private method onDestroy takes nothing returns nothing
call GroupRemoveUnit(Projectile.ProjGroup, .p)
if .pU then
call PauseUnit(.p, false)
endif
set LastProj = .p
set .p = null
call StartPos.setdata(.pi.x, .pi.y, .pi.z)
call StartVel.setdata(.vi.x, .vi.y, .vi.z)
call LastPos.setdata(.pf.x, .pf.y, .pf.z)
call LastVel.setdata(.vf.x, .vf.y, .vf.z)
if .DV then
call .pi.destroy()
call .pf.destroy()
call .vi.destroy()
call .vf.destroy()
endif
call .A.destroy()
call .B.destroy()
call .vp.destroy()
call .vv.destroy()
if .CodeEnd != null then
call TriggerExecute(.CodeEnd)
endif
set Projectile.counter = Projectile.counter - 1
set Projectile.Projectiles[Projectile.counter].i = .i
set Projectile.Projectiles[.i] = Projectile.Projectiles[Projectile.counter]
endmethod
private static method Loop takes nothing returns nothing
local integer i = 0
loop
exitwhen i >= Projectile.counter
call Projectile.Projectiles[i].update()
if Projectile.Projectiles[i].t > Projectile.Projectiles[i].tf or not IsPointInRegion(Projectile.reg, Projectile.Projectiles[i].vp.x, Projectile.Projectiles[i].vp.y) then
// destroys the Projectile data if it finished its time or if it's going to leave the playable map area
call Projectile.Projectiles[i].destroy()
else // updates the location and angle facing of the unit
call SetUnitX(Projectile.Projectiles[i].p, Projectile.Projectiles[i].vp.x)
call SetUnitY(Projectile.Projectiles[i].p, Projectile.Projectiles[i].vp.y)
call SetUnitFlyHeight(Projectile.Projectiles[i].p, Projectile.Projectiles[i].vp.z, 0.)
endif
set i = i + 1
endloop
endmethod
static method create takes unit p, vector pi, vector pf, vector vi, vector vf, real t, boolean DestroyVectors, boolean pauseUnit returns Projectile
local Projectile pr = Projectile.allocate()
call pr.SetData(p, pi, pf, vi, vf, t, DestroyVectors, pauseUnit)
set pr.i = Projectile.counter
set Projectile.Projectiles[Projectile.counter] = integer(pr)
set Projectile.counter = Projectile.counter + 1
return pr
endmethod
static method createSpherical takes unit p, vector pi, vector pf, vector vsi, vector vsf, real t, boolean DestroyVectors, boolean pauseUnit returns Projectile
// this method allows you to define the speed vectors in spherical coordinates [r, t, f]
// where r is the vector magnitude
// t is the angle in XY plane
// f is the angle in the plane defined by the vector R and the Z axis
// ALL the angles are in RADIANS
// when the Projectile is created, all the entered vectors will be changed to cartesian system
call vsi.setdata(vsi.x * Cos(vsi.z) * Cos(vsi.y), vsi.x * Cos(vsi.z) * Sin(vsi.y), vsi.x * Sin(vsi.z))
call vsf.setdata(vsf.x * Cos(vsf.z) * Cos(vsf.y), vsf.x * Cos(vsf.z) * Sin(vsf.y), vsf.x * Sin(vsf.z))
return Projectile.create(p, pi, pf, vsi, vsf, t, DestroyVectors, pauseUnit)
endmethod
private static method onInit takes nothing returns nothing
set Projectile.reg = CreateRegion()
call RegionAddRect(Projectile.reg, bj_mapInitialPlayableArea)
set StartPos = vector.create(0,0,0)
set StartVel = vector.create(0,0,0)
set LastPos = vector.create(0,0,0)
set LastVel = vector.create(0,0,0)
call TimerStart(CreateTimer(), dt, true, function Projectile.Loop)
endmethod
endstruct
// functions used to get the the unit Projectile and the vectors in custom triggers
// those functions are to be used when the unit starts and stops being a Projectile
function GetUnitProjectile takes nothing returns unit
return LastProj
endfunction
function GetInitPos takes nothing returns vector
return vector.create(StartPos.x, StartPos.y, StartPos.z)
endfunction
function GetInitVel takes nothing returns vector
return vector.create(StartVel.x, StartVel.y, StartVel.z)
endfunction
function GetLastPos takes nothing returns vector
return vector.create(LastPos.x, LastPos.y, LastPos.z)
endfunction
function GetLastVel takes nothing returns vector
return vector.create(LastVel.x, LastVel.y, LastVel.z)
endfunction
function GetMovingProjectile takes nothing returns Projectile
// only to use when you need to get the projectile data in a CodeMove function
return MovingProj
endfunction
function IsUnitProjectile takes unit u returns boolean
return IsUnitInGroup(u, Projectile.ProjGroup)
endfunction
endlibrary
JASS:
library ProjSys initializer InitMovSys requires GroupUtils, SmoothMovSys
//***************************************************************************************************************
//* Configuration globals
globals
private constant string MovDustFX = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"
private constant string MovSplashFX = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl"
endglobals
//***************************************************************************************************************
//* End configuration globals
globals
private constant rect TreeReg = Rect(0,0,1,1)
endglobals
private function KillTrees takes nothing returns nothing
local destructable d = GetEnumDestructable()
call KillDestructable(d)
set d = null
endfunction
private function RemoveTreesInRange takes real x, real y, real r returns nothing
call SetRect(TreeReg, x-r, y-r, x+r, y+r)
call EnumDestructablesInRect(TreeReg, BOOLEXPR_TRUE, function KillTrees)
endfunction
private function MoveKnockBackBasic takes nothing returns nothing
local Projectile P = GetMovingProjectile()
local real x = GetUnitX(P.p)
local real y = GetUnitY(P.p)
if GetUnitFlyHeight(P.p) <= 0. then
if not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then
call DestroyEffect(AddSpecialEffect(MovDustFX, x, y))
elseif not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) then
call DestroyEffect(AddSpecialEffect(MovSplashFX, x, y))
endif
if IsUnitType(P.p, UNIT_TYPE_GROUND) == true and IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then
call SetUnitPosition(P.p, x, y)
call P.destroy()
endif
endif
endfunction
private function MoveKnockBack takes nothing returns nothing
local Projectile P = GetMovingProjectile()
call MoveKnockBackBasic()
call RemoveTreesInRange(GetUnitX(P.p), GetUnitY(P.p), 100.)
endfunction
private function MoveMissile takes nothing returns nothing
local Projectile p = GetMovingProjectile()
call SetUnitFacing(p.p, Atan2(p.vv.y, p.vv.x) * bj_RADTODEG)
endfunction
private function InitMovSys takes nothing returns nothing
call Preload(MovDustFX)
call Preload(MovSplashFX)
endfunction
private function ParabolicA takes real h, real d returns real
// returns the coeficient A in the parabolic function
// h, d, and x are in the same units of distance in WC3, which is more comfortable to use :)
if d > 0. then
return -4 * h / (d * d)
else
return 0.
endif
endfunction
private function ParabolicB takes real h, real d returns real
// returns the coeficient B in the parabolic function
// h, d, and x are in the same units of distance in WC3, which is more comfortable to use :)
// NOTE: THIS VALUE IS THE TAN OF THE PROJECTILE ANGLE
if d > 0. then
return 4 * h / d
else
return 0.
endif
endfunction
//***********************************************************************************************
//* public usage functions
//***********************************************************************************************
function Atan3 takes real x1, real y1, real x2, real y2 returns real
local real a = Atan2(y2 - y1, x2 - x1)
if a >= 0. then
return a
else
return 2 * bj_PI + a
endif
endfunction
function Dist takes real x1, real y1, real x2, real y2 returns real
local real dx = x2 - x1
local real dy = y2 - y1
return SquareRoot( dx * dx + dy * dy )
endfunction
function GetParabolicAngle takes real h, real d returns real
return Atan(ParabolicB(h, d))
endfunction
function GetUnitsAngle takes unit a, unit b returns real
return Atan3(GetUnitX(a), GetUnitY(a), GetUnitX(b), GetUnitY(b))
endfunction
function GetUnitsDist takes unit a, unit b returns real
return Dist(GetUnitX(a), GetUnitY(a), GetUnitX(b), GetUnitY(b))
endfunction
function GetUnitProjPoint takes unit u, real angle, real dist returns location
local real x = GetUnitX(u) + dist * Cos(angle)
local real y = GetUnitY(u) + dist * Sin(angle)
return Location(x, y)
endfunction
//******************************************************************************
//* KnockBack Function:
//* - unit t: the unit that will become into a projectile
//* - real dist: the distance that the projectile will move
//* - real dir: the angle in the plane XY which indicates the movemente direction
//* - real height: the max height that the projectile will fly
//* - real speed: the initial projectile speed. If height = 0 then the projectile will change the speed until reach 0
//* - Code InitCode: the name of the function that will be evaluated when the projectile starts moving
//* - Code EndCode: the name of the function that will be evaluated when the projectile stops.
//* - boolean DestroyDestructables: Indicates if the knockbacked units will destroy nearby trees while they move
function KnockBack takes unit t, real dist, real dir, real height, real speed, code InitCode, code EndCode, boolean DestroyDestructables returns nothing
local real a = GetParabolicAngle(height, dist)
local real tm = dist / (speed * Cos(a))
local vector p1 = vector.create(GetUnitX(t), GetUnitY(t), GetUnitFlyHeight(t))
local vector p2 = vector.create(p1.x + dist * Cos(dir), p1.y + dist * Sin(dir), p1.z)
local vector vi
local vector vf
local Projectile P
if height > 0. then
set vi = vector.create(speed, dir, a)
set vf = vector.create(vi.x, vi.y, -vi.z)
else
set vi = vector.create(speed, dir, 0.)
set vf = vector.create(0., 0., 0.)
endif
set P = Projectile.createSpherical(t, p1, p2, vi, vf, tm, true, true)
call P.SetStartCode(InitCode)
if DestroyDestructables then
call P.SetMoveCode(function MoveKnockBack)
else
call P.SetMoveCode(function MoveKnockBackBasic)
endif
call P.SetEndCode(EndCode)
endfunction
//===========================================================================
// Null function; for use with KnockBack's InitCode and EndCode arguments
//===========================================================================
function Null takes nothing returns nothing
endfunction
endlibrary
JASS:
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//* [group] ENUM_GROUP As you might expect, this group is good for
//* when you need a group just for enumeration.
//* [boolexpr] BOOLEXPR_TRUE This is a true boolexpr, which is important
//* because a 'null' boolexpr in enumeration
//* calls results in a leak. Use this instead.
//* [boolexpr] BOOLEXPR_FALSE This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//* local group MyGroup = NewGroup()
//* call GroupRefresh(MyGroup)
//* call ReleaseGroup(MyGroup)
//* call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//* call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
//If you don't have xebasic in your map, this value will be used instead.
//This value corresponds to the max collision size of a unit in your map.
private constant real MAX_COLLISION_SIZE = 197.
//If you are insane and don't care about any of the protection involved in
//this library, but want this script to be really fast, set this to true.
private constant boolean LESS_SAFETY = false
endglobals
globals
//* Constants that are available to the user
group ENUM_GROUP = CreateGroup()
boolexpr BOOLEXPR_TRUE = null
boolexpr BOOLEXPR_FALSE = null
endglobals
globals
//* Hashtable for debug purposes
private hashtable ht = InitHashtable()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Arrays and counter for the group stack
private group array Groups
private integer Count = 0
//* Variables for use with the GroupUnitsInArea function
private real X = 0.
private real Y = 0.
private real R = 0.
private hashtable H = InitHashtable()
endglobals
private function HookDestroyGroup takes group g returns nothing
if g == ENUM_GROUP then
call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
endif
endfunction
debug hook DestroyGroup HookDestroyGroup
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
static if not LESS_SAFETY then
call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
endif
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer id = GetHandleId(g)
static if LESS_SAFETY then
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
else
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
return false
elseif LoadInteger(ht, 0, id) == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
call SaveInteger(ht, 0, id, 2)
endif
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
return true
endfunction
private function Filter takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction
private function HookDestroyBoolExpr takes boolexpr b returns nothing
local integer bid = GetHandleId(b)
if HaveSavedHandle(H, 0, bid) then
//Clear the saved boolexpr
call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
call RemoveSavedHandle(H, 0, bid)
endif
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
private constant function GetRadius takes real radius returns real
static if LIBRARY_xebasic then
return radius+XE_MAX_COLLISION_SIZE
else
return radius+MAX_COLLISION_SIZE
endif
endfunction
function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
local integer bid = 0
//Set variables to new values
set X = x
set Y = y
set R = radius
if filter == null then
//Adjusts for null boolexprs passed to the function
set filter = Condition(function Filter)
else
//Check for a saved boolexpr
set bid = GetHandleId(filter)
if HaveSavedHandle(H, 0, bid) then
//Set the filter to use to the saved one
set filter = LoadBooleanExprHandle(H, 0, bid)
else
//Create a new And() boolexpr for this filter
set filter = And(Condition(function Filter), filter)
call SaveBooleanExprHandle(H, 0, bid, filter)
endif
endif
//Enumerate, if they want to use the boolexpr, this lets them
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
//Set variables to new values
set X = x
set Y = y
set R = radius
//Enumerate
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
private function True takes nothing returns boolean
return true
endfunction
private function False takes nothing returns boolean
return false
endfunction
private function Init takes nothing returns nothing
set BOOLEXPR_TRUE = Condition(function True)
set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
Attachments
Last edited: