//TESH.scrollpos=0
//TESH.alwaysfold=0
function Print takes string s returns nothing
call DisplayTextToForce(GetPlayersAll(), s)
endfunction
Name | Type | is_array | initial_value |
//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 TimedHandles uses optional TimerUtils
/**************************************************************
*
* v1.0.5 by TriggerHappy
* ----------------------
*
* Use this to destroy a handle after X amount seconds.
*
* It's useful for things like effects where you may
* want it to be temporary, but not have to worry
* about the cleaning memory leak. By default it supports
* effects, lightning, weathereffect, items, ubersplats, and units.
*
* If you want to add your own handle types copy a textmacro line
* at the bottom and add whichever handle you want along with it's destructor.
*
* Example: //! runtextmacro TIMEDHANDLES("handle", "DestroyHandle")
*
* Installation
----------------------
* 1. Copy this script and over to your map inside a blank trigger.
* 2. If you want more efficiency copy TimerUtils over as well.
*
* API
* ----------------------
* call DestroyEffectTimed(AddSpecialEffect("effect.mdx", 0, 0), 5)
* call DestroyLightningTimed(AddLightning("CLPB", true, 0, 0, 100, 100), 5)
*
* Credits to Vexorian for TimerUtils and his help on the script.
*
**************************************************************/
globals
// If you don't want a timer to be ran each instance
// set this to true.
private constant boolean SINGLE_TIMER = true
// If you chose a single timer then this will be the speed
// at which the timer will update
private constant real UPDATE_PERIOD = 0.05
endglobals
// here you may add or remove handle types
//! runtextmacro TIMEDHANDLES("effect", "DestroyEffect")
//! runtextmacro TIMEDHANDLES("lightning", "DestroyLightning")
//! runtextmacro TIMEDHANDLES("weathereffect", "RemoveWeatherEffect")
//! runtextmacro TIMEDHANDLES("item", "RemoveItem")
//! runtextmacro TIMEDHANDLES("unit", "RemoveUnit")
//! runtextmacro TIMEDHANDLES("ubersplat", "DestroyUbersplat")
// Do not edit below this line
//! textmacro TIMEDHANDLES takes HANDLE,DESTROY
struct $HANDLE$Timed
$HANDLE$ $HANDLE$_var
static integer index = -1
static thistype array instance
static real REAL=UPDATE_PERIOD
static if SINGLE_TIMER then
static timer timer = CreateTimer()
real duration
real elapsed = 0
else static if not LIBRARY.TimerUtils then
static hashtable table = InitHashtable()
endif
method destroy takes nothing returns nothing
call $DESTROY$(this.$HANDLE$_var)
set this.$HANDLE$_var = null
static if SINGLE_TIMER then
set this.elapsed = 0
endif
call this.deallocate()
endmethod
private static method remove takes nothing returns nothing
static if SINGLE_TIMER then
local integer i = 0
local thistype this
loop
exitwhen i > thistype.index
set this = instance[i]
set this.elapsed = this.elapsed + UPDATE_PERIOD
if (this.elapsed >= this.duration) then
set instance[i] = instance[index]
set i = i - 1
set index = index - 1
call this.destroy()
if (index == -1) then
call PauseTimer(thistype.timer)
endif
endif
set i = i + 1
endloop
else
local timer t = GetExpiredTimer()
static if LIBRARY.TimerUtils then
local $HANDLE$Timed this = GetTimerData(t)
call ReleaseTimer(t)
call this.destroy()
else
local $HANDLE$Timed this = LoadInteger(table, 0, GetHandleId(t))
call DestroyTimer(t)
set t = null
call this.destroy()
endif
endif
endmethod
static method create takes $HANDLE$ h, real timeout returns $HANDLE$Timed
local $HANDLE$Timed this = $HANDLE$Timed.allocate()
static if SINGLE_TIMER then
set index = index + 1
set instance[index] = this
if (index == 0) then
call TimerStart(thistype.timer, UPDATE_PERIOD, true, function thistype.remove)
endif
set this.duration = timeout
else
static if LIBRARY.TimerUtils then
call TimerStart(NewTimerEx(this), timeout, false, function $HANDLE$timed.remove)
else
local timer t = CreateTimer()
call SaveInteger(thistype.table, 0, GetHandleId(t), this)
call TimerStart(t, timeout, false, function $HANDLE$Timed.remove)
set t = null
endif
endif
set this.$HANDLE$_var = h
return this
endmethod
endstruct
function $DESTROY$Timed takes $HANDLE$ h, real duration returns $HANDLE$Timed
return $HANDLE$Timed.create(h, duration)
endfunction
//! endtextmacro
endlibrary
//TESH.scrollpos=30
//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
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 3.1.1.0
One map, one hashtable. Welcome to NewTable 3.1
This library was originally called NewTable so it didn't conflict with
the API of Table by Vexorian. However, the damage is done and it's too
late to change the library name now. To help with damage control, I
have provided an extension library called TableBC, which bridges all
the functionality of Vexorian's Table except for 2-D string arrays &
the ".flush(integer)" method. I use ".flush()" to flush a child hash-
table, because I wanted the API in NewTable to reflect the API of real
hashtables (I thought this would be more intuitive).
API
------------
struct Table
| static method create takes nothing returns Table
| create a new Table
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush all stored values inside of it
|
| method remove takes integer key returns nothing
| remove the value at index "key"
|
| method operator []= takes integer key, $TYPE$ value returns nothing
| assign "value" to index "key"
|
| method operator [] takes integer key returns $TYPE$
| load the value at index "key"
|
| method has takes integer key returns boolean
| whether or not the key was assigned
|
----------------
struct TableArray
| static method operator [] takes integer array_size returns TableArray
| create a new array of Tables of size "array_size"
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush and destroy it
|
| method operator size takes nothing returns integer
| returns the size of the TableArray
|
| method operator [] takes integer key returns Table
| returns a Table accessible exclusively to index "key"
*/
globals
private integer less = 0 //Index generation for TableArrays (below 0).
private integer more = 8190 //Index generation for Tables.
//Configure it if you use more than 8190 "key" variables in your map (this will never happen though).
private hashtable ht = InitHashtable()
private key sizeK
private key listK
endglobals
private struct dex extends array
static method operator size takes nothing returns Table
return sizeK
endmethod
static method operator list takes nothing returns Table
return listK
endmethod
endstruct
private struct handles extends array
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private struct agents extends array
method operator []= takes integer key, agent value returns nothing
call SaveAgentHandle(ht, this, key, value)
endmethod
endstruct
//! textmacro NEW_ARRAY_BASIC takes SUPER, FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSaved$SUPER$(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSaved$SUPER$(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//! textmacro NEW_ARRAY takes FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$Handle(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$Handle(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//Run these textmacros to include the entire hashtable API as wrappers.
//Don't be intimidated by the number of macros - Vexorian's map optimizer is
//supposed to kill functions which inline (all of these functions inline).
//! runtextmacro NEW_ARRAY_BASIC("Real", "Real", "real")
//! runtextmacro NEW_ARRAY_BASIC("Boolean", "Boolean", "boolean")
//! runtextmacro NEW_ARRAY_BASIC("String", "Str", "string")
//New textmacro to allow table.integer[] syntax for compatibility with textmacros that might desire it.
//! runtextmacro NEW_ARRAY_BASIC("Integer", "Integer", "integer")
//! runtextmacro NEW_ARRAY("Player", "player")
//! runtextmacro NEW_ARRAY("Widget", "widget")
//! runtextmacro NEW_ARRAY("Destructable", "destructable")
//! runtextmacro NEW_ARRAY("Item", "item")
//! runtextmacro NEW_ARRAY("Unit", "unit")
//! runtextmacro NEW_ARRAY("Ability", "ability")
//! runtextmacro NEW_ARRAY("Timer", "timer")
//! runtextmacro NEW_ARRAY("Trigger", "trigger")
//! runtextmacro NEW_ARRAY("TriggerCondition", "triggercondition")
//! runtextmacro NEW_ARRAY("TriggerAction", "triggeraction")
//! runtextmacro NEW_ARRAY("TriggerEvent", "event")
//! runtextmacro NEW_ARRAY("Force", "force")
//! runtextmacro NEW_ARRAY("Group", "group")
//! runtextmacro NEW_ARRAY("Location", "location")
//! runtextmacro NEW_ARRAY("Rect", "rect")
//! runtextmacro NEW_ARRAY("BooleanExpr", "boolexpr")
//! runtextmacro NEW_ARRAY("Sound", "sound")
//! runtextmacro NEW_ARRAY("Effect", "effect")
//! runtextmacro NEW_ARRAY("UnitPool", "unitpool")
//! runtextmacro NEW_ARRAY("ItemPool", "itempool")
//! runtextmacro NEW_ARRAY("Quest", "quest")
//! runtextmacro NEW_ARRAY("QuestItem", "questitem")
//! runtextmacro NEW_ARRAY("DefeatCondition", "defeatcondition")
//! runtextmacro NEW_ARRAY("TimerDialog", "timerdialog")
//! runtextmacro NEW_ARRAY("Leaderboard", "leaderboard")
//! runtextmacro NEW_ARRAY("Multiboard", "multiboard")
//! runtextmacro NEW_ARRAY("MultiboardItem", "multiboarditem")
//! runtextmacro NEW_ARRAY("Trackable", "trackable")
//! runtextmacro NEW_ARRAY("Dialog", "dialog")
//! runtextmacro NEW_ARRAY("Button", "button")
//! runtextmacro NEW_ARRAY("TextTag", "texttag")
//! runtextmacro NEW_ARRAY("Lightning", "lightning")
//! runtextmacro NEW_ARRAY("Image", "image")
//! runtextmacro NEW_ARRAY("Ubersplat", "ubersplat")
//! runtextmacro NEW_ARRAY("Region", "region")
//! runtextmacro NEW_ARRAY("FogState", "fogstate")
//! runtextmacro NEW_ARRAY("FogModifier", "fogmodifier")
//! runtextmacro NEW_ARRAY("Hashtable", "hashtable")
struct Table extends array
// Implement modules for intuitive syntax (tb.handle; tb.unit; etc.)
implement realm
implement integerm
implement booleanm
implement stringm
implement playerm
implement widgetm
implement destructablem
implement itemm
implement unitm
implement abilitym
implement timerm
implement triggerm
implement triggerconditionm
implement triggeractionm
implement eventm
implement forcem
implement groupm
implement locationm
implement rectm
implement boolexprm
implement soundm
implement effectm
implement unitpoolm
implement itempoolm
implement questm
implement questitemm
implement defeatconditionm
implement timerdialogm
implement leaderboardm
implement multiboardm
implement multiboarditemm
implement trackablem
implement dialogm
implement buttonm
implement texttagm
implement lightningm
implement imagem
implement ubersplatm
implement regionm
implement fogstatem
implement fogmodifierm
implement hashtablem
method operator handle takes nothing returns handles
return this
endmethod
method operator agent takes nothing returns agents
return this
endmethod
//set this = tb[GetSpellAbilityId()]
method operator [] takes integer key returns Table
return LoadInteger(ht, this, key) //return this.integer[key]
endmethod
//set tb[389034] = 8192
method operator []= takes integer key, Table tb returns nothing
call SaveInteger(ht, this, key, tb) //set this.integer[key] = tb
endmethod
//set b = tb.has(2493223)
method has takes integer key returns boolean
return HaveSavedInteger(ht, this, key) //return this.integer.has(key)
endmethod
//call tb.remove(294080)
method remove takes integer key returns nothing
call RemoveSavedInteger(ht, this, key) //call this.integer.remove(key)
endmethod
//Remove all data from a Table instance
method flush takes nothing returns nothing
call FlushChildHashtable(ht, this)
endmethod
//local Table tb = Table.create()
static method create takes nothing returns Table
local Table this = dex.list[0]
if this == 0 then
set this = more + 1
set more = this
else
set dex.list[0] = dex.list[this]
call dex.list.remove(this) //Clear hashed memory
endif
debug set dex.list[this] = -1
return this
endmethod
// Removes all data from a Table instance and recycles its index.
//
// call tb.destroy()
//
method destroy takes nothing returns nothing
debug if dex.list[this] != -1 then
debug call BJDebugMsg("Table Error: Tried to double-free instance: " + I2S(this))
debug return
debug endif
call this.flush()
set dex.list[this] = dex.list[0]
set dex.list[0] = this
endmethod
//! runtextmacro optional TABLE_BC_METHODS()
endstruct
//! runtextmacro optional TABLE_BC_STRUCTS()
struct TableArray extends array
//Returns a new TableArray to do your bidding. Simply use:
//
// local TableArray ta = TableArray[array_size]
//
static method operator [] takes integer array_size returns TableArray
local Table tb = dex.size[array_size] //Get the unique recycle list for this array size
local TableArray this = tb[0] //The last-destroyed TableArray that had this array size
debug if array_size <= 0 then
debug call BJDebugMsg("TypeError: Invalid specified TableArray size: " + I2S(array_size))
debug return 0
debug endif
if this == 0 then
set this = less - array_size
set less = this
else
set tb[0] = tb[this] //Set the last destroyed to the last-last destroyed
call tb.remove(this) //Clear hashed memory
endif
set dex.size[this] = array_size //This remembers the array size
return this
endmethod
//Returns the size of the TableArray
method operator size takes nothing returns integer
return dex.size[this]
endmethod
//This magic method enables two-dimensional[array][syntax] for Tables,
//similar to the two-dimensional utility provided by hashtables them-
//selves.
//
//ta[integer a].unit[integer b] = unit u
//ta[integer a][integer c] = integer d
//
//Inline-friendly when not running in debug mode
//
method operator [] takes integer key returns Table
static if DEBUG_MODE then
local integer i = this.size
if i == 0 then
call BJDebugMsg("IndexError: Tried to get key from invalid TableArray instance: " + I2S(this))
return 0
elseif key < 0 or key >= i then
call BJDebugMsg("IndexError: Tried to get key [" + I2S(key) + "] from outside TableArray bounds: " + I2S(i))
return 0
endif
endif
return this + key
endmethod
//Destroys a TableArray without flushing it; I assume you call .flush()
//if you want it flushed too. This is a public method so that you don't
//have to loop through all TableArray indices to flush them if you don't
//need to (ie. if you were flushing all child-keys as you used them).
//
method destroy takes nothing returns nothing
local Table tb = dex.size[this.size]
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to destroy an invalid TableArray: " + I2S(this))
debug return
debug endif
if tb == 0 then
//Create a Table to index recycled instances with their array size
set tb = Table.create()
set dex.size[this.size] = tb
endif
call dex.size.remove(this) //Clear the array size from hash memory
set tb[this] = tb[0]
set tb[0] = this
endmethod
private static Table tempTable
private static integer tempEnd
//Avoids hitting the op limit
private static method clean takes nothing returns nothing
local Table tb = .tempTable
local integer end = tb + 0x1000
if end < .tempEnd then
set .tempTable = end
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
else
set end = .tempEnd
endif
loop
call tb.flush()
set tb = tb + 1
exitwhen tb == end
endloop
endmethod
//Flushes the TableArray and also destroys it. Doesn't get any more
//similar to the FlushParentHashtable native than this.
//
method flush takes nothing returns nothing
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to flush an invalid TableArray instance: " + I2S(this))
debug return
debug endif
set .tempTable = this
set .tempEnd = this + this.size
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
call this.destroy()
endmethod
endstruct
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
library Alloc /* v1.1.0.1
*************************************************************************************
*
* */uses/*
*
* */ ErrorMessage /* hiveworkshop.com/forums/submissions-414/snippet-error-message-239210/
*
************************************************************************************
*
* module Alloc
*
* static method allocate takes nothing returns thistype
* method deallocate takes nothing returns nothing
*
* debug static method calculateMemoryUsage takes nothing returns integer
* debug static method getAllocatedMemoryAsString takes nothing returns string
*
************************************************************************************/
module Alloc
private static integer array recycler
static method allocate takes nothing returns thistype
local thistype this = recycler[0]
debug call ThrowError(this == 0, "Alloc", "allocate", "thistype", 0, "Overflow.")
set recycler[0] = recycler[this]
debug set recycler[this] = -1
return this
endmethod
method deallocate takes nothing returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "deallocate", "thistype", this, "Attempted To Deallocate Null Instance.")
set recycler[this] = recycler[0]
set recycler[0] = this
endmethod
private static method onInit takes nothing returns nothing
local integer i = 0
set recycler[8191] = 0 //so that the array doesn't reallocate over and over again
loop
set recycler[i] = i + 1
exitwhen i == 8190
set i = i + 1
endloop
endmethod
static if DEBUG_MODE then
static method calculateMemoryUsage takes nothing returns integer
local integer start = 1
local integer end = 8191
local integer count = 0
loop
exitwhen start > end
if (start + 500 > end) then
set count = count + checkRegion(start, end)
set start = end + 1
else
set count = checkRegion(start, start + 500)
set start = start + 501
endif
endloop
return count
endmethod
private static method checkRegion takes integer start, integer end returns integer
local integer count = 0
loop
exitwhen start > end
if (recycler[start] == -1) then
set count = count + 1
endif
set start = start + 1
endloop
return count
endmethod
static method getAllocatedMemoryAsString takes nothing returns string
local integer start = 1
local integer end = 8191
local string memory = null
loop
exitwhen start > end
if (start + 500 > end) then
if (memory != null) then
set memory = memory + ", "
endif
set memory = memory + checkRegion2(start, end)
set start = end + 1
else
if (memory != null) then
set memory = memory + ", "
endif
set memory = memory + checkRegion2(start, start + 500)
set start = start + 501
endif
endloop
return memory
endmethod
private static method checkRegion2 takes integer start, integer end returns string
local string memory = null
loop
exitwhen start > end
if (recycler[start] == -1) then
if (memory == null) then
set memory = I2S(start)
else
set memory = memory + ", " + I2S(start)
endif
endif
set start = start + 1
endloop
return memory
endmethod
endif
endmodule
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library SharedList /* v1.0.0.2
************************************************************************************
*
* */uses/*
*
* */ ErrorMessage /* hiveworkshop.com/forums/submissions-414/snippet-error-message-239210/
*
************************************************************************************
*
* module SharedList
*
* Description
* -------------------------
*
* NA
*
* Fields
* -------------------------
*
* readonly static integer sentinel
*
* readonly thistype list
*
* readonly thistype first
* readonly thistype last
*
* readonly thistype next
* readonly thistype prev
*
* Methods
* -------------------------
*
* static method create takes nothing returns thistype
* method destroy takes nothing returns nothing
* - May only destroy lists
*
* method push takes nothing returns thistype
* method enqueue takes nothing returns thistype
*
* method pop takes nothing returns nothing
* method dequeue takes nothing returns nothing
*
* method remove takes nothing returns nothing
*
* method clear takes nothing returns nothing
*
* debug static method calculateMemoryUsage takes nothing returns integer
* debug static method getAllocatedMemoryAsString takes nothing returns string
*
************************************************************************************/
module SharedList
private static thistype instanceCount = 0
debug private boolean isNode
debug private boolean isCollection
private thistype _list
method operator list takes nothing returns thistype
debug call ThrowError(this == 0, "SharedList", "list", "thistype", this, "Attempted To Read Null Node.")
debug call ThrowError(isCollection, "SharedList", "next", "thistype", this, "Attempted To Read List, Expecting Node.")
debug call ThrowError(not isNode, "SharedList", "list", "thistype", this, "Attempted To Read Invalid Node.")
return _list
endmethod
private thistype _next
method operator next takes nothing returns thistype
debug call ThrowError(this == 0, "SharedList", "next", "thistype", this, "Attempted To Go Out Of Bounds.")
debug call ThrowError(isCollection, "SharedList", "next", "thistype", this, "Attempted To Read List, Expecting Node.")
debug call ThrowError(not isNode, "SharedList", "next", "thistype", this, "Attempted To Read Invalid Node.")
return _next
endmethod
private thistype _prev
method operator prev takes nothing returns thistype
debug call ThrowError(this == 0, "SharedList", "prev", "thistype", this, "Attempted To Go Out Of Bounds.")
debug call ThrowError(isCollection, "SharedList", "prev", "thistype", this, "Attempted To Read List, Expecting Node.")
debug call ThrowError(not isNode, "SharedList", "prev", "thistype", this, "Attempted To Read Invalid Node.")
return _prev
endmethod
method operator first takes nothing returns thistype
debug call ThrowError(this == 0, "SharedList", "first", "thistype", this, "Attempted To Read Null List.")
debug call ThrowError(isNode, "SharedList", "first", "thistype", this, "Attempted To Read Node, Expecting List.")
debug call ThrowError(not isCollection, "SharedList", "first", "thistype", this, "Attempted To Read Invalid List.")
return _next
endmethod
method operator last takes nothing returns thistype
debug call ThrowError(this == 0, "SharedList", "last", "thistype", this, "Attempted To Read Null List.")
debug call ThrowError(isNode, "SharedList", "last", "thistype", this, "Attempted To Read Node, Expecting List.")
debug call ThrowError(not isCollection, "SharedList", "last", "thistype", this, "Attempted To Read Invalid List.")
return _prev
endmethod
static method operator sentinel takes nothing returns integer
return 0
endmethod
private static method allocate takes nothing returns thistype
local thistype this = thistype(0)._next
if (0 == this) then
debug call ThrowError(instanceCount == 8191, "SharedList", "allocate", "thistype", 0, "Overflow.")
set this = instanceCount + 1
set instanceCount = this
else
set thistype(0)._next = _next
endif
return this
endmethod
static method create takes nothing returns thistype
local thistype this = allocate()
debug set isCollection = true
set _next = 0
return this
endmethod
method push takes nothing returns thistype
local thistype node = allocate()
debug call ThrowError(this == 0, "SharedList", "push", "thistype", this, "Attempted To Push On To Null List.")
debug call ThrowError(isNode, "SharedList", "push", "thistype", this, "Attempted To Push On To Node, Expecting List.")
debug call ThrowError(not isCollection, "SharedList", "push", "thistype", this, "Attempted To Push On To Invalid List.")
debug set node.isNode = true
set node._list = this
if (_next == 0) then
set _next = node
set _prev = node
set node._next = 0
else
set _next._prev = node
set node._next = _next
set _next = node
endif
set node._prev = 0
return node
endmethod
method enqueue takes nothing returns thistype
local thistype node = allocate()
debug call ThrowError(this == 0, "SharedList", "enqueue", "thistype", this, "Attempted To Enqueue On To Null List.")
debug call ThrowError(isNode, "SharedList", "enqueue", "thistype", this, "Attempted To Enqueue On To Node, Expecting List.")
debug call ThrowError(not isCollection, "SharedList", "enqueue", "thistype", this, "Attempted To Enqueue On To Invalid List.")
debug set node.isNode = true
set node._list = this
if (_next == 0) then
set _next = node
set _prev = node
set node._prev = 0
else
set _prev._next = node
set node._prev = _prev
set _prev = node
endif
set node._next = 0
return node
endmethod
method pop takes nothing returns nothing
local thistype node = _next
debug call ThrowError(this == 0, "SharedList", "pop", "thistype", this, "Attempted To Pop Null List.")
debug call ThrowError(isNode, "SharedList", "pop", "thistype", this, "Attempted To Pop Node, Expecting List.")
debug call ThrowError(not isCollection, "SharedList", "pop", "thistype", this, "Attempted To Pop Invalid List.")
debug call ThrowError(node == 0, "SharedList", "pop", "thistype", this, "Attempted To Pop Empty List.")
debug set node.isNode = false
set _next._list = 0
set _next = _next._next
if (_next == 0) then
set _prev = 0
else
set _next._prev = 0
endif
set node._next = thistype(0)._next
set thistype(0)._next = node
endmethod
method dequeue takes nothing returns nothing
local thistype node = _prev
debug call ThrowError(this == 0, "SharedList", "dequeue", "thistype", this, "Attempted To Dequeue Null List.")
debug call ThrowError(isNode, "SharedList", "dequeue", "thistype", this, "Attempted To Dequeue Node, Expecting List.")
debug call ThrowError(not isCollection, "SharedList", "dequeue", "thistype", this, "Attempted To Dequeue Invalid List.")
debug call ThrowError(node == 0, "SharedList", "dequeue", "thistype", this, "Attempted To Dequeue Empty List.")
debug set node.isNode = false
set _prev._list = 0
set _prev = _prev._prev
if (_prev == 0) then
set _next = 0
else
set _prev._next = 0
endif
set node._next = thistype(0)._next
set thistype(0)._next = node
endmethod
method remove takes nothing returns nothing
local thistype node = this
set this = node._list
debug call ThrowError(node == 0, "SharedList", "remove", "thistype", this, "Attempted To Remove Null Node.")
debug call ThrowError(not node.isNode, "SharedList", "remove", "thistype", this, "Attempted To Remove Invalid Node (" + I2S(node) + ").")
debug call ThrowError(not isCollection, "SharedList", "remove", "thistype", this, "Attempted To Remove Node (" + I2S(node) + ") From Invalid List.")
debug call ThrowError(this == 0, "SharedList", "remove", "thistype", this, "Attempted To Remove Node (" + I2S(node) + ") Not Belonging To A List.")
debug set node.isNode = false
set node._list = 0
if (0 == node._prev) then
set _next = node._next
else
set node._prev._next = node._next
endif
if (0 == node._next) then
set _prev = node._prev
else
set node._next._prev = node._prev
endif
set node._next = thistype(0)._next
set thistype(0)._next = node
endmethod
method clear takes nothing returns nothing
debug local thistype node = _next
debug call ThrowError(this == 0, "SharedList", "clear", "thistype", this, "Attempted To Clear Null List.")
debug call ThrowError(isNode, "SharedList", "clear", "thistype", this, "Attempted To Clear Node, Expecting List.")
debug call ThrowError(not isCollection, "SharedList", "clear", "thistype", this, "Attempted To Clear Invalid List.")
static if DEBUG_MODE then
loop
exitwhen node == 0
set node.isNode = false
set node = node._next
endloop
endif
if (_next == 0) then
return
endif
set _prev._next = thistype(0)._next
set thistype(0)._next = _next
set _next = 0
set _prev = 0
endmethod
method destroy takes nothing returns nothing
debug call ThrowError(this == 0, "SharedList", "destroy", "thistype", this, "Attempted To Destroy Null List.")
debug call ThrowError(isNode, "SharedList", "destroy", "thistype", this, "Attempted To Destroy Node, Expecting List.")
debug call ThrowError(not isCollection, "SharedList", "destroy", "thistype", this, "Attempted To Destroy Invalid List.")
static if DEBUG_MODE then
debug call clear()
debug set isCollection = false
else
if (_next != 0) then
set _prev._next = thistype(0)._next
set thistype(0)._next = _next
set _prev = 0
endif
endif
set _next = thistype(0)._next
set thistype(0)._next = this
endmethod
static if DEBUG_MODE then
static method calculateMemoryUsage takes nothing returns integer
local thistype start = 1
local thistype end = instanceCount
local integer count = 0
loop
exitwhen integer(start) > integer(end)
if (integer(start) + 500 > integer(end)) then
return count + checkRegion(start, end)
else
set count = count + checkRegion(start, start + 500)
set start = start + 501
endif
endloop
return count
endmethod
private static method checkRegion takes thistype start, thistype end returns integer
local integer count = 0
loop
exitwhen integer(start) > integer(end)
if (start.isNode) then
set count = count + 1
elseif (start.isCollection) then
set count = count + 1
endif
set start = start + 1
endloop
return count
endmethod
static method getAllocatedMemoryAsString takes nothing returns string
local thistype start = 1
local thistype end = instanceCount
local string memory = null
loop
exitwhen integer(start) > integer(end)
if (integer(start) + 500 > integer(end)) then
if (memory != null) then
set memory = memory + ", "
endif
set memory = memory + checkRegion2(start, end)
set start = end + 1
else
if (memory != null) then
set memory = memory + ", "
endif
set memory = memory + checkRegion2(start, start + 500)
set start = start + 501
endif
endloop
return memory
endmethod
private static method checkRegion2 takes thistype start, thistype end returns string
local string memory = null
loop
exitwhen integer(start) > integer(end)
if (start.isNode) then
if (memory == null) then
set memory = I2S(start)
else
set memory = memory + ", " + I2S(start) + "N"
endif
elseif (start.isCollection) then
if (memory == null) then
set memory = I2S(start)
else
set memory = memory + ", " + I2S(start) + "C"
endif
endif
set start = start + 1
endloop
return memory
endmethod
endif
endmodule
endlibrary
//TESH.scrollpos=15
//TESH.alwaysfold=0
library ErrorMessage /* v1.0.1.4
*************************************************************************************
*
* Issue Compliant Error Messages
*
************************************************************************************
*
* debug function ThrowError takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
* - In the event of an error the game will be permanently paused
*
* debug function ThrowWarning takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
*
************************************************************************************/
static if DEBUG_MODE then
private struct Fields extends array
static constant string COLOR_RED = "|cffff0000"
static constant string COLOR_YELLOW = "|cffffff00"
static string lastError = null
endstruct
private function Pause takes nothing returns nothing
call PauseGame(true)
endfunction
private function ThrowMessage takes string libraryName, string functionName, string objectName, integer objectInstance, string description, string errorType, string color returns nothing
local string str
local string color_braces = "|cff66FF99"
local string orange = "|cffff6600"
set str = "->\n-> " + color_braces + "{|r " + "Library" + color_braces + "(" + orange + libraryName + color_braces + ")"
if (objectName != null) then
if (objectInstance > 0) then
set str = str + "|r.Object" + color_braces + "(" + orange + objectName + color_braces + " (|rinstance = " + orange + I2S(objectInstance) + color_braces + ") )" + "|r." + "Method" + color_braces + "(" + orange + functionName + color_braces + ")"
else
set str = str + "|r.Object" + color_braces + "(" + orange + objectName + color_braces + ")|r." + "Method" + color_braces + "(" + orange + functionName + color_braces + ")"
endif
else
set str = str + "|r." + "Function" + color_braces + "(" + orange + functionName + color_braces + ")"
endif
set str = str + color_braces + " }|r " + "has thrown an exception of type " + color_braces + "(" + color + errorType + color_braces + ")|r."
set Fields.lastError = str + "\n->\n" + "-> " + color + description + "|r\n->"
endfunction
function ThrowError takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
if (Fields.lastError != null) then
set objectInstance = 1/0
endif
if (expression) then
call ThrowMessage(libraryName, functionName, objectName, objectInstance, description, "Error", Fields.COLOR_RED)
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,Fields.lastError)
call TimerStart(CreateTimer(), 0, true, function Pause)
set objectInstance = 1/0
endif
endfunction
function ThrowWarning takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
if (Fields.lastError != null) then
set objectInstance = 1/0
endif
if (expression) then
call ThrowMessage(libraryName, functionName, objectName, objectInstance, description, "Warning", Fields.COLOR_YELLOW)
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,Fields.lastError)
set Fields.lastError = null
endif
endfunction
endif
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
//! novjass
// Read Me:
//
// I will try to cover most frequent questions
// so Missile is easy to use for everyone with
// little vJass knowledge.
//
// 1. I'm a GUI'er and I know nothing about vJass. Can I still use Missile?
//
// No you can't. Missile requires struct usage and therefore can't be used
// with GUI. That is also the reason the "location" handle is not supported
// for creating new Missles instances.
//
// 2. How can I create a Missile?
//
// Missile offers you various functions to create a new instance:
//
// - static method create takes real x, real y, real z, real angleInRadians, real distanceToTravel, real endZ returns Missile
//
// - static method createXYZ takes real x, real y, real z, real impactX, real impactY, real impactZ returns Missile (new)
//
// - static method createEx takes unit missileDummy, AdvLoc originP, AdvLoc impactP returns Missile
//
// - static method createLoc takes AdvLoc originP, AdvLoc impactP returns Missile (Dirac's API)
//
// 3. What are valid values for arc= ?
//
// method operator arc= takes real value returns nothing
//
// - arc takes radian, while anything similar to PI" (1.57) will result in an error. For example bj_PI/4 is ok.
// - arc= can take values in range of -PI/2 < arc < PI/2 --> ~~ -1.5 to 1.5
// - using -PI/2 or PI/2 will result in malfunction. A missile with 90° incline never reaches it's goal.
//
// 3. What are valid values for curve= ?
//
// method operator curve= takes real value returns nothing
// - like arc, curve takes radian, while anything similar to PI" (1.57) will result in an error. For example bj_PI/4 is ok.
// - curve= can take values in range of -PI/2 < arc < PI/2 --> ~~ -1.5 to 1.5
// - using -PI/2 or PI/2 will result in a fatal error and not even BoundSentinel can save you here.
// A missile with PI/2 curve will be out of world bounds on the first tick.
//! endnovjass
//TESH.scrollpos=1156
//TESH.alwaysfold=0
library Missile /* v1.2 (2.0)
*************************************************************************************
*
* Creating custom projectiles in wc3.
*
* Major goal:
* Runs as effective as possible.
* No unessary external requirements.
* Implements code optional.
*
* Philosophy:
* I want that feature --> Compiler writes that code into your map script.
* I don't want that --> Compiler ignores that code completly.
*
* Important:
* Take yourself 2 minutes time to setup Missile correctly.
* Otherwise I can't guarantee, that Missile works the way you want.
* Once the setup is done, you can check out some examples and Missile will be easy
* to use for everyone. I promise it.
*
* Do the setup at:
*
* 1.) Import instruction
* 2.) Configuration
*
* Credits to Dirac, emjlr3, AceHart
*
*************************************************************************************
*
* */ requires /*
*
* - Missile requires nothing
*
*************************************************************************************
*
* Optional requirements listed can reduce overall code generation,
* add safety mechanims, decrease overhead and optimize handle management.
* For a better overview I put them into blocks.
*
* I recommend to use at least one per block in your map.
*
* a). For best debug results: (Extremly useful for Missile)
* */ optional ErrorMessage /* [url]https://github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage[/url]
*
* b). Fatal error protection (Case: unit out of world bounds):
* - WorldBounds is safer than BoundSentinel.
* - WorldBounds adds more overhead than BoundSentinel.
* */ optional WorldBounds /* [url]https://raw.githubusercontent.com/nestharus/JASS/master/jass/Systems/WorldBounds/script.j[/url]
* */ optional BoundSentinel /* [url]http://www.wc3c.net/showthread.php?t=102576[/url]
*
* c). Handle recycling (Speed gain, memory management):
* - uses MissileRecylcer over Dummy.
* */ optional MissileRecycler /* [url]http://www.hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/[/url]
* */ optional Dummy /* [url]https://github.com/nestharus/JASS/blob/master/jass/Systems/Dummy/Dummy.w3x[/url]
* */ optional xedummy /* [url]http://www.wc3c.net/showthread.php?t=101150[/url]
* */ optional GroupUtils /* [url]http://www.wc3c.net/showthread.php?t=104464[/url]
*
* d). Misc (Absolutely not needed, just listed to avoid an onIndex event.)
* */ optional UnitIndexer /* [url]https://github.com/nestharus/JASS/tree/master/jass/Systems/Unit%20Indexer[/url]
*
************************************************************************************
*
* 1. Import instruction
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* You need a dummy unit, using Vexorians "dummy.mdx".
* If you are using water terrain in your map, the movement type has to be "Hover".
* This unit must use the locust and crow form ability. (Aloc & Amrf)
* ¯¯¯¯
* Copy Missile into to your map.
* Libraries listed as optional may improve Missile,
* however aren't mandatory to have.
*
* 2. Configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Eight constants and two functions to setup and Missile is ready to use!
*
*/
globals
/**
* Missiles are moved periodically. 1/32 is recommended.
* - Small values may cause performance issues.
* - Large values may look fishy.
*/
private constant real TIMER_TIMEOUT = 0.031250000
/**
* Owner of all Missile dummies. Should be a neutral player in your map.
*/
private constant player NEUTRAL_PASSIVE = Player(15)
/**
* Raw code of the dummy unit. Object Editor (F6)
* Must be correct, otherwise you will experience lags as ingame time goes by.
* Units having other type ids will not be thrown into the recycler bin.
* In DEBUG_MODE you get an error this setting is incorrect.
*/
private constant integer DUMMY_UNIT_ID = 'h001'
/**
* The maximum collision size used in your map. If unsure use 197.
* Applies for units and destructables.
* A precise value can improve Missile's performance,
* since smaller values enumerate less widgtes per loop per missile.
* In DEBUG_MODE you get an error this setting is incorrect.
*/
public constant real MAXIMUM_COLLISION_SIZE = 197.
// Set features listed below to true and the compiler will write
// them into your map. Otherwise this code is completly ignored.
// o Yay, I want that --> true
// o Naah that's useless for me --> false
/**
* Set USE_DESTRUCTABLE_FILTER to true to:
* - Enable destructable collision for Missiles.
*/
public constant boolean USE_DESTRUCTABLE_FILTER = true
/**
* Enables z axis collision for Missiles. Normally it is only in x and y axis.
* This will add overhead. Use when you need:
* - Missiles fly over or under widgets.
* - Determine between flying and walking units.
*/
public constant boolean USE_COLLISION_Z_FILTER = true
/**
* Set WRITE_DELAYED_MISSILE_RECYCLING to true, to write a delayed dummy
* recycling feature into library Missile. Very recommended in two cases:
*
* - You use a dummy recycling library like Dummy, xedummy or MissileRecycler.
* - You want to display death animations of the sfx added to your missiles.
*/
public constant boolean WRITE_DELAYED_MISSILE_RECYCLING = true
/**
* Relates to WRITE_DELAYED_MISSILE_RECYCLING. It's the approximated
* death animation time for every missile you use. Doesn't have to be precise.
*/
private constant real DELAYED_MISSILE_DEATH_ANIMATION_TIME = 2.
endglobals
/**
* Only required when USE_COLLSION_Z_FILTER = true.
* To detect z collision, every unit needs a body size value.
* How you reference these values is up to you: (Hashtable + HandleId/UnitTypeId, constant value for all units...)
* In the example I placed a constant value (100) for all units.
*/
function GetUnitBodySize takes unit whichUnit returns real
return 100. // LoadReal(hash, GetHandleId(whichUnit), KEY_UNIT_BODY_SIZE)<- Example
endfunction
/**
* Only required when USE_COLLSION_Z_FILTER = true.
* Same as GetUnitBodySize, but for destructables.
*/
function GetDestructableHeight takes destructable whichDestructable returns real
return GetDestructableOccluderHeight(whichDestructable)
endfunction
/**
* 2. API
* ¯¯¯¯¯¯
* struct Missile extends array
*
* Methods to create a new Missile instance:
*
* static method create takes real x, real y, real z, real angleInRadians, real distanceToTravel, real endZ returns Missile
* static method createXYZ takes real x, real y, real z, real impactX, real impactY, real impactZ returns Missile (new)
* static method createEx takes unit missileDummy, MissilePosition originP, MissilePosition impactP returns Missile
* static method createLoc takes location loc, real z, location impactLoc, real impactZ returns thistype
* static method createFacing takes real x, real y, real z, real angleInRadians, real distanceToTravel, real impactZ, real faceInDegree returns Missile
* static method createFacingXYZ takes real x, real y, real z, real impactX, real impactY, real impactZ, real faceInDegree returns Missile
*
* Members you can configurate:
*
* - source
* - target
* - real speed
* - real acceleration
* - real turn
* - real open --> use curve=
* - real height --> use arc=
* - string model
* - integer data
* - real collision
* - real collisionZ ( only if WRITE_DELAYED_MISSILE_RECYCLING is set to true )
*
* Methods and operator overloading:
*
* method operator arc= takes real value returns nothing
* method operator curve= takes real value returns nothing
* method operator scale= takes real value returns nothing
* method bounce takes nothing returns nothing
* method deflect takes real x, real y returns nothing
* method deflectEx takes real x, real y, real z returns nothing
*
*
* Missile offers various optional static methods, which can be written into
* any struct you implement the MissileStruct module in.
*
* All of them return boolean
* - return true means: destroy the Missile
* - return false means: keep the Missile flying.
*
* static method onCollide takes Missile this, unit hit returns boolean
* - Runs every time the missile collides with an unit.
*
* static method onDestructableFilter takes nothing returns boolean
* - Runs before onDestructable.
* - Creates a better filter for enumerated destructables.
*
* static method onDestructable takes Missile this, destructable hit returns boolean
* - Runs every time the missile collides with a destructable.
* - Can use onDestructableFilter ( optional )
*
* static method onPeriod takes Missile this returns boolean
* - Runs every Mssile_TIMER_TIMEOUT seconds.
*
* static method onFinish takes Missile this returns boolean
* - Runs whenever the missile finishes it's course.
* - Runs before the missile is destroyed.
*
* static method onRemove takes Missile this returns boolean
* - Runs whenever the missile is deallocated.
* - Return true will recycle a Missile delayed ( only if WRITE_DELAYED_MISSILE_RECYCLING set to true )
* - Return false will recycle a Missile right away.
*
* module MissileStruct
*
* static method launch takes Missile toLaunch returns nothing
* - Enqueues a Missile instance to the Missile stack.
* - Example: call Fireball.launch(toLaunch)
*
************************************************************************************************/
// Hello and welcome to Missile.
// I wrote a guideline for every piece of code inside, so you
// can easily understand how Missile is compiled and evaluated.
// Let's go!
// Macros work like copy 'n' paste. This one covers Missles
// creator functions, which are: create, createEx, createXYZ and createLoc.
// So below I only have to change a few arguments for each function. Sweet, or?
// Please do not use this macro anywhere else!
//! textmacro CREATE_MISSILE_DUMMY_UNIT takes X, Y, Z, FACE
call MissilePosition.link(originP, impactP)
static if LIBRARY_MissileRecycler then
return Missile.createEx(GetRecycledMissile($X$, $Y$, $Z$, $FACE$*bj_RADTODEG), $FACE$, originP, impactP)
elseif LIBRARY_Dummy and Dummy.create.exists then
return Missile.createEx(Dummy.create($X$, $Y$, $FACE$*bj_RADTODEG).unit, $FACE$, originP, impactP)
elseif LIBRARY_xedummy and xedummy.new.exists then
return Missile.createEx(xedummy.new(NEUTRAL_PASSIVE, $X$, $Y$, $FACE$*bj_RADTODEG), $FACE$, originP, impactP)
else
return Missile.createEx(Missile.newMissileUnit($X$, $Y$, $FACE$*bj_RADTODEG), $FACE$, originP, impactP)
endif
//! endtextmacro
// If you set WRITE_DELAYED_MISSILE_RECYCLING from the configs to true, the compiler will write
// a recycler bin, doing delayed dummy unit recycling into Missle.
// If set to false, no code is created here as it includes a static if.
//
// The code of the macro is located at the bottom of Missile, as it would hurt read-ability if placed here.
//! runtextmacro WRITE_MISSILE_RECYCLE_BIN("WRITE_DELAYED_MISSILE_RECYCLING", "DELAYED_MISSILE_DEATH_ANIMATION_TIME")
// This macro boxes a missiles positions and does the required trigonometry.
// I took the best from Nes Position and Dirac's Loc/AdvLoc.
//
// The code of the macro is located at the bottom of Missile, as it would hurt read-ability if placed here.
//! runtextmacro WRITE_MISSILE_POSITION_CODE()
public function GetLocZ takes real x, real y returns real
call MoveLocation(MissilePosition.loc, x, y)
return GetLocationZ(MissilePosition.loc)
endfunction
// keywords are replaced directives for a scope.
// Missiles structure works like a list with the folling methods:
// allocateCollection(), allocateNode(), insertEnd(node) and remove()
//
// MissileStructure is implemented as a module
// The code is located at the bottom of Missile, as it would also hurt read-ability if placed here.
private keyword MissileStructure
struct Missile extends array
implement MissileStructure
// I included enormous debug safety to Missile,
// to eradicate wrong user usage. You should do this
// with every system you write.
//
debug static constant boolean DEBUG_MISSILE = true
// launched prevents double launching! Very logical.
debug boolean launched
// Sfx attach point name. Read-only for shared usage in your map.
// Of course only if you read until here :)!
readonly static constant string ORIGIN = "origin"
// Please do not use this group from somewhere else.
// Rather go with bj_lastCreatedGroup
readonly static group enumGroup = CreateGroup()
// Prevents a double free case.
readonly boolean allocated
// Useful struct members. Names chosen to be self-explaining.
private effect sfx
private string path
private real missileScale
private real cA// current angle
// For curved missiles the position of a missile is not
// equal to the x/y required by Missiles math.
// Therefore we need this member two times.
// x/y/z for your needs and xPrivate/yPrivate for cool mathematics.
private real xPrivate
private real yPrivate
readonly MissilePosition origin
readonly MissilePosition impact
readonly real x
readonly real y
readonly real z
readonly unit dummy
readonly group unitsHit
// wantDestroy deallocates an instance on the next
// timer callback. 100% safe to use.
boolean wantDestroy
// recycle is set to true, when a missile reached
// it's destination. Then it may be destroyed.
boolean recycle
// distance traveled.
real distance
// Members for your needs.
// No operator overloading, expect in a few cases.
unit target
unit source
// I really don't know how wc3 works with negative collision values. (bug free???)
// So I decided to monitor collision and eventually collisionZ.
static if DEBUG_MODE and LIBRARY_ErrorMessage then
// operators allow us to check for invalid arguments like value < 0
debug private real collisionDebug
method operator collision= takes real value returns nothing
debug call ThrowError((value < 0), "Missile", "collision=", "collision", this, "Found Collision Below Zero [" + R2S(value) + "]!")
debug set collisionDebug = value
endmethod
method operator collision takes nothing returns real
debug return collisionDebug
endmethod
// Same applies for collisionZ
static if Missile_USE_COLLISION_Z_FILTER then
debug private real collisionZDebug
method operator collisionZ= takes real value returns nothing
debug call ThrowError((value < 0), "Missile", "collisionZ=", "collisionZ", this, "Found CollisionZ Below Zero [" + R2S(value) + "]!")
debug set collisionZDebug = value
endmethod
method operator collisionZ takes nothing returns real
debug return collisionZDebug
endmethod
endif
else
// Without debug mode we give a shit on monitoring!
real collision
static if Missile_USE_COLLISION_Z_FILTER then
real collisionZ
endif
endif
// Speed determines the lenght of our vector for Missile movement.
// Non positive values and zero are invalid. So let's monitor again.
static if DEBUG_MODE and LIBRARY_ErrorMessage then
debug private real speedDebug
method operator speed= takes real value returns nothing
debug call ThrowError((value <= 0), "Missile", "speed=", "speed", this, "Speed Values Below Or Equal To Zero Are Not Allowed [" + R2S(value) + "]!")
set speedDebug = value
endmethod
method operator speed takes nothing returns real
return speedDebug
endmethod
else
real speed
endif
real height
real turn
real open
real damage
real acceleration
// You can attach data to a missile.
integer data
// As the dummy always belongs to the neutral player.
// You can add a pseudo owner for faster onCollide evaluation.
// No monitoring or nulling included. Use carefully.
player owner
// Dummy is readonly so you can add more than one model.
method operator model= takes string modelFile returns nothing
if (sfx != null) then
call DestroyEffect(sfx)
endif
set path = modelFile
set sfx = AddSpecialEffectTarget(modelFile, dummy, Missile.ORIGIN)
endmethod
method operator model takes nothing returns string
return path
endmethod
// Simple trigonometry, some values will freak out the missile movement.
// I can't protect you from all invalid values. I did it for PI/2!
method operator curve= takes real value returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError((value == bj_PI/2), "Missile", "curve=", "value", this, "Entered Value [" + R2S(value) + "] Will Result In Game Crash. Radians similar and close to PI/2 are not allowed!")
endif
set open = Tan(value)*origin.distance
endmethod
method operator curve takes nothing returns real
if (origin.distance == 0) then
return 0. // Mmh what to return here?
endif
return Atan(open/origin.distance)
endmethod
// For arc the same as for curve
method operator arc= takes real value returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError((value == bj_PI/2), "Missile", "arc=", "value", this, "Entered Value [" + R2S(value) + "] Won't Let The Missile Finish. Radians similar to PI/2 are not allowed!")
endif
set height = Tan(value)*origin.distance/4
endmethod
method operator arc takes nothing returns real
if (origin.distance == 0) then
return 0. // Mmh what to return here?
endif
return Atan(4*height/origin.distance)
endmethod
method operator scale= takes real value returns nothing
call SetUnitScale(dummy, value, 1., 1.)
set missileScale = value
endmethod
method operator scale takes nothing returns real
return missileScale
endmethod
method bounce takes nothing returns nothing
call origin.move(xPrivate, yPrivate, z)
set distance = 0
endmethod
method deflect takes real tx, real ty returns nothing
local real a = 2*Atan2(ty - yPrivate, tx - xPrivate) + bj_PI - cA
call impact.move(xPrivate + (origin.distance - distance)*Cos(a), yPrivate + (origin.distance - distance)*Sin(a), impact.z)
call bounce()
endmethod
method deflectEx takes real tx, real ty, real tz returns nothing
call impact.move(impact.x, impact.y, tz)
call deflect(tx, ty)
endmethod
// I would expect more from a destroy method :)!
method destroy takes nothing returns nothing
set wantDestroy = true
endmethod
// As user you shouldn't have access to .remove(), so you can use terminate instead.
method terminate takes nothing returns nothing
set allocated = false
call DestroyEffect(sfx)
static if LIBARARY_GroupUtils then
call ReleaseGroup(unitsHit)
else
call DestroyGroup(unitsHit)
endif
if (GetUnitTypeId(dummy) == DUMMY_UNIT_ID) then
static if LIBRARY_MissileRecycler then
call RecycleMissile(dummy)
elseif Dummy.create.exists and LIBRARY_Dummy then
call Dummy[dummy].destroy()
elseif LIBRARY_xedummy and xedummy.release.exists then
call xedummy.release(dummy)
else
call RemoveUnit(dummy)
endif
endif
set recycle = false
set sfx = null
set unitsHit = null
set source = null
set target = null
set dummy = null
call impact.destroy()
call origin.destroy()
call remove()
endmethod
static method createEx takes unit missileDummy, real face, MissilePosition originP, MissilePosition impactP returns thistype
local thistype this = thistype.allocateNode()
static if DEBUG_MODE and LIBRARY_ErrorMessage then
debug call ThrowError((GetUnitTypeId(missileDummy) == 0), "Missile", "createEx", "missileDummy", this, "Invalid Missile Dummy Unit (null)!")
debug call ThrowError((originP == 0), "Missile", "createEx", "MissilePosition origin", this, "Invalid MissilePosition Origin (0)!")
debug call ThrowError((impactP == 0), "Missile", "createEx", "MissilePosition impact", this, "Invalid MissilePosition Impact (0)!")
endif
set collision = 0
static if Missile_USE_COLLISION_Z_FILTER then
set collisionZ = 0
endif
set acceleration = 0
set height = 0
set turn = 0
set open = 0
set recycle = false
set wantDestroy = false
set path = ""
set xPrivate = originP.x
set yPrivate = originP.y
set z = originP.z
set origin = originP
set impact = impactP
set cA = face
set distance = 0
set dummy = missileDummy
set allocated = true
static if LIBRARY_GroupUtils then
set unitsHit = NewGroup()
else
set unitsHit = CreateGroup()
endif
call MoveLocation(MissilePosition.loc, originP.x, originP.y)
call SetUnitFlyHeight(missileDummy, originP.z - GetLocationZ(MissilePosition.loc), 0.)
debug set launched = false
return this
endmethod
// Freaky static if ensures these libraries really don't exist.
static if not LIBRARY_MissileRecycler and not LIBRARY_Dummy and not Dummy.create.exists and not LIBRARY_xe_dummy and not xe_dummy.new.exists then
private static method newMissileUnit takes real x, real y, real face returns unit
// Just in case you have UnitIndexer.
static if LIBRARY_UnitIndexer then
set UnitIndexer.enabled = false
endif
set bj_lastCreatedUnit = CreateUnit(NEUTRAL_PASSIVE, DUMMY_UNIT_ID , x, y, face)
call SetUnitX(bj_lastCreatedUnit, x)
call SetUnitY(bj_lastCreatedUnit, y)
call PauseUnit(bj_lastCreatedUnit, true)
static if LIBRARY_UnitIndexer then
set UnitIndexer.enabled = true
endif
return bj_lastCreatedUnit
endmethod
endif
// Remember the macro I declared at the top of Missile?
static method createFacingXYZ takes real x, real y, real z, real impactX, real impactY, real impactZ, real face returns thistype
local MissilePosition originP = MissilePosition.create(x, y, z)
local MissilePosition impactP = MissilePosition.create(impactX, impactY, impactZ)
//! runtextmacro CREATE_MISSILE_DUMMY_UNIT("x", "y", "originP.z", "face")
endmethod
static method createFacing takes real x, real y, real z, real angle, real distanceP, real impactZ, real face returns thistype
local MissilePosition originP = MissilePosition.create(x, y, z)
local MissilePosition impactP = MissilePosition.create(x + distanceP*Cos(angle),y + distanceP*Sin(angle), impactZ)
//! runtextmacro CREATE_MISSILE_DUMMY_UNIT("originP.x", "originP.y", "originP.z", "face")
endmethod
static method createLoc takes location loc, real z, location impactLoc, real impactZ returns thistype
local MissilePosition originP = MissilePosition.create(GetLocationX(loc), GetLocationX(loc), z)
local MissilePosition impactP = MissilePosition.create(GetLocationX(impactLoc), GetLocationX(impactLoc), z)
//! runtextmacro CREATE_MISSILE_DUMMY_UNIT("originP.x", "originP.y", "originP.z", "originP.angle")
endmethod
// This one is new. I think it is useful.
static method createXYZ takes real x, real y, real z, real impactX, real impactY, real impactZ returns thistype
local MissilePosition originP = MissilePosition.create(x, y, z)
local MissilePosition impactP = MissilePosition.create(impactX, impactY, impactZ)
//! runtextmacro CREATE_MISSILE_DUMMY_UNIT("x", "y", "originP.z", "originP.angle")
endmethod
static method create takes real x, real y, real z, real angle, real distanceP, real impactZ returns thistype
local MissilePosition originP = MissilePosition.create(x, y, z)
local MissilePosition impactP = MissilePosition.create(x + distanceP*Cos(angle),y + distanceP*Sin(angle), impactZ)
//! runtextmacro CREATE_MISSILE_DUMMY_UNIT("x", "y", "originP.z", "originP.angle")
endmethod
// Periodic loop every 1/32 seconds. Runs once per active missile struct.
method move takes nothing returns nothing
local MissilePosition loc
local real a
local real d
local real s
local unit u
local real h
local real tx
local real ty
loop
exitwhen (this == 0)
set loc = origin
set u = target
set h = height
// Eval missile target.
if (u != null) then
if (0 != GetUnitTypeId(u)) then
set tx = GetUnitX(u)
set ty = GetUnitY(u)
set a = Atan2(ty - yPrivate, tx - xPrivate)
call impact.move(tx, ty, GetUnitFlyHeight(u))
set distance = loc.distance - SquareRoot((tx - xPrivate)*(tx - xPrivate) + (ty - yPrivate)*(ty - yPrivate))
else
set target = null
endif
else
set a = loc.angle
endif
if (0 != turn) and (Cos(cA - a) < Cos(turn)) then
if (Sin(a - cA) >= 0) then
set cA = cA + turn
else
set cA = cA - turn
endif
else
set cA = a
endif
set d = loc.distance
set s = distance + speed
set distance = s
set u = dummy
set tx = xPrivate + speed*Cos(cA)
set ty = yPrivate + speed*Sin(cA)
set speed = speed + acceleration
set xPrivate = tx
set yPrivate = ty
if (open != 0) then
set a = 4*open*s*(d - s)/(d*d)
set tx = tx + a*Cos(cA + 1.57)
set ty = ty + a*Sin(cA + 1.57)
call SetUnitFacing(u, (cA + Atan(-((4*open)*(2*s - d))/(d*d)))*bj_RADTODEG)
else
call SetUnitFacing(u, cA*bj_RADTODEG)
endif
set x = tx
set y = ty
static if not LIBRARY_BoundSentinel and LIBRARY_WorldBounds then
if (tx>WorldBounds.maxX) or (tx<WorldBounds.minX) or (ty>WorldBounds.maxY) or (ty<WorldBounds.minY) then
call destroy()
else
call SetUnitX(u, tx)
call SetUnitY(u, ty)
endif
else
call SetUnitX(u, tx)
call SetUnitY(u, ty)
endif
call MoveLocation(MissilePosition.loc, tx, ty)
if (h != 0) or (loc.slope != 0) then
set z = (4*h*s*(d - s)/(d*d) + loc.slope*s + loc.z) - GetLocationZ(MissilePosition.loc)
call SetUnitAnimationByIndex(u, R2I((Atan(loc.slope) - Atan(((4*h)*(2*s - d))/(d*d))*bj_RADTODEG) + 90.5))
else
set z = loc.z - GetLocationZ(MissilePosition.loc)
endif
call SetUnitFlyHeight(u, z, 0)
if (s >= d) then
set recycle = true
endif
set u = null
set this = next
endloop
endmethod
// Optional code stays optional.
static if Missile_USE_DESTRUCTABLE_FILTER then
readonly static rect enumRect = Rect(0., 0., 0., 0.)
static Missile temp = 0
static real tempDist = 0
// After several test it turned out, that the square used in EnumDestructablesInRect leaves out
// destructables which are within an appropriate range of the projectile.
// Sadly there is no IsDestructableInRange native!!!
static method actionFuncDest takes nothing returns nothing
local destructable d = GetEnumDestructable()
local real xd = GetDestructableX(d)
local real yd = GetDestructableY(d)
local real dist = (temp.x - xd)*(temp.x - xd) + (temp.y - yd)*(temp.y - yd)
static if Missile_USE_COLLISION_Z_FILTER then
if(GetDestructableHeight(d) >= temp.z) and (dist <= bj_enumDestructableRadius) then
if (dist < Missile.tempDist) then
set Missile.tempDist = dist
set bj_destRandomCurrentPick = d
endif
endif
else
if (dist <= bj_enumDestructableRadius) then
if (dist < Missile.tempDist) then
set Missile.tempDist = dist
set bj_destRandomCurrentPick = d
endif
endif
endif
set d = null
endmethod
endif
static if Missile_WRITE_DELAYED_MISSILE_RECYCLING then
method nullBefore takes nothing returns nothing
set dummy = null
endmethod
endif
// Does not check for 'Aloc' and 'Amrf' as they could be customized.
static if Missile.DEBUG_MISSILE and DEBUG_MODE and LIBRARY_ErrorMessage then
private static method onInit takes nothing returns nothing
static if LIBRARY_UnitIndexer then
debug set UnitIndexer.enabled = false
endif
debug set bj_lastCreatedUnit = CreateUnit(NEUTRAL_PASSIVE, DUMMY_UNIT_ID, 0, 0, 0)
debug call ThrowError((GetUnitTypeId(bj_lastCreatedUnit) != DUMMY_UNIT_ID), "Missile", "DEBUG_MISSILE", "typeid", 0, "Missile Setup For DUMMY_UNIT_ID Is Incorrect! Map Can't Use Missile!")
debug call ThrowError((Missile_MAXIMUM_COLLISION_SIZE < 0), "Missile", "DEBUG_MISSILE", "collision", 0, "Missile Setup For MAXIMUM_COLLISION_SIZE Is Incorrect, Below Zero! Map Can't Use Missile!")
debug call RemoveUnit(bj_lastCreatedUnit)
debug set bj_lastCreatedUnit = null
static if LIBRARY_UnitIndexer then
debug set UnitIndexer.enabled = true
endif
endmethod
endif
endstruct
// You made it to the end of Missile, but we are not finished.
// Do you remember about the data structure the delayed recycler
// and of course our interafe module MissileStruct
//
// This comes now.
// Debug code taken from List (credits to Nestharus)
private module MissileStructure
private static thistype collectionCount = 0
private static thistype nodeCount = 0
static if LIBRARY_ErrorMessage then
debug private boolean isNode
debug private boolean isCollection
endif
private thistype _list
method operator list takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "MissileStructure", "list", "thistype", this, "Attempted To Read Null Node.")
debug call ThrowError(not isNode, "MissileStructure", "list", "thistype", this, "Attempted To Read Invalid Node.")
endif
return _list
endmethod
private thistype _next
method operator next takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "MissileStructure", "next", "thistype", this, "Attempted To Go Out Of Bounds.")
debug call ThrowError(not isNode, "MissileStructure", "next", "thistype", this, "Attempted To Read Invalid Node.")
endif
return _next
endmethod
private thistype _prev
method operator prev takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "MissileStructure", "prev", "thistype", this, "Attempted To Go Out Of Bounds.")
debug call ThrowError(not isNode, "MissileStructure", "prev", "thistype", this, "Attempted To Read Invalid Node.")
endif
return _prev
endmethod
private thistype _first
method operator first takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "MissileStructure", "first", "thistype", this, "Attempted To Read Null List.")
debug call ThrowError(not isCollection, "MissileStructure", "first", "thistype", this, "Attempted To Read Invalid List.")
endif
return _first
endmethod
private thistype _last
method operator last takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "MissileStructure", "last", "thistype", this, "Attempted To Read Null List.")
debug call ThrowError(not isCollection, "MissileStructure", "last", "thistype", this, "Attempted To Read Invalid List.")
endif
return _last
endmethod
static method allocateCollection takes nothing returns thistype
local thistype this = thistype(0)._first
if (0 == this) then
static if LIBRARY_ErrorMessage then
debug call ThrowError(collectionCount == 8191, "MissileStructure", "allocateCollection", "thistype", 0, "Overflow.")
endif
set this = collectionCount + 1
set collectionCount = this
else
set thistype(0)._first = _first
endif
static if LIBRARY_ErrorMessage then
debug set isCollection = true
endif
set _first = 0
return this
endmethod
static method allocateNode takes nothing returns thistype
local thistype this = thistype(0)._next
if (0 == this) then
static if LIBRARY_ErrorMessage then
debug call ThrowError(nodeCount == 8191, "MissileStructure", "allocateNode", "thistype", 0, "Overflow.")
endif
set this = nodeCount + 1
set nodeCount = this
else
set thistype(0)._next = _next
endif
return this
endmethod
method insertEnd takes thistype node returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "MissileStructure", "enqueue", "thistype", this, "Attempted To Enqueue On To Null List.")
debug call ThrowError(not isCollection, "MissileStructure", "enqueue", "thistype", this, "Attempted To Enqueue On To Invalid List.")
debug set node.isNode = true
endif
set node._list = this
if (_first == 0) then
set _first = node
set _last = node
set node._prev = 0
else
set _last._next = node
set node._prev = _last
set _last = node
endif
set node._next = 0
return node
endmethod
method remove takes nothing returns nothing
local thistype node = this
set this = node._list
static if LIBRARY_ErrorMessage then
debug call ThrowError(node == 0, "MissileStructure", "remove", "thistype", this, "Attempted To Remove Null Node.")
debug call ThrowError(not node.isNode, "MissileStructure", "remove", "thistype", this, "Attempted To Remove Invalid Node (" + I2S(node) + ").")
debug set node.isNode = false
endif
set node._list = 0
if (0 == node._prev) then
set _first = node._next
else
set node._prev._next = node._next
endif
if (0 == node._next) then
set _last = node._prev
else
set node._next._prev = node._prev
endif
set node._next = thistype(0)._next
set thistype(0)._next = node
endmethod
endmodule
// Can private as the MissileStruct module is written here.
globals
private trigger core = CreateTrigger()
private timer clock = CreateTimer()
private integer active = 0
private boolean array enabled
private integer array instances
private Missile array missileStack
private boolexpr array expression
private triggercondition array condition
endglobals
private function Fire takes nothing returns nothing
call TriggerEvaluate(core)
endfunction
private function MissileCreateExpression takes integer structId, code c returns nothing
set expression[structId] = Condition(c)
endfunction
// Start timer 1/32 if not already running.
private function StartPeriodic takes integer structId returns nothing
if (instances[structId] == 0) then
set condition[structId] = TriggerAddCondition(core, expression[structId])
set enabled[structId] = true
if (active == 0) then
call TimerStart(clock, TIMER_TIMEOUT, true, function Fire)
endif
set active = active + 1
endif
set instances[structId] = instances[structId] + 1
endfunction
// And stops it.
private function StopPeriodic takes integer structId returns nothing
set instances[structId] = instances[structId] - 1
if (instances[structId] == 0) and enabled[structId] then
set active = active - 1
if (active == 0) then
call PauseTimer(clock)
endif
call TriggerRemoveCondition(core, condition[structId])
set condition[structId] = null
set enabled[structId] = false
endif
endfunction
// Now the popular MissileStruct.
// How is it working? Let's see!
//
// P in the end of all methods should prevent users
// from accidently declaring a method with the same name.
module MissileStruct
// Why not run some debug checks first.
static if DEBUG_MODE then
static if not Missile_USE_DESTRUCTABLE_FILTER and thistype.onDestructable.exists then
Error, Missile, thistype, onDestructable is a reserved name for Missile once you implemented MissileStruct.
Either you forget to set USE_DESTRUCTABLE_FILTER to true in library Missile or re-name that method.
endif
static if not Missile_USE_DESTRUCTABLE_FILTER and thistype.onDestructableFilter.exists then
Error, Missile, thistype, onDestructableFilter is a reserved name for Missile once you implemented MissileStruct.
Either you forget to set USE_DESTRUCTABLE_FILTER to true in library Missile or re-name that method.
endif
static if thistype.onMissile.exists then
Error, Missile, thistype, onMissile is currently not supported by library Missile.
endif
endif
static if Missile_USE_DESTRUCTABLE_FILTER and thistype.onDestructableFilter.exists then
// Stupid name chosen, to be unique in that struct ... hopefully.
private static boolexpr destCondition_p = null
endif
// Stupid name ensures not to be called accidently.
private static method missileTerminateP takes Missile node returns nothing
static if thistype.onRemove.exists then
static if Missile_WRITE_DELAYED_MISSILE_RECYCLING and RecycleBin.recycle.exists then
if thistype.onRemove(node) and (GetUnitTypeId(node.dummy) == DUMMY_UNIT_ID) then
call RecycleBin.recycle(node.dummy)
call node.nullBefore()
endif
else
call thistype.onRemove(node)
endif
endif
call node.terminate()
call StopPeriodic(thistype.typeid)
endmethod
private static method missileIterateP takes nothing returns boolean
local Missile this = missileStack[.typeid].first
local Missile node
local real collideZ
local unit u
// Move the whole stack.
call this.move()
loop
exitwhen this == 0
set node = this.next
if (this.wantDestroy) then
static if thistype.onFinish.exists then
if thistype.onFinish(this) then
call thistype.missileTerminateP(this)
endif
else
call thistype.missileTerminateP(this)
endif
else
static if thistype.onCollide.exists then
if (0 != this.collision) then
call GroupEnumUnitsInRange(Missile.enumGroup, this.x, this.y, this.collision + Missile_MAXIMUM_COLLISION_SIZE, null)
loop
set u = FirstOfGroup(Missile.enumGroup)
exitwhen u == null
call GroupRemoveUnit(Missile.enumGroup, u)
if (u != this.target) and IsUnitInRange(u, this.dummy, this.collision) then
static if Missile_USE_COLLISION_Z_FILTER then
set collideZ = Missile_GetLocZ(GetUnitX(u), GetUnitY(u)) + GetUnitFlyHeight(u) - Missile_GetLocZ(this.x, this.y)
if (collideZ + GetUnitBodySize(u) >= this.z - this.collisionZ) and (collideZ <= this.z + this.collisionZ) then
if not (IsUnitInGroup(u, this.unitsHit)) then
if thistype.onCollide(this, u) then
call thistype.missileTerminateP(this)
exitwhen true
endif
call GroupAddUnit(this.unitsHit, u)
endif
endif
else
if not (IsUnitInGroup(u, this.unitsHit)) then
if thistype.onCollide(this, u) then
call thistype.missileTerminateP(this)
exitwhen true
endif
call GroupAddUnit(this.unitsHit, u)
endif
endif
endif
endloop
endif
endif
static if thistype.onDestructable.exists then
// Check if the missile is not terminated.
if (this.allocated) and (0 != this.collision) then
set bj_enumDestructableRadius = this.collision + Missile_MAXIMUM_COLLISION_SIZE
set bj_destRandomCurrentPick = null
call SetRect(Missile.enumRect, this.x - bj_enumDestructableRadius, this.y - bj_enumDestructableRadius, this.x + bj_enumDestructableRadius, this.y + bj_enumDestructableRadius)
set bj_enumDestructableRadius = this.collision*this.collision
set Missile.temp = this
set Missile.tempDist = bj_enumDestructableRadius + 1
static if thistype.onDestructableFilter.exists then
call EnumDestructablesInRect(Missile.enumRect, thistype.destCondition_p, function Missile.actionFuncDest)
else
call EnumDestructablesInRect(Missile.enumRect, null, function Missile.actionFuncDest)
endif
if (bj_destRandomCurrentPick != null) and thistype.onDestructable(this, bj_destRandomCurrentPick) then
call missileTerminateP(this)
endif
endif
endif
if (this.recycle) and (this.allocated) then
static if thistype.onCollide.exists then
static if thistype.onFinish.exists then
if (this.target == null) then
if thistype.onFinish(this) then
call thistype.missileTerminateP(this)
else
set this.recycle = false
endif
elseif thistype.onCollide(this, this.target) then
call thistype.missileTerminateP(this)
else
set this.recycle = false
endif
else
if (this.target == null) then
call thistype.missileTerminateP(this)
elseif thistype.onCollide(this, this.target) then
call thistype.missileTerminateP(this)
else
set this.recycle = false
endif
endif
else
static if thistype.onFinish.exists then
if thistype.onFinish(this) then
call thistype.missileTerminateP(this)
else
set this.recycle = false
endif
else
call thistype.missileTerminateP(this)
endif
endif
else
// Does not run after recycle was true
static if thistype.onTerrain.exists then
if (this.allocated) and (this.z < 0) and thistype.onTerrain(this) then
static if thistype.onFinish.exists then
if thistype.onFinish(this) then
call missileTerminateP(this)
endif
else
call missileTerminateP(this)
endif
endif
endif
// Runs last
static if thistype.onPeriod.exists then
if (this.allocated) and thistype.onPeriod(this) then
static if thistype.onFinish.exists then
if thistype.onFinish(this) then
call missileTerminateP(this)
endif
else
call missileTerminateP(this)
endif
endif
endif
endif
endif
set this = node
endloop
set u = null
return false
endmethod
static method launch takes Missile missile returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(missile.launched, "thistype", "launch", "missile.launched", missile, "Missile Was Already Launched Before!")
endif
call missileStack[thistype.typeid].insertEnd(missile)
call StartPeriodic(thistype.typeid)
debug set missile.launched = true
endmethod
private static method onInit takes nothing returns nothing
set missileStack[thistype.typeid] = Missile.allocateCollection()
call MissileCreateExpression(thistype.typeid, function thistype.missileIterateP)
static if thistype.onDestructableFilter.exists then
set thistype.destCondition_p = Condition(function thistype.onDestructableFilter)
endif
endmethod
endmodule
//! textmacro WRITE_MISSILE_POSITION_CODE
struct MissilePosition extends array
private static integer array recycler
private static integer alloc = 0
readonly static location loc = Location(0., 0.)
readonly real x
readonly real y
readonly real z
readonly real angle
readonly real distance
readonly real slope
private thistype ref
private static method math takes thistype a, thistype b returns nothing
set a.angle = Atan2(b.y - a.y, b.x - a.x)
set a.distance = SquareRoot((b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y))
if (a.distance != 0) then
set a.slope = (b.z - a.z)/a.distance
else// What now??? infinity. I decide we do this math again!
call a.move(a.x + .0001, a.y, a.z)
return
endif
set b.angle = a.angle + bj_PI
set b.distance = a.distance
set b.slope = -a.slope
endmethod
static method link takes thistype a, thistype b returns nothing
set a.ref = b
set b.ref = a
call math(a, b)
endmethod
method move takes real toX, real toY, real toZ returns nothing
call MoveLocation(MissilePosition.loc, toX, toY)
set x = toX
set y = toY
set z = toZ + GetLocationZ(MissilePosition.loc)
if (ref != this) then
call math(this, ref)
endif
endmethod
method destroy takes nothing returns nothing
set recycler[this] = recycler[0]
set recycler[0] = this
endmethod
static method create takes real x, real y, real z returns MissilePosition
local thistype this = recycler[0]
if (0 == this) then
set alloc = alloc + 1
set this = alloc
else
set recycler[0] = recycler[this]
endif
set ref = this
call move(x, y, z)
return this
endmethod
endstruct
//! endtextmacro
//! textmacro WRITE_MISSILE_RECYCLE_BIN takes DO_THIS, AFTER_TIME
static if $DO_THIS$ then
// In order to play a proper death animation,
// RecycleBin to recycles missiles after
// DEFAULT_DEATH_ANIMATION_TIME
private struct RecycleBin extends array
private static timer t = CreateTimer()
private static integer alloc = 0
private static unit array dummy
private static real array time
private static method handlerFunc takes nothing returns nothing
local integer index = 0
loop
exitwhen index == thistype.alloc
set thistype.time[index] = thistype.time[index] - 1
if (0 >= thistype.time[index]) then
static if LIBRARY_MissileRecycler then
call RecycleMissile(thistype.dummy[index])
elseif Dummy.create.exists and LIBRARY_Dummy then
call Dummy[thistype.dummy[index]].destroy()
elseif LIBRARY_xedummy and xedummy.release.exists then
call xedummy.release(thistype.dummy[index])
else
call RemoveUnit(thistype.dummy[index])
endif
set thistype.dummy[index] = null
set thistype.alloc = thistype.alloc - 1
set thistype.dummy[index] = thistype.dummy[thistype.alloc]
set thistype.time[index] = thistype.time[thistype.alloc]
set thistype.dummy[thistype.alloc] = null
set index = index - 1
if (0 == thistype.alloc) then
call PauseTimer(thistype.t)
endif
endif
set index = index + 1
endloop
endmethod
static method recycle takes unit toRecycle returns nothing
if (0 == thistype.alloc) then
call TimerStart(thistype.t, 1., true, function thistype.handlerFunc)
endif
set thistype.dummy[alloc] = toRecycle
set thistype.time[alloc] = $AFTER_TIME$ + TimerGetRemaining(thistype.t)
set thistype.alloc = thistype.alloc + 1
endmethod
endstruct
endif
//! endtextmacro
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope MagnumBarrage
/* Magnum Barrage v1.1
Copy:
Imports
- dummy.mdx
Objects
- dummy unit
- pause ability
- magnum barrage ability
Requires:
Magtheridon
- RegisterPlayerUnitEvent http://hiveworkshop.com/forums/showthread.php?t=203338
Nestharus
- Alloc https://github.com/nestharus/JASS/tree/master/jass/Systems/Alloc
- SharedList https://github.com/nestharus/JASS/tree/master/jass/Data%20Structures/SharedList
- ErrorMessage https://github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage
Cohadar
- TT http://www.thehelper.net/threads/timer-ticker.68189/
TriggerHappy
- TimedHandles http://www.wc3c.net/showthread.php?t=105456
Bribe
- SpellEffectEvent http://www.hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/
- Table http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
Changelog:
v1.0
- Initial upload
v1.1
- Code minor cleanups
- Code Documentation
- Now Requires TimedHandles to make sure missiles don't bug out groupEnum
- Custom FilterUnit function for customizable filter
Configure:
ABIL_ID to magnum barrage ability id
PAUSE_ABIL_ID to pause ability id
DUMMY_ID to dummy unit id
PAUSE_ABIL_ORDER_STRING is the order string for PAUSE_ABIL_ID
CHARGE_FX are the offsets for the charge fx, because it the fx is attached toa dummy unit, you have to custom position it with each unit.
MISSILE_Z is the height of the missile.
MISSILE_FX is the string path of the effect missiles take
CHARGE_FX is the string path of the effect used to charge up the ability
EXPLODE_FX is the string path of the explosion effect
LOAD_PER_SECOND is how many missiles the barrage loads up per second of charging
FIRE_PER_SECOND is how many missiles are discharged per second upon releasing
MISSILE_SIZE is how big the missiles are
COLLISION_AOE is how big the missile collision radius is
CAST_AOE is the aoe within missiles should be fired at
SPLASH_AOE is how much area the damage should splash within
SPLASH_DAMAGE is the ratio of damage that should be dealt
SPEED is how fast the missile moves
DAMAGE is how much damage is dealt upon collision
STATS is how much stats is factored into the damage formula
FILTER_UNIT what kind of units you want to target
** by default filters out alive enemy units, then proceeds to check your filter **
*/
globals
public constant integer ABIL_ID = 'A000'
public constant integer PAUSE_ABIL_ID = 'A001'
public constant integer DUMMY_ID = 'e001'
public constant string PAUSE_ABIL_ORDER_STRING = "carrionswarm"
public constant real CHARGE_FX_X = 60.
public constant real CHARGE_FX_Y = 60.
public constant real CHARGE_FX_Z = 60.
public constant real MISSILE_Z = 60.
public constant string MISSILE_FX = "Abilities\\Weapons\\SearingArrow\\SearingArrowMissile.mdl"
public constant string CHARGE_FX = "Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget.mdl"
public constant string EXPLODE_FX = "abilities\\weapons\\catapult\\catapultmissile.mdl"
endglobals
private constant function LOAD_PER_SECOND takes integer i returns real
return 6 + 1.5*i
endfunction
private constant function FIRE_PER_SECOND takes integer i returns real
return 12.
endfunction
private constant function MISSILE_SIZE takes integer i returns real
return .8 + .35*i
endfunction
private constant function COLLISION_AOE takes integer i returns real
return 100. + 20*i
endfunction
private constant function CAST_AOE takes integer i returns real
return 100 + 100.*i
endfunction
private constant function SPLASH_AOE takes integer i returns real
return 100 + 70.*i
endfunction
private constant function SPLASH_DAMAGE takes integer i returns real
return .65
endfunction
private constant function SPEED takes integer i returns real
return 1000 +400.*i
endfunction
private constant function DAMAGE takes integer i, integer stats returns real
return 5.*i + stats*i*.35
endfunction
private constant function STATS takes integer str, integer agi, integer int returns integer
return agi
endfunction
private function FilterUnit takes unit u returns boolean
return IsUnitType(u, UNIT_TYPE_HERO) or IsUnitType(u, UNIT_TYPE_GROUND)
endfunction
// End of COnfigurables
// ================================================================================
public struct Magnum extends array
implement SharedList
unit cast // Casting Unit
real dmg // Damage Dealt upon Explosion
unit mis // Missile Unit
effect fx // Effect of charging model
real currentDist // Distance curently traveled
real splashAoe // Aoe of Explosion
real splashDmg // Splash Damage Factor
real speed // Speed of missile
real finalDist // Maximum Distance
real collisionSize // Collision Size
real cos // Cos of angle
real sin // Sin of angle
static group g = CreateGroup()
static unit fog = null
static player filterPlayer = null
static thistype l
static timer tmr = CreateTimer()
static real period = 0.025
// By default pre filters alive, enemy units
static method filter takes nothing returns boolean
return IsUnitEnemy(GetFilterUnit(), filterPlayer) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) and GetWidgetLife(GetFilterUnit()) > .405
endmethod
// Explode method separated to allow flexible explosions
// Can explode with or without a target
method explode takes unit hitTarget returns nothing
// Set the effect coordinates by default to the location of the missile
local real sx = GetUnitX(.mis)
local real sy = GetUnitY(.mis)
// First of Group loop to deal damage to all filtered units
set filterPlayer = GetOwningPlayer(.cast)
call GroupEnumUnitsInRange(g, GetUnitX(.mis), GetUnitY(.mis), .splashAoe, function thistype.filter)
set fog = FirstOfGroup(g)
loop
exitwhen fog == null
// For now only affect non target units
if (fog != hitTarget and FilterUnit(fog)) then
call UnitDamageTarget(.cast, fog, .dmg *.splashDmg, false, false, null, null, null)
endif
call GroupRemoveUnit(g, fog)
set fog = FirstOfGroup(g)
endloop
// If there is a target to explode on, change the effect coordinates and deal full damage to the target
if (hitTarget != null) then
call UnitDamageTarget(.cast, hitTarget, .dmg, false, false, null, null, null)
set sx = GetUnitX(hitTarget)
set sy = GetUnitY(hitTarget)
endif
// Add explosion effect, remove missile, null handles and remove node
call DestroyEffect(AddSpecialEffect(EXPLODE_FX, sx, sy))
call RemoveUnitTimed(.mis, 1.0)
call DestroyEffect(.fx)
set .fx = null
set .mis = null
set .cast = null
call .remove()
endmethod
// Separated a method to check for target for better readability - but probably could be inlined
method checkTarget takes nothing returns boolean
// First of Group to check for a valid target
set filterPlayer = GetOwningPlayer(.cast)
call GroupEnumUnitsInRange(g, GetUnitX(.mis), GetUnitY(.mis), .collisionSize, function thistype.filter)
set fog = FirstOfGroup(g)
loop
exitwhen fog == null
// Check if unit is valid, if so clear the group and exit the loop
if (fog != null and FilterUnit(fog)) then
call .explode(fog)
call GroupClear(g)
set fog = null
return true
// Else keep checking
else
call GroupRemoveUnit(g, fog)
set fog = FirstOfGroup(g)
endif
endloop
return false
endmethod
// Periodic Method
static method onLoop takes nothing returns nothing
local thistype this = l.first
local thistype nn
// Loop through every node in the list
loop
// Safety first!
exitwhen this == l.sentinel
set nn = .next
// If the next movement is the last, explode with no target
if (.currentDist + .speed > .finalDist) then
// Set unit to accurate final coordinates
call SetUnitX(.mis, GetUnitX(.mis)+ (.finalDist - .currentDist) * .cos)
call SetUnitY(.mis, GetUnitY(.mis)+ (.finalDist - .currentDist) * .sin)
// If there are no valid targets, explode without one
if (not .checkTarget()) then
call .explode(null)
endif
else
// Move coordinates and update distance traveled info
call SetUnitX(.mis, GetUnitX(.mis)+ (.speed) * .cos)
call SetUnitY(.mis, GetUnitY(.mis)+ (.speed) * .sin)
set .currentDist = .currentDist + .speed
// Also check for a valid target
call .checkTarget()
endif
set this = nn
endloop
if (l.first == l.sentinel) then
call PauseTimer(tmr)
endif
endmethod
// Method used to create, setup and launch missiles
static method fireAt takes unit caster, real dmg, real x, real y, real sx, real sy, integer lvl, /*
*/real speed, real colSize, real splDmg, real splAoe, real size returns nothing
// Some simple math
local real yy = sy - y
local real xx = sx - x
local real direction = Atan2(yy, xx)
// New node
local thistype this = l.enqueue()
// Setup variables
set .cast = caster
set .dmg = dmg
set .collisionSize = colSize
set .splashDmg = splDmg
set .splashAoe = splAoe
set .cos = Cos(direction)
set .sin = Sin(direction)
set .mis = CreateUnit(GetOwningPlayer(.cast), DUMMY_ID, x, y, direction*bj_RADTODEG)
call SetUnitFlyHeight(.mis, MISSILE_Z, 0.0)
set .fx = AddSpecialEffectTarget(MISSILE_FX, .mis, "origin")
call SetUnitScale(.mis, size, size, size)
set .finalDist = SquareRoot(xx*xx+yy*yy)
set .currentDist = 0.0
set .speed = speed
if (l.first == this) then
call TimerStart(tmr, period, true, function thistype.onLoop)
endif
endmethod
// onInit list creation
static method onInit takes nothing returns nothing
set l = create()
endmethod
endstruct
public struct Data extends array
implement Alloc
boolean stop // Stop charging, start firing
unit cast // Caster unit
unit fx // Charge effect dummy unit
effect fxx // FX model attached to dummy unit
real sx // Spell Starting LocationX
real sy // Spell Starting LocationY
real aoe // Aoe of Barrage
real dmg // Damage
real speed // Misile Speed
real missileSize // Missile Size
real collisionSize // Missile Collision Size
real splashDmg // Splash Damage Factor
real splashAoe // Splash Aoe
real lps // Load Per Second
real fps // Fire Per Second
real time // Time Charged
integer lvl // Ability Level
static Table tb
// Method for rapid firing missiles until charge duration runs out
static method rapidFire takes nothing returns boolean
// Get Index
local thistype this = TT_GetData()
local real rx
local real ry
local real rd
local real rr
// If there is enough charge time keep firing
if (.time > 0) then
set rr = GetRandomReal(0, 360) * bj_DEGTORAD
set rd = GetRandomReal(0, .aoe/2.)
set rx = .sx + rd * Cos(rr)
set ry = .sy + rd * Sin(rr)
call SetUnitAnimation(.cast, "attack")
call Magnum.fireAt(.cast, .dmg, GetUnitX(.fx),GetUnitY(.fx), rx, ry, .lvl, .speed, .collisionSize, .splashDmg, .splashAoe, .missileSize)
// Else stop firing, clear/null handles, reset everything and stop timer
else
call UnitRemoveAbility(.cast, PAUSE_ABIL_ID)
call KillUnit(.fx)
call DestroyEffect(.fxx)
call tb.integer.remove(GetHandleId(.cast))
call SetUnitPropWindow(.cast, GetUnitDefaultPropWindow(.cast) * bj_DEGTORAD)
set .fx = null
set .fxx = null
set .cast = null
call .deallocate()
return true
endif
// Remove timed based on fire rate
set .time = .time - .fps
// Set charge effect model unit scale to time, so as spell runs the unit gets smaller
call SetUnitScale(.fx, .time, .time, .time)
return false
endmethod
// Function that takes care of units while they are charging
static method charging takes nothing returns boolean
local thistype this = TT_GetData()
// If unit should stop charging, starting rapid firing at fire rate period and stop this timer
// Also the "stop" order sometimes disallows the pause ability from being added, so by adding another command here you can
// make sure the unit will be paused properly.
if (.stop) then
call IssueImmediateOrder(.cast, PAUSE_ABIL_ORDER_STRING)
call TT_StartEx(function thistype.rapidFire, this, .fps)
return true
endif
// Increase the charge duration and set the charge effect model unit scale
set .time = .time + .lps
call SetUnitScale(.fx, .time, .time, .time)
return false
endmethod
// Function fired on cast
static method onCast takes nothing returns nothing
// Allocation and some pre instance variables
local thistype this = allocate()
local unit u = GetTriggerUnit()
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real dir = Atan2(GetSpellTargetY()-y,GetSpellTargetX()-x)
local integer lvl = GetUnitAbilityLevel(u, ABIL_ID)
local integer stats = STATS(GetHeroStr(u, true), GetHeroAgi(u, true), GetHeroInt(u, true))
// Setup some variables, precalculate and save all numbers so that we don't have to re calculate them later during
// missile creation
call SetUnitPropWindow(u, 0.0)
set tb.integer[GetHandleId(u)] = this
set .stop = false
set .cast = u
set .fx = CreateUnit(GetOwningPlayer(.cast), DUMMY_ID, x+60*Cos(dir), y+60*Sin(dir), dir*bj_RADTODEG)
call SetUnitFlyHeight(.fx, CHARGE_FX_Z, 0.0)
set .fxx = AddSpecialEffectTarget(CHARGE_FX, .fx, "origin")
call SetUnitScale(.fx, 0, 0, 0)
set .sx = GetSpellTargetX()
set .sy = GetSpellTargetY()
set .dmg = DAMAGE(lvl, stats)
set .aoe = CAST_AOE(lvl)
set .lps = 1./LOAD_PER_SECOND(lvl)
set .fps = 1./FIRE_PER_SECOND(lvl)
set .speed = SPEED(lvl) * Magnum.period
set .missileSize = MISSILE_SIZE(lvl)
set .collisionSize = COLLISION_AOE(lvl)
set .splashDmg = SPLASH_DAMAGE(lvl)
set .splashAoe = SPLASH_AOE(lvl)
set .time = 0.0
set .lvl = lvl
// Start the charging function loop
call TT_StartEx(function thistype.charging, this, .lps)
set u = null
endmethod
// When spell is canceled or finished
static method onCancel takes nothing returns nothing
local unit u = GetTriggerUnit()
local thistype this = tb.integer[GetHandleId(u)]
// If canceld ability is Magnum Barrage, unit has a valid instance, and said instance has not started firing yet...
if GetSpellAbilityId() == ABIL_ID or (this != 0 and not .stop) then
// Stop charging, start firing and pause unit
// Note that if you stop your caster by ordering "stop" then the pause will not work properly thus
// We re issue the pause ability command when the charging function ceases to make sure unit pauses properly
set .stop = true
call UnitAddAbility(.cast, PAUSE_ABIL_ID)
call IssueImmediateOrder(.cast, PAUSE_ABIL_ORDER_STRING)
endif
set u = null
endmethod
// OnInit
static method onInit takes nothing returns nothing
set tb = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onCast)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function thistype.onCancel)
endmethod
endstruct
endscope
//TESH.scrollpos=151
//TESH.alwaysfold=0
scope MagnumBarrage
/* Magnum Barrage v1.0
Copy:
Imports
- dummy.mdx
Objects
- dummy unit
- pause ability
- magnum barrage ability
Requires:
Magtheridon
- RegisterPlayerUnitEvent
Nestharus
- Alloc
- SharedList
- ErrorMessage
Cohadar
- TT
Bribe
- SpellEffectEvent
- Table
Configure:
ABIL_ID to magnum barrage ability id
PAUSE_ABIL_ID to pause ability id
DUMMY_ID to dummy unit id
PAUSE_ABIL_ORDER_STRING is the order string for PAUSE_ABIL_ID
CHARGE_FX are the offsets for the charge fx, because it the fx is attached toa dummy unit, you have to custom position it with each unit.
MISSILE_Z is the height of the missile.
MISSILE_FX is the string path of the effect missiles take
CHARGE_FX is the string path of the effect used to charge up the ability
EXPLODE_FX is the string path of the explosion effect
LOAD_PER_SECOND is how many missiles the barrage loads up per second of charging
FIRE_PER_SECOND is how many missiles are discharged per second upon releasing
MISSILE_SIZE is how big the missiles are
COLLISION_AOE is how big the missile collision radius is
CAST_AOE is the aoe within missiles should be fired at
SPLASH_AOE is how much area the damage should splash within
SPLASH_DAMAGE is the ratio of damage that should be dealt
SPEED is how fast the missile moves
DAMAGE is how much damage is dealt upon collision
STATS is how much stats is factored into the damage formula
*/
globals
public constant integer ABIL_ID = 'A000'
public constant integer PAUSE_ABIL_ID = 'A001'
public constant integer DUMMY_ID = 'e001'
public constant string PAUSE_ABIL_ORDER_STRING = "carrionswarm"
public constant real CHARGE_FX_X = 60.
public constant real CHARGE_FX_Y = 60.
public constant real CHARGE_FX_Z = 60.
public constant real MISSILE_Z = 60.
public constant string MISSILE_FX = "Abilities\\Weapons\\SearingArrow\\SearingArrowMissile.mdl"
public constant string CHARGE_FX = "Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget.mdl"
public constant string EXPLODE_FX = "abilities\\weapons\\catapult\\catapultmissile.mdl"
endglobals
private constant function LOAD_PER_SECOND takes integer i returns real
return 6 + 1.5*i
endfunction
private constant function FIRE_PER_SECOND takes integer i returns real
return 12.
endfunction
private constant function MISSILE_SIZE takes integer i returns real
return .8 + .35*i
endfunction
private constant function COLLISION_AOE takes integer i returns real
return 100. + 20*i
endfunction
private constant function CAST_AOE takes integer i returns real
return 100 + 100.*i
endfunction
private constant function SPLASH_AOE takes integer i returns real
return 100 + 70.*i
endfunction
private constant function SPLASH_DAMAGE takes integer i returns real
return .65
endfunction
private constant function SPEED takes integer i returns real
return 1000 +400.*i
endfunction
private constant function DAMAGE takes integer i, integer stats returns real
return 5.*i + stats*i*.35
endfunction
private constant function STATS takes integer str, integer agi, integer int returns integer
return agi
endfunction
// End of Configurables
// ================================================================================
public struct Magnum extends array
unit cast
real dmg
real sx
real sy
real aoe
real splash
boolean explode
static group g = CreateGroup()
static unit fog = null
static player filterPlayer = null
static method filterFunc takes nothing returns boolean
return IsUnitEnemy(GetFilterUnit(), filterPlayer) and GetWidgetLife(GetFilterUnit())>.405 and not IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD)
endmethod
method explodeMissile takes unit hitTarget returns nothing
local real x = .sx
local real y = .sy
if hitTarget != null then
set x = GetUnitX(hitTarget)
set y = GetUnitY(hitTarget)
call UnitDamageTarget(.cast, hitTarget, .dmg, false, false, null, null, null)
endif
call DestroyEffect(AddSpecialEffect(EXPLODE_FX, x, y))
set filterPlayer = GetOwningPlayer(.cast)
call GroupEnumUnitsInRange(g, x, y, .aoe, function thistype.filterFunc)
set fog = FirstOfGroup(g)
loop
exitwhen fog == null
if hitTarget != fog then
call UnitDamageTarget(.cast, fog, .splash * .dmg, false, false, null, null, null)
endif
call GroupRemoveUnit(g, fog)
set fog = FirstOfGroup(g)
endloop
set .explode = false
endmethod
static method onCollide takes Missile mis, unit hitTarget returns boolean
local thistype this = mis.data
if IsUnitEnemy(hitTarget, GetOwningPlayer(.cast)) and GetWidgetLife(hitTarget)>.405 and not IsUnitType(hitTarget, UNIT_TYPE_DEAD) then
call .explodeMissile(hitTarget)
return true
endif
return false
endmethod
static method onRemove takes Missile mis returns boolean
return true
endmethod
static method onFinish takes Missile mis returns boolean
local thistype this = mis.data
if (.explode) then
call .explodeMissile(null)
endif
set .cast = null
return true
endmethod
implement MissileStruct
endstruct
public struct Data extends array
implement Alloc
boolean stop
unit cast
unit fx
effect fxx
real ix
real iy
real sx
real sy
real aoe
real dmg
real lps
real fps
real time
real speed
real scale
real splashDmg
real splashAoe
real collisionAoe
integer lvl
static Table tb
static method rapidFire takes nothing returns boolean
local thistype this = TT_GetData()
local real rx
local real ry
local real rd
local real rr
local real xx
local real yy
local real dist
local Missile mis
local Magnum mag
if (.time > 0) then
set rr = GetRandomReal(0, 360) * bj_DEGTORAD
set rd = GetRandomReal(0, .aoe/2.)
set rx = .sx + rd * Cos(rr)
set ry = .sy + rd * Sin(rr)
set xx = rx - .ix
set yy = ry - .iy
call SetUnitAnimation(.cast, "attack")
set dist = SquareRoot(xx*xx+yy*yy)
set mis = Missile.create(.ix, .iy, MISSILE_Z, Atan2(yy, xx), dist, MISSILE_Z)
set mis.speed = .speed
set mis.model = MISSILE_FX
set mis.arc = .10
set mis.scale = .scale
set mis.collision = .collisionAoe
set mag = mis
set mag.cast = .cast
set mag.dmg = .dmg
set mag.splash = .splashDmg
set mag.aoe = .splashAoe
set mag.sx = rx
set mag.sy = ry
set mag.explode = true
set mis.data = mag
call Magnum.launch(mis)
else
call UnitRemoveAbility(.cast, PAUSE_ABIL_ID)
call KillUnit(.fx)
set .fx = null
call DestroyEffect(.fxx)
set .fxx = null
call tb.integer.remove(GetHandleId(.cast))
call SetUnitPropWindow(.cast, GetUnitDefaultPropWindow(.cast) * bj_DEGTORAD)
set .cast = null
return true
endif
set .time = .time - .fps
call SetUnitScale(.fx, .time, .time, .time)
return false
endmethod
static method onLoop takes nothing returns boolean
local thistype this = TT_GetData()
if (.stop) then
call IssueImmediateOrder(.cast, PAUSE_ABIL_ORDER_STRING)
call TT_StartEx(function thistype.rapidFire, this, .fps)
return true
endif
set .time = .time + .lps
call SetUnitScale(.fx, .time, .time, .time)
return false
endmethod
static method onCast takes nothing returns nothing
local thistype this = allocate()
local unit u = GetTriggerUnit()
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real dir = Atan2(GetSpellTargetY()-y,GetSpellTargetX()-x)
local integer lvl = GetUnitAbilityLevel(u, ABIL_ID)
local integer stats = STATS(GetHeroStr(u, true), GetHeroAgi(u, true), GetHeroInt(u, true))
call SetUnitPropWindow(u, 0.0)
set tb.integer[GetHandleId(u)] = this
set .stop = false
set .cast = u
set .ix = x+60*Cos(dir)
set .iy = y+60*Sin(dir)
set .fx = CreateUnit(GetOwningPlayer(.cast), DUMMY_ID, .ix, .iy, dir*bj_RADTODEG)
call SetUnitFlyHeight(.fx, CHARGE_FX_Z, 0.0)
set .fxx = AddSpecialEffectTarget(CHARGE_FX, .fx, "origin")
call SetUnitScale(.fx, 0, 0, 0)
set .sx = GetSpellTargetX()
set .sy = GetSpellTargetY()
set .dmg = DAMAGE(lvl, stats)
set .aoe = CAST_AOE(lvl)
set .lps = 1./LOAD_PER_SECOND(lvl)
set .fps = 1./FIRE_PER_SECOND(lvl)
set .speed = SPEED(lvl)/32.
set .splashDmg = SPLASH_DAMAGE(lvl)
set .splashAoe = SPLASH_AOE(lvl)
set .collisionAoe = COLLISION_AOE(lvl)
set .scale = MISSILE_SIZE(lvl)
set .time = 0.0
set .lvl = lvl
call TT_StartEx(function thistype.onLoop, this, .lps)
set u = null
endmethod
static method onCancel takes nothing returns nothing
local unit u = GetTriggerUnit()
local thistype this = tb.integer[GetHandleId(u)]
if GetSpellAbilityId() == ABIL_ID or (this != 0 and not .stop) then
set .stop = true
call UnitAddAbility(.cast, PAUSE_ABIL_ID)
call IssueImmediateOrder(.cast, PAUSE_ABIL_ORDER_STRING)
endif
set u = null
endmethod
static method onInit takes nothing returns nothing
set tb = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onCast)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function thistype.onCancel)
endmethod
endstruct
endscope
//TESH.scrollpos=8
//TESH.alwaysfold=0
// Lab 1 explains the usage of:
//
// static method onDestructableFilter takes nothing returns boolean
// static method onDestructable takes Missile this, destructable hit returns boolean
scope LAB1 initializer Init
private struct OnDestructableExample extends array
// Filter function turns out to be very useful. Filter those destructable which you wish to hit.
// example: alive, dead, IsTree, ..
private static method onDestructableFilter takes nothing returns boolean
return GetDestructableTypeId(GetFilterDestructable()) == 'LTlt'
endmethod
// Declare missile behaviours above the MissileStruct implementation.
// Runs only for filtered destructables
private static method onDestructable takes Missile this, destructable hit returns boolean
//call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5, "Info: Missile " + I2S(this) + " collided with a destructable")
call SetDestructableAnimation(hit, "stand hit")
return true// will destroy the missile.
endmethod
// Implement as module below declared methods.
implement MissileStruct
endstruct
function fireArrow1 takes nothing returns nothing
local Missile m
call TriggerSleepAction(.5)
call DestroyEffect(AddSpecialEffect("UI\\Feedback\\Confirmation\\Confirmation.mdl", -1920, 300))
set m = Missile.create(-1920, -670, 50, 90*bj_DEGTORAD, 1000, 50)
set m.speed = 15.
set m.model = "Abilities\\Weapons\\Arrow\\ArrowMissile.mdl"
set m.arc = GetRandomReal(0, 1.)
set m.curve = .15//GetRandomReal(-.15, .15)
set m.collision = 32
call OnDestructableExample.launch(m)
endfunction
//! runtextmacro LAB_MAIN("1", "-1920", "-700", "90", "ON DESTRUCTABLE", ".5")
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
// Lab 2 shows how missiles behave over canions:
//
//
scope LAB2 initializer Init
private struct PhysiksExample extends array
implement MissileStruct
endstruct
function fireArrow2 takes nothing returns nothing
local Missile m
call TriggerSleepAction(.5)
call DestroyEffect(AddSpecialEffect("UI\\Feedback\\Confirmation\\Confirmation.mdl", -1920, 300))
set m = Missile.create(-1150, -670, 50, 90*bj_DEGTORAD, 1000, 50)
set m.speed = 15.
set m.model = "Abilities\\Weapons\\Arrow\\ArrowMissile.mdl"
set m.arc = GetRandomReal(0, 1.)
call PhysiksExample.launch(m)
endfunction
//! runtextmacro LAB_MAIN("2", "-1150", "-700", "90", "PHYSIKS", "1.")
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
// Lab 3 shows how to use arc and curve:
//
// method operator curve= takes real value returns nothing
// method operator arc= takes real value returns nothing
// Note: Curve is a very sensitive function, just enter very small numbers in order to achieve good results.
scope LAB3 initializer Init
private struct ArcCurveExample extends array
implement MissileStruct
endstruct
function fireArrow3 takes nothing returns nothing
local Missile m
call SetUnitTimeScale(LAB_UNIT[3], 5)
call TriggerSleepAction(.1)
call SetUnitTimeScale(LAB_UNIT[3], 1)
set m = Missile.create(-505, -670, 50, 90*bj_DEGTORAD, 1000, 50)
set m.speed = 15.
set m.model = "Abilities\\Weapons\\Arrow\\ArrowMissile.mdl"
set m.arc = GetRandomReal(0, 1.0)
set m.curve = GetRandomReal(-0.1, 0.1)
call ArcCurveExample.launch(m)
endfunction
//! runtextmacro LAB_MAIN("3", "-505", "-700", "90", "ARC & CURVE", ".2")
endscope
//TESH.scrollpos=13
//TESH.alwaysfold=0
// Lab 4 shows what target does:
//
// unit target
scope LAB4 initializer Init
globals
private unit Footman = null
endglobals
private struct TargetExample extends array
private static method onRemove takes Missile this returns boolean
// Runs when Missiles get destroyed.
return true
endmethod
private static method onFinish takes Missile this returns boolean
// Runs when Missiles get recycled
return true
endmethod
implement MissileStruct
private static method onInit takes nothing returns nothing
set Footman = CreateUnit(Player(15), 'hfoo', 0, 500, 0)
call IssuePointOrder(Footman, "patrol", 200, 300)
endmethod
endstruct
function fireArrow4 takes nothing returns nothing
local Missile m
call TriggerSleepAction(.5)
set m = Missile.create(140, -670, 50, 90*bj_DEGTORAD, 2000, 50)
set m.speed = 15.
set m.model = "Abilities\\Weapons\\Arrow\\ArrowMissile.mdl"
set m.arc = .8
set m.curve = 0.0
set m.target = Footman
call TargetExample.launch(m)
endfunction
//! runtextmacro LAB_MAIN("4", "140", "-700", "90", "TARGET UNIT", ".5")
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
// Lab 5 explains the usage of:
//
// static method onTerrain takes Missile this returns boolean
scope LAB5 initializer Init
private struct OnTerrainExample extends array
// Declare missile behaviours above the MissileStruct implementation.
private static method onTerrain takes Missile this returns boolean
//call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5, "Info: Missile " + I2S(this) + " collided with terrain")
if GetRandomInt(1, 10) > 5 then
call this.deflect(GetRandomReal(150, 1400), -700)// :)
return false// will not destroy the missile.
endif
return true// will destroy the missile.
endmethod
// Implement as module below declared methods.
implement MissileStruct
endstruct
function fireArrow5 takes nothing returns nothing
local Missile m
call TriggerSleepAction(.5)
call DestroyEffect(AddSpecialEffect("UI\\Feedback\\Confirmation\\Confirmation.mdl", 790, 300))
set m = Missile.create(790, -670, 50, 90*bj_DEGTORAD, 1000, 50)
set m.speed = 10.
set m.model = "Abilities\\Weapons\\LichMissile\\LichMissile.mdl"
set m.arc = 0.2
set m.collision = 32
call OnTerrainExample.launch(m)
endfunction
//! runtextmacro LAB_MAIN("5", "790", "-700", "90", "ON TERRAIN\n+ deflect(x, y)", "1.")
endscope
//TESH.scrollpos=14
//TESH.alwaysfold=0
// Lab 6 explains the usage of:
//
// static method onCollide takes Missile this, unit hit returns nothing
// set USE_COLLISION_Z_FILTER = true to consider z axis aswell. (in library Missile)
//
scope LAB6 initializer Init
native UnitAlive takes unit id returns boolean
private struct OnCollideExample extends array
// Declare missile behaviours above the MissileStruct implementation.
// Want this also? No problem!
private static method onTerrain takes Missile this returns boolean
return true
endmethod
private static method onCollide takes Missile this, unit hit returns boolean
if (hit == this.source) or not UnitAlive(hit) then
return false
endif
return true// will destroy the missile.
endmethod
implement MissileStruct
endstruct
function fireArrow6 takes nothing returns nothing
local Missile m
call TriggerSleepAction(.5)
call DestroyEffect(AddSpecialEffect("UI\\Feedback\\Confirmation\\Confirmation.mdl", 1430, 300))
set m = Missile.create(1430, -670, 50, 90*bj_DEGTORAD, 1000, 50)
set m.speed = 10.
set m.model = "Abilities\\Weapons\\DemonHunterMissile\\DemonHunterMissile.mdl"
set m.arc = GetRandomReal(0.2, 1)
set m.collision = 32
set m.source = LAB_UNIT[6]
call OnCollideExample.launch(m)
endfunction
//! runtextmacro LAB_MAIN("6", "1430", "-700", "90", "ON COLLIDE (Z?)", "1.")
endscope
//TESH.scrollpos=45
//TESH.alwaysfold=0
library_once IsPointInRectangle
//determines if point P is in rectangle ABCD
function IsPointInRectangle takes real ax, real ay, real bx, real by, real cx, real cy, real dx, real dy, real px, real py returns boolean
local real cross0 = (py-ay)*(bx-ax)-(px-ax)*(by-ay)
local real cross1 = (py-cy)*(ax-cx)-(px-cx)*(ay-cy)
local real cross4 = (py-dy)*(ax-dx)-(px-dx)*(ay-dy)
return ((cross0*cross1 >= 0) and (((py-by)*(cx-bx)-(px-bx)*(cy-by))*cross1 >= 0)) or ((cross0*cross4 >= 0) and (((py-by)*(dx-bx)-(px-bx)*(dy-by))*cross4 >= 0))
endfunction
endlibrary
// Lab 7 explains how to:
//
// detect destructables using no square pathing block (e.g. gates)
//
// Can be done by using IsPointInRectAngle
// Don't do if not absolutly wanted. This operation is heavy.
//
// I recommend to save gate coordinates in a hashtable and check if a
// normalized coordinate contains a gate. The load that destructable handle from the hashtable.
//
scope LAB7 initializer Init
private struct OnGateExample extends array
// Required. Use bj_destRandomCurrentPick.
// You have to know the facing angle of the destructable :(
private static method onDestructableFilter takes nothing returns boolean
local destructable d = GetFilterDestructable()
local real xd = GetDestructableX(d)
local real yd = GetDestructableY(d)
local integer id = GetDestructableTypeId(d)
local real angle = 270*bj_DEGTORAD
local real xMin = xd - 288
local real xMax = xd + 288
local real yMin = yd - 64*Sin(angle)
local real yMax = yd + 64*Sin(angle)
if (id == 'ATg1') and (bj_destRandomCurrentPick == null) then
if (GetDestructableOccluderHeight(d) >= Missile.temp.z) then
if IsPointInRectangle(xMin, yMin, xMin, yMax, xMax, yMax, xMax, yMin, Missile.temp.x, Missile.temp.y) then
set bj_destRandomCurrentPick = GetFilterDestructable()
endif
endif
endif
return true
endmethod
// Runs only for filtered destructables
private static method onDestructable takes Missile this, destructable hit returns boolean
call SetDestructableAnimation(hit, "stand hit")
return true// will destroy the missile.
endmethod
implement MissileStruct
endstruct
function fireArrow7 takes nothing returns nothing
local Missile m
local real random = GetRandomInt(-1000, 1000)
call TriggerSleepAction(.5)
call DestroyEffect(AddSpecialEffect("UI\\Feedback\\Confirmation\\Confirmation.mdl", 2070 + random, 300))
set m = Missile.createXYZ(2070, -670, 50, 2070 + random, 330, 50)
set m.speed = 10.
set m.model = "Abilities\\Weapons\\DemonHunterMissile\\DemonHunterMissile.mdl"
set m.arc = GetRandomReal(0.2, 1)
set m.collision = 32
set m.source = LAB_UNIT[7]
call OnGateExample.launch(m)
endfunction
//! runtextmacro LAB_MAIN("7", "2070", "-700", "90", "DETECTING GATES/nNOT RECOMMENDED", ".5")
endscope