Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
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
//TESH.scrollpos=27
//TESH.alwaysfold=0
library BoundSentinel initializer init
//*************************************************
//* BoundSentinel
//* -------------
//* Don't leave your units unsupervised, naughty
//* them may try to get out of the map bounds and
//* crash your game.
//*
//* To implement, just get a vJass compiler and
//* copy this library/trigger to your map.
//*
//*************************************************
//==================================================
globals
// High enough so the unit is no longer visible, low enough so the
// game doesn't crash...
//
// I think you need 0.0 or soemthing negative prior to patch 1.22
//
private constant real EXTRA = 500.0
endglobals
//=========================================================================================
globals
private real maxx
private real maxy
private real minx
private real miny
endglobals
//=======================================================================
private function dis takes nothing returns nothing
local unit u=GetTriggerUnit()
local real x=GetUnitX(u)
local real y=GetUnitY(u)
if(x>maxx) then
set x=maxx
elseif(x<minx) then
set x=minx
endif
if(y>maxy) then
set y=maxy
elseif(y<miny) then
set y=miny
endif
call SetUnitX(u,x)
call SetUnitY(u,y)
set u=null
endfunction
private function init takes nothing returns nothing
local trigger t=CreateTrigger()
local region r=CreateRegion()
local rect rc
set minx=GetCameraBoundMinX() - EXTRA
set miny=GetCameraBoundMinY() - EXTRA
set maxx=GetCameraBoundMaxX() + EXTRA
set maxy=GetCameraBoundMaxY() + EXTRA
set rc=Rect(minx,miny,maxx,maxy)
call RegionAddRect(r, rc)
call RemoveRect(rc)
call TriggerRegisterLeaveRegion(t,r, null)
call TriggerAddAction(t, function dis)
//this is not necessary but I'll do it anyway:
set t=null
set r=null
set rc=null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope Kamikaze initializer onInit // requires TimerUtils, BoundSentinel
//===================================================\\
// CONFIGURABLES \\
// You may edit the variables below \\
//===================================================\\
globals
private constant integer SPELL_ID = 'A000' // The raw code of the ability
private constant real TIMER_PERIOD = 0.03 // How fast the update timer is ran
private constant real CRASH_SPEED = 15 // How much the unit moves per TIMER_PERIOD
private constant real SPEED_INC = .5 // How much the unit speeds up per TIMER_PERIOD + CRASH_SPEED
private constant real REQUIRED_DIST = 300 // How far away the caster has to be to the target
private constant string EXPLOSION = "Objects\\Spawnmodels\\Other\\NeutralBuildingExplosion\\NeutralBuildingExplosion.mdl" // the boom effect.
private constant string DUST_FX = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl" // the dust that shows upon crash.
private constant string TRAIL_FX = "" // the effect on the copter (I chose none)
private constant real KB_SPEED = 10 // knockback speed.
private constant real KB_DEC = .2 // knockback speed slowdown factor. Making it too low will look wierd.
private constant real ARC = 0.4 // Crash arc
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_FIRE
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_PIERCE
endglobals
private constant function SPELL_DAMAGE takes integer level returns real
return 150.0*level
endfunction
private constant function SPELL_RANGE takes integer level returns real
return 600.0 // I like the range to be the same.
endfunction
private constant function Parabola takes real d, real h, real x returns real // moyack/Spec
return ( 4 * h / d ) * ( d - x ) * ( x / d )
endfunction
//===================================================\\
// DO NOT EDIT BELOW THIS LINE \\
// \\
//===================================================\\
native UnitAlive takes unit id returns boolean
private keyword kb
private keyword data
globals
private kb array kbArray
endglobals
private struct kb
static integer count = 0
static timer timer = CreateTimer()
static player p
static data d
unit u
real face
real x
real y
real sin
real cos
real speed = KB_SPEED
static method enum takes nothing returns boolean
local unit u = GetFilterUnit()
if (UnitAlive(u) and IsUnitEnemy(u, kb.p)) then
call UnitDamageTarget(kb.d.copter, u, kb.d.dmg, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
if (UnitAlive(u)) then
call kb.create(u, kb.d.x, kb.d.y)
endif
endif
set u=null
return false
endmethod
static method callback takes nothing returns nothing
local integer i = 1
local kb this
local real x
local real y
loop
exitwhen i > kb.count
set this = kbArray[i]
set x = this.x - this.speed * this.cos
set y = this.y - this.speed * this.sin
set this.x = x
set this.y = y
set this.speed = this.speed-KB_DEC
if (this.speed <= 0) then
set kb.count = kb.count-1
set kbArray[i] = kbArray[kb.count]
call this.destroy()
if kb.count == 0 then
call PauseTimer(kb.timer)
endif
endif
call SetUnitX(this.u, x)
call SetUnitY(this.u, y)
set i = i + 1
endloop
endmethod
static method create takes unit u, real centerX, real centerY returns kb
local kb this = kb.allocate()
set this.x = GetUnitX(u)
set this.y = GetUnitY(u)
set this.u = u
set this.face = bj_RADTODEG*Atan2(this.y-centerX,this.x-centerY)
set this.sin = Sin(face*bj_DEGTORAD)
set this.cos = Cos(face*bj_DEGTORAD)
set kb.count = kb.count + 1
set kbArray[kb.count] = this
if (kb.count == 1) then
call TimerStart(kb.timer, TIMER_PERIOD, true, function kb.callback)
endif
return this
endmethod
endstruct
private struct data
unit copter
real facing
real x
real y
real sin
real cos
real cx
real cy
real dmg
real height
real range
real speed = CRASH_SPEED
real dist
real d2
boolean b = false
boolean b2 = true
static group GLOBAL = CreateGroup()
static method callback takes nothing returns nothing
local data d = GetTimerData(GetExpiredTimer())
local real x = d.cx + d.speed * d.cos
local real y = d.cy + d.speed * d.sin
local integer i = 0
local real x2
local real y2
// Increase speed and set new position
set d.speed = d.speed + SPEED_INC
set d.cx = x
set d.cy = y
call SetUnitX(d.copter, x)
call SetUnitY(d.copter, y)
// Create FX/Height
call DestroyEffect(AddSpecialEffectTarget(TRAIL_FX, d.copter, "chest"))
set d.dist = d.dist-d.speed
call SetUnitFlyHeight(d.copter, Parabola(d.d2+d.height, d.d2 * ARC, d.dist), 0)
if (d.b2) then // Checks it once instead of every timer interval
set d.b = IsUnitInRangeXY(d.copter, d.x, d.y, 100)
endif
// Start crashing stuff
if (d.b and d.b2) then
call KillUnit(d.copter)
set kb.p = GetOwningPlayer(d.copter)
call d.destroy()
call ReleaseTimer(GetExpiredTimer())
call DestroyEffect(AddSpecialEffect(EXPLOSION, x, y))
// Dust Circle
loop
exitwhen i >= 360
set x2 = x + 200 * Cos(i * bj_DEGTORAD)
set y2 = y + 200 * Sin(i * bj_DEGTORAD)
call DestroyEffect(AddSpecialEffect(DUST_FX, x2, y2))
set i = i + 90
endloop
set kb.d = d
call GroupEnumUnitsInRange(data.GLOBAL, x, y, d.range, Filter(function kb.enum)) // Knockback/Dmg units
call GroupClear(data.GLOBAL) // Stops possible bugs from occuring.
set d.b2 = false
endif
endmethod
static method create takes nothing returns data
local data d = data.allocate()
local real dx
local real dy
local integer level
set d.copter = GetTriggerUnit()
set level = GetUnitAbilityLevel(d.copter, SPELL_ID)
set d.cx = GetUnitX(d.copter)
set d.cy = GetUnitY(d.copter)
set d.x = GetSpellTargetX()
set d.y = GetSpellTargetY()
set d.facing = bj_RADTODEG * Atan2(d.y - d.cy, d.x - d.cx)
set d.sin = Sin(d.facing * bj_DEGTORAD)
set d.cos = Cos(d.facing * bj_DEGTORAD)
set d.dmg = SPELL_DAMAGE(level)
set dx = d.x - d.cx
set dy = d.y - d.cy
set d.dist = SquareRoot(dx * dx + dy * dy)
set d.d2 = d.dist
set d.height = GetUnitFlyHeight(d.copter)
set d.range = SPELL_RANGE(level)
set d.dmg = SPELL_DAMAGE(level)
call UnitAddAbility(d.copter, 'Amrf')
call UnitRemoveAbility(d.copter, 'Amrf')
return d
endmethod
endstruct
private function Actions takes nothing returns boolean
local timer t
if (GetSpellAbilityId() == SPELL_ID) then
set t = NewTimer()
call SetTimerData(t, data.create())
call TimerStart(t, TIMER_PERIOD, true, function data.callback)
endif
return false
endfunction
private function onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function Actions))
endfunction
endscope