Name | Type | is_array | initial_value |
//TESH.scrollpos=240
//TESH.alwaysfold=0
scope WildHorde
/************************************************************************************/
/* */
/* Wild Horde v1.3 */
/* by: msongyyy */
/* */
/* HOW TO IMPORT: */
/* 1. Import all the triggers in Libraries and this Trigger */
/* 2. Import the 'Wild Horde Dummy' and 'Wild Horde' spell */
/* 3. Set ABIL_ID to the rawcode ID of Wild Horde spell */
/* 4. Set R_ID to the rawcode ID of Wild Horde Dummy unit */
/* 5. Change configurables to your liking */
/* */
/* ENJOY ^^ */
/* */
/* THANKS TO: */
/* 1. Cohadar for TT system and vJASS compiler */
/* 2. Bribe for SpellEffectEvent */
/* 3. Magtheridon96 for RegisterPlayerUnitEvent */
/* 4. Vexorian for BoundSentinel system, GroupUtils, and vJASS compiler */
/* 5. Kenny/Jesus4lyf for ListModule system */
/* 6. Rising_Dusk for TerrainPathability system */
/* 7. KRICZ for Knockback system */
/* 8. Everyone at hiveworkshop.com for their support and guidance */
/* */
/* */
/************************************************************************************/
globals
private constant integer ABIL_ID = 'A000' // Rawcode ID of Wild Horde ability
private constant integer R_ID = 'h000' // Rawcode ID of Wild Horde Dummy unit
private constant string FX = "Abilities\\Spells\\Orc\\FeralSpirit\\feralspiritdone.mdl"
endglobals
/************************************************************************************/
/* START OF CONFIGURABLES */
/************************************************************************************/
globals
private constant boolean KB_SM = true // Does knockback stop unit from moving? (Sets unit prop window to 0.00)
private constant real PERIOD = .025 // How often kodos move
private constant real TOUCH_AOE = 135.00 // Distance required for a target to be hit by a kodo
private constant real KODO_SPEED = 350. * PERIOD // Speed of Kodos
private constant real MAX_DIST = 500. // Max distance traveled by kodos
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
private constant weapontype WEAPON_TYPE = null
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
private constant integer KRED = 155
private constant integer KGREEN = 155
private constant integer KBLUE = 155
private constant integer TRANS = 100
endglobals
// Damage taken over knockback time
private constant function KB_DMG takes integer i returns real
return i * 5.00 + 5.00
endfunction
// Initial damage taken on knockback
private constant function INIT_DMG takes integer i returns real
return i * 20. + 10.
endfunction
// Duration of knockback
private constant function KB_DUR takes integer i returns real
return i * .3 + .7
endfunction
// Distance knockedback
private constant function KB_DIST takes integer i returns real
return i * 25.00 + 100.
endfunction
// Kodos summoned per second
private constant function KPS takes integer i returns integer
return i * 4
endfunction
// Duration of the spell
private constant function DURA takes integer i returns integer
return i * 2 + 4
endfunction
/************************************************************************************/
/* END OF CONFIGURABLES */
/************************************************************************************/
globals
KnockbackType HordeKb
private constant hashtable H = InitHashtable()
private constant timer TMR = CreateTimer()
private group g = CreateGroup()
private unit fog
endglobals
private struct data2 extends array
private unit caster
private unit rhino
private real ang
private real dist
private real x
private real y
private real kbDmg
private real initDmg
private real kbDur
private real kbDist
private player play
private static integer array prev
private static integer array next
private static integer array rn
private static integer ic = 0
static method Move takes nothing returns nothing
local thistype this = next[0]
loop
exitwhen this == 0
if .dist >= 400 then
call RemoveUnit(.rhino)
set .caster = null
set .rhino = null
set .play = null
set prev[next[this]] = prev[this]
set next[prev[this]] = next[this]
set rn[this] = rn[0]
set rn[0] = this
else
call SetUnitX(.rhino, GetUnitX(.rhino) + .x)
call SetUnitY(.rhino, GetUnitY(.rhino) + .y)
call GroupEnumUnitsInRange(g, GetUnitX(.rhino), GetUnitY(.rhino), TOUCH_AOE, null)
loop
set fog = FirstOfGroup(g)
if IsUnitEnemy(fog, .play) and GetUnitState(fog, UNIT_STATE_LIFE) > .405 then
set .dist = 400
call Knockback.create(.caster, fog, KB_DIST(GetUnitAbilityLevel(.caster, ABIL_ID)), KB_DUR(GetUnitAbilityLevel(.caster, ABIL_ID)), Atan2(GetUnitY(fog) - GetUnitY(.rhino), GetUnitX(fog) - GetUnitX(.rhino)) * bj_RADTODEG, HordeKb)
call SaveReal(H, GetHandleId(fog), 0, GetUnitPropWindow(fog))
endif
exitwhen fog == null or IsUnitEnemy(fog, .play)
call GroupRemoveUnit(g, fog)
endloop
set .dist = .dist + KODO_SPEED
endif
set this = next[this]
if next[0] == 0 then
call PauseTimer(TMR)
endif
endloop
endmethod
static method Start takes unit caster, unit rhino, real r returns nothing
local thistype this = rn[0]
if this == 0 then
set ic = ic + 1
set this = ic
else
set rn[0] = rn[this]
endif
set .caster = caster
set .rhino = rhino
set .play = GetOwningPlayer(caster)
set .ang = r
set .dist = 0.
set .x = KODO_SPEED * Cos(ang)
set .y = KODO_SPEED * Sin(ang)
set .kbDmg = KB_DMG(GetUnitAbilityLevel(.caster, ABIL_ID))
set .kbDur = KB_DUR(GetUnitAbilityLevel(.caster, ABIL_ID))
set .initDmg = INIT_DMG(GetUnitAbilityLevel(.caster, ABIL_ID))
set .kbDist = KB_DIST(GetUnitAbilityLevel(.caster, ABIL_ID))
set prev[this] = prev[0]
set next[this] = 0
set next[prev[0]] = this
set prev[0] = this
if prev[this] == 0 then
call TimerStart(TMR, PERIOD, true, function thistype.Move)
endif
endmethod
endstruct
//
private struct data extends array
private unit caster
private player play
private integer count
private static integer array prev
private static integer array next
private static integer array rn
private static integer ic = 0
static method Rhinos takes nothing returns boolean
local thistype this = TT_GetData()
local real rdist = GetRandomReal(250, 400)
local real rang = GetRandomReal(0, 360) * bj_DEGTORAD
local real x = GetUnitX(.caster) + rdist * Cos(rang)
local real y = GetUnitY(.caster) + rdist * Sin(rang)
local unit rhino
if .count == 0 then
set .caster = null
set .play = null
set rn[this] = rn[0]
set rn[0] = this
return true
else
set .count = .count - 1
set rang = GetRandomReal(0, 360)
set rhino = CreateUnit(.play, R_ID, x, y, rang * bj_RADTODEG)
call data2.Start(.caster, rhino, rang)
call SetUnitVertexColor(rhino, KRED, KGREEN, KBLUE, TRANS)
call SetUnitAnimationByIndex(rhino, 7)
set rhino = null
endif
return false
endmethod
static method Start takes nothing returns nothing
local thistype this = rn[0]
if this == 0 then
set ic = ic + 1
set this = ic
else
set rn[0] = rn[this]
endif
set .caster = GetTriggerUnit()
set .play = GetOwningPlayer(.caster)
set .count = (KPS(GetUnitAbilityLevel(.caster, ABIL_ID))) * DURA(GetUnitAbilityLevel(.caster, ABIL_ID))
call TT_StartEx(function thistype.Rhinos, this, 1./KPS(GetUnitAbilityLevel(.caster, ABIL_ID)))
endmethod
// Knockback Struct stuff
//
static method DamageLoop takes Knockback kb returns nothing
call UnitDamageTarget(kb.caster, kb.target, KB_DMG(GetUnitAbilityLevel(kb.caster, ABIL_ID)) * Knockback_TIMER_INTERVAL, false, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null)
endmethod
static method InitialDamage takes Knockback kb returns nothing
call UnitDamageTarget(kb.caster, kb.target, INIT_DMG(GetUnitAbilityLevel(kb.caster, ABIL_ID)), false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
call DestroyEffect(AddSpecialEffectTarget(FX, kb.target, "origin"))
if KB_SM then
call SetUnitPropWindow(kb.target, 0.00)
endif
endmethod
static method EndResetUnit takes Knockback kb returns nothing
call SetUnitPropWindow(kb.target, LoadReal(H, GetHandleId(kb.target), 0))
call FlushChildHashtable(H, GetHandleId(kb.target))
endmethod
//
// Init stuff
//
static method onInit takes nothing returns nothing
call RegisterSpellEffectEvent(ABIL_ID, function thistype.Start)
set HordeKb = KnockbackType.create()
set HordeKb.onLoopAction = DamageLoop
set HordeKb.onStartAction = InitialDamage
set HordeKb.onEndAction = EndResetUnit
endmethod
//
endstruct
endscope
//TESH.scrollpos=54
//TESH.alwaysfold=0
library Setup initializer init
globals
constant timer S_TMR = CreateTimer()
constant integer array CREEPS
private group g = CreateGroup()
private unit fog
endglobals
private function sCond takes nothing returns boolean
call GroupEnumUnitsOfPlayer(g, Player(0), null)
loop
set fog = FirstOfGroup(g)
exitwhen fog == null
if GetUnitAbilityLevel(fog, 'Aloc') == 0 then
call SetUnitState(fog, UNIT_STATE_LIFE, 99999999)
call SetUnitState(fog, UNIT_STATE_MANA, 99999999)
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl", fog, "origin"))
endif
call GroupRemoveUnit(g, fog)
endloop
return false
endfunction
private function rCond takes nothing returns boolean
local unit lvUnit = GetTriggerUnit()
if IsUnitType(lvUnit, UNIT_TYPE_HERO) then
call ReviveHero(lvUnit, 0, 0, true)
call PanCameraToTimed(0, 0, .5)
endif
set lvUnit = null
return false
endfunction
private function createCreeps takes nothing returns nothing
local integer i = 0
local integer i2 = GetRandomInt(3, 6)
local location lvLoc = GetRandomLocInRect(GetPlayableMapRect())
call GroupEnumUnitsOfPlayer(g, Player(12), null)
loop
set fog = FirstOfGroup(g)
exitwhen fog == null
if GetUnitState(fog, UNIT_STATE_LIFE) > 0 then
set i = i + 1
endif
call GroupRemoveUnit(g, fog)
endloop
if i < 50 then
loop
exitwhen i2 == 0 or i >= 50
call CreateUnitAtLoc(Player(12), CREEPS[GetRandomInt(0,5)], lvLoc, 0)
set i2 = i2 - 1
endloop
endif
call RemoveLocation(lvLoc)
set lvLoc = null
endfunction
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
local trigger t2 = CreateTrigger()
set CREEPS[0] = 'nanm'
set CREEPS[1] = 'nenc'
set CREEPS[2] = 'nftr'
set CREEPS[3] = 'nfrl'
set CREEPS[4] = 'nltc'
set CREEPS[5] = 'nslr'
call TimerStart(S_TMR, 5, true, function createCreeps)
call DisplayTimedTextToPlayer(Player(0), 0, 0, 99999999, "Wild Horde by msongyyy!")
call DisplayTimedTextToPlayer(Player(0), 0, 0, 99999999, "Enjoy!!!!")
call DisplayTimedTextToPlayer(Player(0), 0, 0, 99999999, "Press ESC to heal fully")
call TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC)
call TriggerAddCondition(t, function sCond)
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(t2, function rCond)
set t = null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
//============================================================================
// SpellEffectEvent
// - Version 1.1.0.0
//
// API
// ---
// RegisterSpellEffectEvent(integer abil, code onCast)
//
// Requires
// --------
// RegisterPlayerUnitEvent: hiveworkshop.com/forums/showthread.php?t=203338
//
// Optional
// --------
// Table: hiveworkshop.com/forums/showthread.php?t=188084
//
library SpellEffectEvent requires RegisterPlayerUnitEvent, optional Table
//============================================================================
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable ht = InitHashtable()
endif
static method onCast takes nothing returns nothing
static if LIBRARY_Table then
call TriggerEvaluate(.tb.trigger[GetSpellAbilityId()])
else
call TriggerEvaluate(LoadTriggerHandle(.ht, 0, GetSpellAbilityId()))
endif
endmethod
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
set .tb = Table.create()
endif
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onCast)
endmethod
endmodule
//============================================================================
private struct S extends array
implement M
endstruct
//============================================================================
function RegisterSpellEffectEvent takes integer abil, code onCast returns nothing
static if LIBRARY_Table then
if not S.tb.handle.has(abil) then
set S.tb.trigger[abil] = CreateTrigger()
endif
call TriggerAddCondition(S.tb.trigger[abil], Filter(onCast))
else
if not HaveSavedHandle(S.ht, 0, abil) then
call SaveTriggerHandle(S.ht, 0, abil, CreateTrigger())
endif
call TriggerAddCondition(LoadTriggerHandle(S.ht, 0, abil), Filter(onCast))
endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
/**************************************************************
*
* RegisterPlayerUnitEvent
* v5.1.0.1
* By Magtheridon96
*
* I would like to give a special thanks to Bribe, azlier
* and BBQ for improving this library. For modularity, it only
* supports player unit events.
*
* Functions passed to RegisterPlayerUnitEvent must either
* return a boolean (false) or nothing. (Which is a Pro)
*
* Warning:
* --------
*
* - Don't use TriggerSleepAction inside registered code.
* - Don't destroy a trigger unless you really know what you're doing.
*
* API:
* ----
*
* - function RegisterPlayerUnitEvent takes playerunitevent whichEvent, code whichFunction returns nothing
* - Registers code that will execute when an event fires.
* - function RegisterPlayerUnitEventForPlayer takes playerunitevent whichEvent, code whichFunction, player whichPlayer returns nothing
* - Registers code that will execute when an event fires for a certain player.
* - function GetPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
* - Returns the trigger corresponding to ALL functions of a playerunitevent.
*
**************************************************************/
library RegisterPlayerUnitEvent // Special Thanks to Bribe and azlier
globals
private trigger array t
endglobals
function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
local integer i = GetHandleId(p)
local integer k = 15
if t[i] == null then
set t[i] = CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t[i], Player(k), p, null)
exitwhen k == 0
set k = k - 1
endloop
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function RegisterPlayerUnitEventForPlayer takes playerunitevent p, code c, player pl returns nothing
local integer i = 16 * GetHandleId(p) + GetPlayerId(pl)
if t[i] == null then
set t[i] = CreateTrigger()
call TriggerRegisterPlayerUnitEvent(t[i], pl, p, null)
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function GetPlayerUnitEventTrigger takes playerunitevent p returns trigger
return t[GetHandleId(p)]
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
//==============================================================================
// TT -- TIMER TICKER SYSTEM BY COHADAR -- v4.1
//==============================================================================
//
// PURPOUSE OF TT:
// * Passing data to timers
// * Avoiding direct use of timer handles
//
// PROS:
// * It is easier than using attaching
// * It is optimized to use only one timer on default high frequency
// * GetData method is the same for all timer frequencies
//
// CONS:
// * You must remember to always return true from your function
// when you want to stop timer, even if it is of one-shot type.
// (otherwise it will leak)
//
// START FUNCTIONS:
// * TT_Start(userFunc, struct)
// * TT_StartEx(userFunc, struct, period)
// * TT_Once(userFunc, struct, timeout)
// * TT_StartTimerDialog(userFunc, struct, timeout) -> timerdialog
//
// * userFunc is a user function that takes nothing and returns boolean
// it will be periodically called by the system until it returns true.
//
// GET FUNCTIONS:
// * TT_GetData() -> struct
// * TT_GetTimerDialog() -> timerdialog
//
// * These functions can only be called from inside userFunc
// TT_GetData() will return struct passed to any of the start functions
// TT_GetTimerDialog() returns timerdialog created by TT_StartTimerDialog
//
// DETAILS:
// * On default frequency all user functions are stored in an array.
// Timer will call all those functions each period.
//
// * While user function returns false timer will continue to call it each period
// Once user function returns true it will be removed from system
//
// * TT is using smart timer preloading and simple hash
// When colliding timer handle is found that timer is simply discarded
//
// REQUIREMENTS:
// * NewGen v4c and above (there might be some problems with older NewGen's)
//
// HOW TO IMPORT:
// * Just create a trigger named TT
// * convert it to text and replace the whole trigger text with this one
//
//==============================================================================
library TT initializer Init
//==============================================================================
// Configuration
//==============================================================================
globals
// List of recommended periods for high-frequency timer:
// 0.04 = 25 calls per second
// 0.03125 = 32 calls per second
// 0.025 = 40 calls per second
// 0.02 = 50 calls per second
public constant real PERIOD = 0.03125
// how many low-frequency timers to preload
// system can safely extend beyond this limit
private constant integer PRELOAD = 32
endglobals
//==============================================================================
// End of Configuration
//==============================================================================
//==============================================================================
globals
// "frames per second" of high-frequency timer
public constant integer FPS = R2I(1.0/PERIOD)
// globals for passing data to userFunc
private integer Data
private timerdialog timerDialog
// One Timer to rule them all, One Timer to find them,
// One Timer to call them all and in the jass bind them
// In the land of warcraft where the desyncs lie.
private timer HF_Timer = CreateTimer()
private integer HF_Counter = 0
private trigger array HF_Triggz
private integer array HF_Dataz
// we can safely use dummy hashing here because timers are preloaded
private constant integer LF_HASH = 8191
private integer array LF_Dataz
private trigger array LF_Triggz
private timer array LF_Timerz
private timerdialog array LF_Dialogz
// recycling
private integer array LF_Indexz
private integer LF_Counter = PRELOAD
endglobals
//==============================================================================
// note how colliding timer handles are discarded
// so TT would work properly even after preload limit break
//==============================================================================
private function NewIndex takes nothing returns integer
local integer i
local timer t
if (LF_Counter==0) then
loop
debug call BJDebugMsg("WARNING: TT reached preloaded timer limit!")
set t = CreateTimer()
set i = GetHandleId(t)
set i = i - (i / LF_HASH) * LF_HASH // dummy modulo hash
if LF_Timerz[i] == null then
set LF_Timerz[i] = t
set LF_Triggz[i] = CreateTrigger()
return i
endif
endloop
endif
set LF_Counter = LF_Counter - 1
return LF_Indexz[LF_Counter]
endfunction
//==============================================================================
private function HF_Handler takes nothing returns nothing
local trigger swap
local integer i = HF_Counter
loop
exitwhen i<=0
set Data = HF_Dataz[i]
if TriggerEvaluate(HF_Triggz[i]) then
set swap = HF_Triggz[i]
call TriggerClearConditions(swap)
set HF_Triggz[i] = HF_Triggz[HF_Counter]
set HF_Triggz[HF_Counter] = swap
set HF_Dataz[i] = HF_Dataz[HF_Counter]
set HF_Counter = HF_Counter - 1
endif
set i = i - 1
endloop
// who can guess why am I not nulling swap here?
endfunction
//==============================================================================
private function LF_Handler takes nothing returns nothing
local integer i = GetHandleId(GetExpiredTimer())
set i = i - (i / LF_HASH) * LF_HASH // dummy modulo hash
set Data = LF_Dataz[i]
if TriggerEvaluate(LF_Triggz[i]) then
// recycle the trigger and timer
call TriggerClearConditions(LF_Triggz[i])
call PauseTimer(LF_Timerz[i])
set LF_Indexz[LF_Counter] = i
set LF_Counter = LF_Counter + 1
endif
endfunction
//==============================================================================
// Periodic timer that runs on TT_PERIOD
//==============================================================================
public function Start takes code userFunc, integer data returns nothing
debug if userFunc == null then
debug call BJDebugMsg("ERROR: TT_Start - null userFunc")
debug return
debug endif
set HF_Counter = HF_Counter + 1
if HF_Triggz[HF_Counter] == null then
set HF_Triggz[HF_Counter] = CreateTrigger()
endif
set HF_Dataz[HF_Counter] = data
call TriggerAddCondition(HF_Triggz[HF_Counter], Condition(userFunc))
endfunction
//==============================================================================
// Periodic timer with custom period
//==============================================================================
public function StartEx takes code userFunc, integer data, real period returns nothing
local integer i
debug if userFunc == null then
debug call BJDebugMsg("ERROR: TT_StartEx - null userFunc")
debug return
debug endif
set i = NewIndex()
call TriggerAddCondition(LF_Triggz[i], Condition(userFunc))
set LF_Dataz[i] = data
call TimerStart(LF_Timerz[i], period, true, function LF_Handler)
endfunction
//==============================================================================
// One shot timer, remember to return true in userFunc
//==============================================================================
public function Once takes code userFunc, integer data, real timeout returns nothing
local integer i
debug if userFunc == null then
debug call BJDebugMsg("ERROR: TT_Once - null userFunc")
debug return
debug endif
set i = NewIndex()
call TriggerAddCondition(LF_Triggz[i], Condition(userFunc))
set LF_Dataz[i] = data
call TimerStart(LF_Timerz[i], timeout, false, function LF_Handler)
endfunction
//==============================================================================
public function StartTimerDialog takes code userFunc, integer data, real timeout returns timerdialog
local integer i
debug if userFunc == null then
debug call BJDebugMsg("ERROR: TT_StartTimerDialog - null userFunc")
debug return null
debug endif
set i = NewIndex()
call TriggerAddCondition(LF_Triggz[i], Condition(userFunc))
set LF_Dataz[i] = data
call TimerStart(LF_Timerz[i], timeout, false, function LF_Handler)
set bj_lastCreatedTimerDialog = CreateTimerDialog(LF_Timerz[i])
set LF_Dialogz[i] = bj_lastCreatedTimerDialog
return bj_lastCreatedTimerDialog
endfunction
//==============================================================================
// Call this function only inside the userFunc
//==============================================================================
public function GetData takes nothing returns integer
return Data
endfunction
//==============================================================================
// Call this function only inside the userFunc
//==============================================================================
public function GetTimerDialog takes nothing returns timerdialog
local integer i = GetHandleId(GetExpiredTimer())
set i = i - (i / LF_HASH) * LF_HASH // dummy modulo hash
return LF_Dialogz[i]
endfunction
//==============================================================================
// Preload LF timers and start HF timer.
//==============================================================================
private function Init takes nothing returns nothing
local integer i
local timer t
local integer j = 0
loop
exitwhen j>=PRELOAD
set t = CreateTimer()
set i = GetHandleId(t)
set i = i - (i / LF_HASH) * LF_HASH // dummy modulo hash
if LF_Timerz[i] == null then
set LF_Timerz[i] = t
set LF_Triggz[i] = CreateTrigger()
set LF_Indexz[j] = i
set j = j + 1
endif
endloop
call TimerStart(HF_Timer, PERIOD, true, function HF_Handler)
endfunction
endlibrary
//==============================================================================
// END OF TIMER TICKER SYSTEM
//==============================================================================
//TESH.scrollpos=0
//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
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
//TESH.scrollpos=0
//TESH.alwaysfold=0
library ListModule
//===========================================================================
// Information:
//==============
//
// This library provides the List module, which allows you to easily create
// a linked list of all of the allocated instances of a struct-type. Iterating
// through a linked list is about 12% faster than iteratating through an array
// in JASS. There is no faster method to iterate through a list of structs than
// the method used by this module. Aside from the marginal speed gain, the best
// use of this library is to hide some ugly low-level code from your structs.
// Rather than manually building and maintaining a list of struct instances,
// just implement the List module, and your code will become much prettier.
//
//===========================================================================
// How to use the List module:
//=============================
//
// Using the List module is pretty simple. First, implement it in your
// struct (preferably at the top to avoid unnecessary TriggerEvaluate calls).
// In the struct's create method, you must call listAdd(). In the onDestroy
// method, you must also call listRemove(). An example is shown below:
/*
struct Example
implement List
static method create takes nothing returns Example
local Example this = allocate()
call listAdd() //This method adds the instance to the list.
return this
endmethod
method onDestroy takes nothing returns nothing
call listRemove() //This method removes the instance from the list.
endmethod
endstruct
*/
// The requirement to call listAdd() and listRemove() will be done away
// with once JassHelper supports module onDestroy and module onCreate, but
// for now, it is not too much of a burden.
//
// Once this is done, your struct will gain all of the methods detailed
// in the API section. Below is an example of how to iterate through the list
// of allocated structs of the implementing struct-type:
/*
function IterationExample takes nothing returns nothing
local Example e = Example.first
loop
exitwhen e == 0
//Do something with e here.
set e = e.next
endloop
//Use .last and .prev instead to iterate backwards.
endmethod
*/
//
//===========================================================================
// List module API:
//==================
//
// (readonly)(static) first -> thistype
// This member contains the first instance of thistype in the list.
//
// (readonly)(static) last -> thistype
// This member contains the last instance of thistype in the list.
//
// (readonly)(static) count -> integer
// This member contains the number of allocated structs of thistype.
//
// (readonly) next -> thistype
// This member contains the next instance of thistype in the list.
//
// (readonly) prev -> thistype
// This member contains the previous instance of thistype in the list.
//
// listAdd()
// This method adds this instance to the list of structs of thistype.
// This should be called on each instance after it is allocated (within
// the create method).
//
// listRemove()
// This method removes this instance from the list of structs of thistype.
// This should be called on each instance before it is destroyed (within
// the onDestroy method).
//
// (static) listDestroy()
// This method destroys all the structs of thistype within the list.
//
//===========================================================================
module List
private static boolean destroying = false
private boolean inlist = false
readonly static integer count = 0
readonly thistype next = 0
readonly thistype prev = 0
static method operator first takes nothing returns thistype
return thistype(0).next
endmethod
static method operator last takes nothing returns thistype
return thistype(0).prev
endmethod
method listRemove takes nothing returns nothing
if not inlist then
return
endif
set inlist = false
set prev.next = next
set next.prev = prev
set count = count - 1
endmethod
method listAdd takes nothing returns nothing
if inlist or destroying then
return
endif
set inlist = true
set last.next = this
set prev = last
set thistype(0).prev = this
set count = count + 1
endmethod
static method listDestroy takes nothing returns nothing
local thistype this = last
set destroying = true
loop
exitwhen this == 0
call destroy()
set this = prev
endloop
set destroying = false
endmethod
endmodule
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TerrainPathability initializer Init
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This script can be used to detect the type of pathing at a specific point.
//* It is valuable to do it this way because the IsTerrainPathable is very
//* counterintuitive and returns in odd ways and aren't always as you would
//* expect. This library, however, facilitates detecting those things reliably
//* and easily.
//*
//******************************************************************************
//*
//* > function IsTerrainDeepWater takes real x, real y returns boolean
//* > function IsTerrainShallowWater takes real x, real y returns boolean
//* > function IsTerrainLand takes real x, real y returns boolean
//* > function IsTerrainPlatform takes real x, real y returns boolean
//* > function IsTerrainWalkable takes real x, real y returns boolean
//*
//* These functions return true if the given point is of the type specified
//* in the function's name and false if it is not. For the IsTerrainWalkable
//* function, the MAX_RANGE constant below is the maximum deviation range from
//* the supplied coordinates that will still return true.
//*
//* The IsTerrainPlatform works for any preplaced walkable destructable. It will
//* return true over bridges, destructable ramps, elevators, and invisible
//* platforms. Walkable destructables created at runtime do not create the same
//* pathing hole as preplaced ones do, so this will return false for them. All
//* other functions except IsTerrainWalkable return false for platforms, because
//* the platform itself erases their pathing when the map is saved.
//*
//* After calling IsTerrainWalkable(x, y), the following two global variables
//* gain meaning. They return the X and Y coordinates of the nearest walkable
//* point to the specified coordinates. These will only deviate from the
//* IsTerrainWalkable function arguments if the function returned false.
//*
//* Variables that can be used from the library:
//* [real] TerrainPathability_X
//* [real] TerrainPathability_Y
//*
globals
private constant real MAX_RANGE = 10.
private constant integer DUMMY_ITEM_ID = 'wolg'
endglobals
globals
private item Item = null
private rect Find = null
private item array Hid
private integer HidMax = 0
public real X = 0.
public real Y = 0.
endglobals
function IsTerrainDeepWater takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
endfunction
function IsTerrainShallowWater takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
endfunction
function IsTerrainLand takes real x, real y returns boolean
return IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)
endfunction
function IsTerrainPlatform takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
endfunction
private function HideItem takes nothing returns nothing
if IsItemVisible(GetEnumItem()) then
set Hid[HidMax] = GetEnumItem()
call SetItemVisible(Hid[HidMax], false)
set HidMax = HidMax + 1
endif
endfunction
function IsTerrainWalkable takes real x, real y returns boolean
//Hide any items in the area to avoid conflicts with our item
call MoveRectTo(Find, x, y)
call EnumItemsInRect(Find ,null, function HideItem)
//Try to move the test item and get its coords
call SetItemPosition(Item, x, y) //Unhides the item
set X = GetItemX(Item)
set Y = GetItemY(Item)
static if LIBRARY_IsTerrainWalkable then
//This is for compatibility with the IsTerrainWalkable library
set IsTerrainWalkable_X = X
set IsTerrainWalkable_Y = Y
endif
call SetItemVisible(Item, false)//Hide it again
//Unhide any items hidden at the start
loop
exitwhen HidMax <= 0
set HidMax = HidMax - 1
call SetItemVisible(Hid[HidMax], true)
set Hid[HidMax] = null
endloop
//Return walkability
return (X-x)*(X-x)+(Y-y)*(Y-y) <= MAX_RANGE*MAX_RANGE and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
endfunction
private function Init takes nothing returns nothing
set Find = Rect(0., 0., 128., 128.)
set Item = CreateItem(DUMMY_ITEM_ID, 0, 0)
call SetItemVisible(Item, false)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
KNOCKBACK
VERSION 1.0
BY KRICZ
Credits:
Vexorian: vJass, BoundSentinel
Rising_Dusk: GroupUtils, TerrainPathability
grim001: ListModule
Knockback is a knockback system, which is used to push units away.
The System has much features such like detecting hits with destructables and other units.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
The syntax to call a new knockback on a unit is:
set myKnockback = Knockback.create(caster, target, distance, time, angle, kbType)
@caster: This is the unit where the knockback comes from.
@target: This is the unit which will be knocked back.
@distance: The distance the target will be knocked back.
@time: The time the unit requires until it reaches the distance
@angle: The angle of the knockback.
@kbType: The KnockbackType Struct which saves all the actions for several events (read more below).
This is a small example of a basic knockback:
set myKnockback = Knockback.create(GetTriggerUnit(), GetSpellTargetUnit(), 500, 2.5, GetUnitFacing(GetSpellTargetUnit()), kbType)
This would knockback the target of an spell 500 units away, which requires 2.5 seconds in the target unit's
facing angle.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
The KnockbackType Struct:
The KnockbackType Struct is a struct which stores all the actions a knockback can has.
This system features the following actions:
This function will be called, when a new knockback is started on an unit
private function interface onStart takes Knockback kb returns nothing
This function will be called whenever the units position will be updated
private function interface onLoop takes Knockback kb returns nothing
This function will be called whenever the knocked unit hits another unit
You can use the filterFunction function to filter out specified units
The system itself will filter out all units arround the target
private function interface onUnitHit takes Knockback kb, unit hit returns nothing
This is the filterFunction. You can use this to filter out specified units. The parameter
enum is the unit which will be filtered at the moment.
Use it like a normal FilterFunction which would return an boolean like:
return IsUnitType(enum) == UNIT_TYPE_HERO
private function interface filterFunction takes Knockback kb, unit enum returns boolean
This function will be called whenever the knocked unit hits a destructable
private function interface onDestructableHit takes Knockback kb, destructable hit returns nothing
This function will be called when the unit being knocked back dies. If it returns true,
the knockback will continue, else it will be stopped, calling the onEnd function below.
private function interface onTargetDeath takes Knockback kb returns boolean
This function will be called when a knockback ends
private function interface onEnd takes Knockback kb returns nothing
You don not need to declare a new KnockbackType whenever you create a new knockback.
Just create a global KnockbackType and assign its actions at map init and use this variable.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
Setup variables:
private constant real STANDARD_DEST_RADIUS / STANDARD_UNIT_RADIUS
This variables are used for gettings near units or destructables around the target.
You can change them while the unit is being knocked back using the aoeDest / aoeUnit variables
private constant real TIMER_INTERVAL
This is the interval, in which the position of units being knocked back are changed. The lower this value,
the smoother is the movement, but the higher this value, the better is the performance.
The standard value is 0.03125.
private constant boolean STOP_WHEN_IMPASSABLE
If this boolean is true, the knockback will instantly end, when a unit can not be moved on an impassable terrain.
For example cliffs, deep water or destructables. Please note, that the system will first run the onDestructableHit Action
before the unit receive its new position.
private constant USE_KNOCKBACK_EFFECTS
If this boolean is true, the system will add special effects depending on the terrain type it is currently being on.
EARTH_EFFECT and WATER_EFFECT are the effects being used, which are attached to KNOCKBACK_EFFECT_ATTACHPOINT.
If IGNORE_FLYING_UNITS is true, the system will not create these effects on flying units.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
User methods:
method destroy takes nothing returns nothing
This method (you can not see it in the code) destroys the current knockback and cleans up all variables.
Using this method will call the onEnd function.
method addSpecialEffect takes string fx, string attachPoint returns nothing
Adds or changes the effect attached to the target.
static method isUnitKnockedBack takes unit whichUnit returns boolean
This method returns true, if the given unit is being knocked back currently, else it returns false
method operator angle= takes real angle returns nothing
Changes the angle of the knockback.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
User variables:
real aoeDest / aoeUnit
These reals are used to get near units or destructables. You can
change these variables to every value you want while the unit is beeing knocked back.
integer array userData[3]
You can use this integer array to store some integer or other structs you may need in the onLoop function or whenever.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
Readonly variables:
These variables can not be changed. They are only for reading outside of the struct.
unit caster / target
Caster should be self-evident. Target is the unit beeing knocked back.
real timeOver
This variable saves the time the unit has been knocked back.
real currentSpeed
This variable is the current speed of the knockback.
real movedDistance
This variable saves the distance the has been knocked back.
real duration
This is the base duration used when the knockback was created.
real distance
This is the base distance used when the knockback was created.
real knockbackAngle
This variable saves the angle of the knockback.
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Knockback requires ListModule, GroupUtils, TerrainPathability, optional BoundSentinel
//Please check the Knockback Info Trigger for more information about this system.
globals
public constant real TIMER_INTERVAL = 0.03125
private constant real STANDARD_DEST_RADIUS = 100.00
private constant real STANDARD_UNIT_RADIUS = 75.00
private constant boolean STOP_WHEN_IMPASSABLE = false
private constant boolean USE_KNOCKBACK_EFFECTS = true
//will only be used, if USE_KNOCKBACK_EFFECTS is true
private constant string EARTH_EFFECT = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"
private constant string WATER_EFFECT = "Abilities\\Spells\\Undead\\AbsorbMana\\AbsorbManaBirthMissile.mdl"
private constant string KNOCKBACK_EFFECT_ATTACHPOINT = "origin"
private constant boolean IGNORE_FLYING_UNITS = true
endglobals
//The Action Respones
private function interface onStart takes Knockback kb returns nothing
private function interface onLoop takes Knockback kb returns nothing
private function interface onUnitHit takes Knockback kb, unit hit returns nothing
private function interface onDestructableHit takes Knockback kb, destructable hit returns nothing
private function interface onTargetDeath takes Knockback kb returns nothing
private function interface onEnd takes Knockback kb returns nothing
private function interface filterFunction takes Knockback kb, unit enum returns boolean
//The KnockbackType Struct
struct KnockbackType
onStart onStartAction = 0
onLoop onLoopAction = 0
onUnitHit onUnitHitAction = 0
onDestructableHit onDestructableHitAction = 0
onEnd onEndAction = 0
onTargetDeath onTargetDeathAction = 0
filterFunction filterFunc = 0
endstruct
//The Knockback Struct
struct Knockback
//public readonly variables
readonly unit caster = null
readonly unit target = null
readonly real timeOver = 0.00
readonly real currentSpeed = 0.00
readonly real movedDistance = 0.00
readonly real duration = 0.00
readonly real distance = 0.00
readonly real knockbackAngle = 0.00
//variables you can change while the unit is being knocked back
public real aoeUnit = STANDARD_UNIT_RADIUS
public real aoeDest = STANDARD_DEST_RADIUS
public integer array userData[3]
//private variables
private real deceleration = 0.00
private real cos = 0.00
private real sin = 0.00
private effect fx = null
private effect kbEffect = null
private integer kbEffectMode = 0
private boolean ranDeathAction = false
private boolean isGround = false
//the actions
private onStart onStartAction = 0
private onLoop onLoopAction = 0
private onUnitHit onUnitHitAction = 0
private onDestructableHit onDestructableHitAction = 0
private onEnd onEndAction = 0
private onTargetDeath onTargetDeathAction = 0
private filterFunction filterFunc = 0
//just some static variables to make the system faster/work
private static timer ticker = null
private static boolexpr unitFilter = null
private static boolexpr destFilter = null
private static code unitActions = null
private static code destActions = null
private static thistype temp = 0
private static rect destRect = Rect(0,0,1,1)
private static real tx = 0.00
private static real ty = 0.00
//ListModule
implement List
//saves the actions in the Knockback Struct (will be used in the create method)
private method assignActions takes KnockbackType kb returns nothing
set .onStartAction = kb.onStartAction
set .onLoopAction = kb.onLoopAction
set .onUnitHitAction = kb.onUnitHitAction
set .onDestructableHitAction = kb.onDestructableHitAction
set .onEndAction = kb.onEndAction
set .onTargetDeathAction = kb.onTargetDeathAction
set .filterFunc = kb.filterFunc
endmethod
//this is the filter for destructables around the target
private static method destFilterMethod takes nothing returns boolean
local real x = GetDestructableX(GetFilterDestructable())
local real y = GetDestructableY(GetFilterDestructable())
return (.tx-x)*(.tx-x) + (.ty-y)*(.ty-y) <= .temp.aoeDest * .temp.aoeDest
endmethod
//this method will run the onDestructableHit action for every destructable hit
private static method runDestActions takes nothing returns nothing
if .temp.onDestructableHitAction != 0 then
call .temp.onDestructableHitAction.evaluate(.temp, GetEnumDestructable())
endif
endmethod
//this method calls the user defined filter method
private static method filterMethod takes nothing returns boolean
if .temp.filterFunc != 0 then
return GetFilterUnit() != .temp.target and temp.filterFunc.evaluate(.temp, GetFilterUnit())
else
return GetFilterUnit() != .temp.target
endif
endmethod
//this method will run the onUnitHit action for every unit hit
private static method runUnitActions takes nothing returns nothing
if .temp.onUnitHitAction != 0 then
call .temp.onUnitHitAction.evaluate(.temp, GetEnumUnit())
endif
endmethod
//cleans up the struct and runs the onEnd actions
private method onDestroy takes nothing returns nothing
if .fx != null then
call DestroyEffect(.fx)
endif
static if USE_KNOCKBACK_EFFECTS then
call DestroyEffect(.kbEffect)
endif
if .onEndAction != 0 then
call .onEndAction.evaluate(this)
endif
call .listRemove()
endmethod
//this method will be called every TIMER_INTERVAL and update the units position as well as runs the onLoop action
private static method onExpire takes nothing returns nothing
local thistype this = .first
local real x
local real y
loop
exitwhen this == 0
//run the onLoop action
if .onLoopAction != 0 then
call .onLoopAction.evaluate(this)
endif
if IsUnitType(.target, UNIT_TYPE_DEAD) or GetUnitTypeId(.target) == 0 and not .ranDeathAction then
if .onTargetDeathAction != 0 then
call .onTargetDeathAction.evaluate(this)
endif
set .ranDeathAction = true
endif
//change the time which has been gone
set .timeOver = .timeOver + TIMER_INTERVAL
set .currentSpeed = .currentSpeed - .deceleration
set .movedDistance = .movedDistance + .currentSpeed
set x = GetUnitX(.target) + (.currentSpeed) * .cos
set y = GetUnitY(.target) + (.currentSpeed) * .sin
///check for destructables arround the target
set .tx = x
set .ty = y
call SetRect(.destRect, -.aoeDest, -.aoeDest, .aoeDest, .aoeDest)
call MoveRectTo(.destRect, x, y)
call EnumDestructablesInRect(.destRect, .destFilter, .destActions)
//check pathability and set new unit position if walkable
if IsTerrainWalkable(x, y) then
call SetUnitX(.target, x)
call SetUnitY(.target, y)
else
static if STOP_WHEN_UNPASSABLE then
call .destroy()
endif
endif
//change the knockback effect if the user want to use it
static if USE_KNOCKBACK_EFFECTS then
if .isGround or not IGNORE_FLYING_UNITS then
if .kbEffectMode == 2 and IsTerrainLand(x, y) then
call DestroyEffect(.kbEffect)
set .kbEffect = AddSpecialEffectTarget(EARTH_EFFECT, .target, KNOCKBACK_EFFECT_ATTACHPOINT)
elseif .kbEffectMode == 1 and IsTerrainShallowWater(x, y) then
call DestroyEffect(.kbEffect)
set .kbEffect = AddSpecialEffectTarget(WATER_EFFECT, .target, KNOCKBACK_EFFECT_ATTACHPOINT)
endif
endif
endif
//refresh group and get units arround target
call GroupRefresh(ENUM_GROUP)
set .temp = this
call GroupEnumUnitsInArea(ENUM_GROUP, x, y, .aoeUnit, .unitFilter)
call ForGroup(ENUM_GROUP, .unitActions)
//if the knockback ended because the target reached the end or the speed is smaller than 0
if .currentSpeed <= 0 or .movedDistance >= .distance then
call .destroy()
endif
//pause the timer if no knockback is active anymore
if .count < 1 then
call PauseTimer(.ticker)
endif
//get next instance
set this = .next
endloop
endmethod
//adds or changes the current effect attached to the target
public method addSpecialEffect takes string fx, string attachPoint returns nothing
if .fx != null then
call DestroyEffect(.fx)
endif
set .fx = AddSpecialEffectTarget(fx, .target, attachPoint)
endmethod
//changes the angle of the knockback
public method operator angle= takes real newAngle returns nothing
set .knockbackAngle = newAngle
set .cos = Cos(.knockbackAngle * bj_DEGTORAD)
set .sin = Sin(.knockbackAngle * bj_DEGTORAD)
endmethod
//use this method to check if a unit is being knocked back
static method isUnitKnockedBack takes unit whichUnit returns boolean
local thistype this = .first
if this == 0 then
return false
endif
loop
exitwhen this == 0
if .target == whichUnit then
return true
endif
set this = .next
endloop
return false
endmethod
//this method creates a new knockback
public static method create takes unit caster, unit target, real distance, real time, real angle, KnockbackType kbType returns thistype
local thistype this = thistype.allocate()
local real speed = 2 * distance / ((time / TIMER_INTERVAL) + 1)
local real deceleration = speed / (time / TIMER_INTERVAL)
local integer i = 0
//reset userData
loop
exitwhen i >= 2
set .userData[i] = 0
set i = i + 1
endloop
//set variables
set .caster = caster
set .target = target
set .knockbackAngle = angle
set .distance = distance
set .duration = time
set .currentSpeed = speed
set .deceleration = deceleration
set .knockbackAngle = angle
set .cos = Cos(.knockbackAngle * bj_DEGTORAD)
set .sin = Sin(.knockbackAngle * bj_DEGTORAD)
set .isGround = not IsUnitType(.target, UNIT_TYPE_FLYING)
//Adds the knockback effect when USE_KNOCKBACK_EFFECTS is set to true
static if USE_KNOCKBACK_EFFECTS then
if .isGround or not IGNORE_FLYING_UNITS then
if IsTerrainLand(GetUnitX(.target), GetUnitY(.target)) then
set .kbEffect = AddSpecialEffectTarget(EARTH_EFFECT, .target, KNOCKBACK_EFFECT_ATTACHPOINT)
set .kbEffectMode = 1
elseif IsTerrainShallowWater(GetUnitX(.target), GetUnitY(.target)) then
set .kbEffect = AddSpecialEffectTarget(WATER_EFFECT, .target, KNOCKBACK_EFFECT_ATTACHPOINT)
set .kbEffectMode = 2
endif
endif
endif
//add the event actions to the struct
call .assignActions(kbType)
//run the onStart action
if .onStartAction != 0 then
call .onStartAction.evaluate(this)
endif
//add the knockback to the List
call .listAdd()
//start the timer if the new instance is the only one
if .count == 1 then
call TimerStart(.ticker, TIMER_INTERVAL, true, function thistype.onExpire)
endif
return this
endmethod
//Initialization
private static method onInit takes nothing returns nothing
set .ticker = CreateTimer()
set .unitFilter = Condition(function thistype.filterMethod)
set .destFilter = Condition(function thistype.destFilterMethod)
set .unitActions = function thistype.runUnitActions
set .destActions = function thistype.runDestActions
endmethod
endstruct
endlibrary