Name | Type | is_array | initial_value |
//TESH.scrollpos=290
//TESH.alwaysfold=0
globals
//strange ones at bottom
constant integer ORDER_OFFSET=851970
constant integer ORDER_wandillusion=852274
constant integer ORDER_absorb=852529
constant integer ORDER_acidbomb=852662
constant integer ORDER_acolyteharvest=852185
constant integer ORDER_AImove=851988
constant integer ORDER_ambush=852131
constant integer ORDER_ancestralspirit=852490
constant integer ORDER_ancestralspirittarget=852491
constant integer ORDER_animatedead=852217
constant integer ORDER_antimagicshell=852186
constant integer ORDER_attack=851983
constant integer ORDER_attackground=851984
constant integer ORDER_attackonce=851985
constant integer ORDER_attributemodskill=852576
constant integer ORDER_auraunholy=852215
constant integer ORDER_auravampiric=852216
constant integer ORDER_autodispel=852132
constant integer ORDER_autodispeloff=852134
constant integer ORDER_autodispelon=852133
constant integer ORDER_autoentangle=852505
constant integer ORDER_autoentangleinstant=852506
constant integer ORDER_autoharvestgold=852021
constant integer ORDER_autoharvestlumber=852022
constant integer ORDER_avatar=852086
constant integer ORDER_avengerform=852531
constant integer ORDER_awaken=852466
constant integer ORDER_banish=852486
constant integer ORDER_barkskin=852135
constant integer ORDER_barkskinoff=852137
constant integer ORDER_barkskinon=852136
constant integer ORDER_battleroar=852099
constant integer ORDER_battlestations=852099
constant integer ORDER_bearform=852138
constant integer ORDER_berserk=852100
constant integer ORDER_blackarrow=852577
constant integer ORDER_blackarrowoff=852579
constant integer ORDER_blackarrowon=852578
constant integer ORDER_blight=852187
constant integer ORDER_blink=852525
constant integer ORDER_blizzard=852089
constant integer ORDER_bloodlust=852101
constant integer ORDER_bloodlustoff=852103
constant integer ORDER_bloodluston=852102
constant integer ORDER_board=852043
constant integer ORDER_breathoffire=852580
constant integer ORDER_breathoffrost=852560
constant integer ORDER_build=851994
constant integer ORDER_burrow=852533
constant integer ORDER_cannibalize=852188
constant integer ORDER_carrionscarabs=852551
constant integer ORDER_carrionscarabsinstant=852554
constant integer ORDER_carrionscarabsoff=852553
constant integer ORDER_carrionscarabson=852552
constant integer ORDER_carrionswarm=852218
constant integer ORDER_chainlightning=852119
constant integer ORDER_channel=852600
constant integer ORDER_charm=852581
constant integer ORDER_chemicalrage=852663
constant integer ORDER_cloudoffog=852473
constant integer ORDER_clusterrockets=852652
constant integer ORDER_coldarrows=852244
constant integer ORDER_coldarrowstarg=852243
constant integer ORDER_controlmagic=852474
constant integer ORDER_corporealform=852493
constant integer ORDER_corrosivebreath=852140
constant integer ORDER_coupleinstant=852508
constant integer ORDER_coupletarget=852507
constant integer ORDER_creepanimatedead=852246
constant integer ORDER_creepdevour=852247
constant integer ORDER_creepheal=852248
constant integer ORDER_creephealoff=852250
constant integer ORDER_creephealon=852249
constant integer ORDER_creepthunderbolt=852252
constant integer ORDER_creepthunderclap=852253
constant integer ORDER_cripple=852189
constant integer ORDER_curse=852190
constant integer ORDER_curseoff=852192
constant integer ORDER_curseon=852191
constant integer ORDER_cyclone=852144
constant integer ORDER_darkconversion=852228
constant integer ORDER_darkportal=852229
constant integer ORDER_darkritual=852219
constant integer ORDER_darksummoning=852220
constant integer ORDER_deathanddecay=852221
constant integer ORDER_deathcoil=852222
constant integer ORDER_deathpact=852223
constant integer ORDER_decouple=852509
constant integer ORDER_defend=852055
constant integer ORDER_detectaoe=852015
constant integer ORDER_detonate=852145
constant integer ORDER_devour=852104
constant integer ORDER_devourmagic=852536
constant integer ORDER_disassociate=852240
constant integer ORDER_disenchant=852495
constant integer ORDER_dismount=852470
constant integer ORDER_dispel=852057
constant integer ORDER_divineshield=852090
constant integer ORDER_doom=852583
constant integer ORDER_drain=852487
constant integer ORDER_dreadlordinferno=852224
constant integer ORDER_dropitem=852001
constant integer ORDER_drunkenhaze=852585
constant integer ORDER_earthquake=852121
constant integer ORDER_eattree=852146
constant integer ORDER_elementalfury=852586
constant integer ORDER_ensnare=852106
constant integer ORDER_ensnareoff=852108
constant integer ORDER_ensnareon=852107
constant integer ORDER_entangle=852147
constant integer ORDER_entangleinstant=852148
constant integer ORDER_entanglingroots=852171
constant integer ORDER_etherealform=852496
constant integer ORDER_evileye=852105
constant integer ORDER_faeriefire=852149
constant integer ORDER_faeriefireoff=852151
constant integer ORDER_faeriefireon=852150
constant integer ORDER_fanofknives=852526
constant integer ORDER_farsight=852122
constant integer ORDER_fingerofdeath=852230
constant integer ORDER_firebolt=852231
constant integer ORDER_flamestrike=852488
constant integer ORDER_flamingarrows=852174
constant integer ORDER_flamingarrowstarg=852173
constant integer ORDER_flamingattack=852540
constant integer ORDER_flamingattacktarg=852539
constant integer ORDER_flare=852060
constant integer ORDER_forceboard=852044
constant integer ORDER_forceofnature=852176
constant integer ORDER_forkedlightning=852586
constant integer ORDER_freezingbreath=852195
constant integer ORDER_frenzy=852561
constant integer ORDER_frenzyoff=852563
constant integer ORDER_frenzyon=852562
constant integer ORDER_frostarmor=852225
constant integer ORDER_frostarmoroff=852459
constant integer ORDER_frostarmoron=852458
constant integer ORDER_frostnova=852226
constant integer ORDER_getitem=851981
constant integer ORDER_gold2lumber=852233
constant integer ORDER_grabtree=852511
constant integer ORDER_harvest=852018
constant integer ORDER_heal=852063
constant integer ORDER_healingspray=852664
constant integer ORDER_healingward=852109
constant integer ORDER_healingwave=852501
constant integer ORDER_healoff=852065
constant integer ORDER_healon=852064
constant integer ORDER_hex=852502
constant integer ORDER_holdposition=851993
constant integer ORDER_holybolt=852092
constant integer ORDER_howlofterror=852588
constant integer ORDER_humanbuild=851995
constant integer ORDER_immolation=852177
constant integer ORDER_impale=852555
constant integer ORDER_incineratearrow=852670
constant integer ORDER_incineratearrowoff=852672
constant integer ORDER_incineratearrowon=852671
constant integer ORDER_inferno=852232
constant integer ORDER_innerfire=852066
constant integer ORDER_innerfireoff=852068
constant integer ORDER_innerfireon=852067
constant integer ORDER_instant=852200
constant integer ORDER_invisibility=852069
constant integer ORDER_lavamonster=852667
constant integer ORDER_lightningshield=852110
constant integer ORDER_load=852046
constant integer ORDER_loadarcher = 852142
constant integer ORDER_loadcorpse=852050
constant integer ORDER_loadcorpseinstant=852053
constant integer ORDER_locustswarm=852556
constant integer ORDER_lumber2gold=852234
constant integer ORDER_magicdefense=852478
constant integer ORDER_magicleash=852480
constant integer ORDER_magicundefense=852479
constant integer ORDER_manaburn=852179
constant integer ORDER_manaflareoff=852513
constant integer ORDER_manaflareon=852512
constant integer ORDER_manashieldoff=852590
constant integer ORDER_manashieldon=852589
constant integer ORDER_massteleport=852093
constant integer ORDER_mechanicalcritter=852564
constant integer ORDER_metamorphosis=852180
constant integer ORDER_militia=852072
constant integer ORDER_militiaconvert=852071
constant integer ORDER_militiaoff=852073
constant integer ORDER_militiaunconvert=852651
constant integer ORDER_mindrot=852565
constant integer ORDER_mirrorimage=852123
constant integer ORDER_monsoon=852591
constant integer ORDER_mount=852469
constant integer ORDER_mounthippogryph=852143
constant integer ORDER_move=851986
constant integer ORDER_nagabuild=852467
constant integer ORDER_neutraldetectaoe=852023
constant integer ORDER_neutralinteract=852566
constant integer ORDER_neutralspell=852630
constant integer ORDER_nightelfbuild=851997
constant integer ORDER_orcbuild=851996
constant integer ORDER_parasite=852601
constant integer ORDER_parasiteoff=852603
constant integer ORDER_parasiteon=852602
constant integer ORDER_patrol=851990
constant integer ORDER_phaseshift=852514
constant integer ORDER_phaseshiftinstant=852517
constant integer ORDER_phaseshiftoff=852516
constant integer ORDER_phaseshifton=852515
constant integer ORDER_phoenixfire=852481
constant integer ORDER_phoenixmorph=852482
constant integer ORDER_poisonarrows=852255
constant integer ORDER_poisonarrowstarg=852254
constant integer ORDER_polymorph=852074
constant integer ORDER_possession=852196
constant integer ORDER_preservation=852568
constant integer ORDER_purge=852111
constant integer ORDER_rainofchaos=852237
constant integer ORDER_rainoffire=852238
constant integer ORDER_raisedead=852197
constant integer ORDER_raisedeadoff=852199
constant integer ORDER_raisedeadon=852198
constant integer ORDER_ravenform=852155
constant integer ORDER_recharge=852157
constant integer ORDER_rechargeoff=852159
constant integer ORDER_rechargeon=852158
constant integer ORDER_rejuvination=852160
constant integer ORDER_renew=852161
constant integer ORDER_renewoff=852163
constant integer ORDER_renewon=852162
constant integer ORDER_repair=852024
constant integer ORDER_repairoff=852026
constant integer ORDER_repairon=852025
constant integer ORDER_replenish=852542
constant integer ORDER_replenishlife=852545
constant integer ORDER_replenishlifeoff=852547
constant integer ORDER_replenishlifeon=852546
constant integer ORDER_replenishmana=852548
constant integer ORDER_replenishmanaoff=852550
constant integer ORDER_replenishmanaon=852549
constant integer ORDER_replenishoff=852544
constant integer ORDER_replenishon=852543
constant integer ORDER_request_hero=852239
constant integer ORDER_requestsacrifice=852201
constant integer ORDER_restoration=852202
constant integer ORDER_restorationoff=852204
constant integer ORDER_restorationon=852203
constant integer ORDER_resumebuild=851999
constant integer ORDER_resumeharvesting=852017
constant integer ORDER_resurrection=852094
constant integer ORDER_returnresources=852020
constant integer ORDER_revenge=852241
constant integer ORDER_revive=852039
constant integer ORDER_roar=852164
constant integer ORDER_robogoblin=852656
constant integer ORDER_root=852165
constant integer ORDER_sacrifice=852205
constant integer ORDER_sanctuary=852569
constant integer ORDER_scout=852181
constant integer ORDER_selfdestruct=852040
constant integer ORDER_selfdestructoff=852042
constant integer ORDER_selfdestructon=852041
constant integer ORDER_sentinel=852182
constant integer ORDER_setrally=851980
constant integer ORDER_shadowsight=852570
constant integer ORDER_shadowstrike=852527
constant integer ORDER_shockwave=852125
constant integer ORDER_silence=852592
constant integer ORDER_sleep=852227
constant integer ORDER_slow=852075
constant integer ORDER_slowoff=852077
constant integer ORDER_slowon=852076
constant integer ORDER_smart=851971
constant integer ORDER_soulburn=852668
constant integer ORDER_soulpreservation=852242
constant integer ORDER_spellshield=852571
constant integer ORDER_spellshieldaoe=852572
constant integer ORDER_spellsteal=852483
constant integer ORDER_spellstealoff=852485
constant integer ORDER_spellstealon=852484
constant integer ORDER_spies=852235
constant integer ORDER_spiritlink=852499
constant integer ORDER_spiritofvengeance=852528
constant integer ORDER_spirittroll=852573
constant integer ORDER_spiritwolf=852126
constant integer ORDER_stampede=852593
constant integer ORDER_standdown=852113
constant integer ORDER_starfall=852183
constant integer ORDER_stasistrap=852114
constant integer ORDER_steal=852574
constant integer ORDER_stomp=852127
constant integer ORDER_stoneform=852206
constant integer ORDER_stop=851972
constant integer ORDER_submerge=852604
constant integer ORDER_summonfactory=852658
constant integer ORDER_summongrizzly=852594
constant integer ORDER_summonphoenix=852489
constant integer ORDER_summonquillbeast=852595
constant integer ORDER_summonwareagle=852596
constant integer ORDER_tankdroppilot=852079
constant integer ORDER_tankloadpilot=852080
constant integer ORDER_tankpilot=852081
constant integer ORDER_taunt=852520
constant integer ORDER_thunderbolt=852095
constant integer ORDER_thunderclap=852096
constant integer ORDER_tornado=852597
constant integer ORDER_townbelloff=852083
constant integer ORDER_townbellon=852082
constant integer ORDER_tranquility=852184
constant integer ORDER_transmute=852665
constant integer ORDER_unavatar=852087
constant integer ORDER_unavengerform=852532
constant integer ORDER_unbearform=852139
constant integer ORDER_unburrow=852534
constant integer ORDER_uncoldarrows=852245
constant integer ORDER_uncorporealform=852494
constant integer ORDER_undeadbuild=851998
constant integer ORDER_undefend=852056
constant integer ORDER_undivineshield=852091
constant integer ORDER_unetherealform=852497
constant integer ORDER_unflamingarrows=852175
constant integer ORDER_unflamingattack=852541
constant integer ORDER_unholyfrenzy=852209
constant integer ORDER_unimmolation=852178
constant integer ORDER_unload=852047
constant integer ORDER_unloadall=852048
constant integer ORDER_unloadallcorpses=852054
constant integer ORDER_unloadallinstant=852049
constant integer ORDER_unpoisonarrows=852256
constant integer ORDER_unravenform=852156
constant integer ORDER_unrobogoblin=852657
constant integer ORDER_unroot=852166
constant integer ORDER_unstableconcoction=852500
constant integer ORDER_unstoneform=852207
constant integer ORDER_unsubmerge=852605
constant integer ORDER_unsummon=852210
constant integer ORDER_unwindwalk=852130
constant integer ORDER_vengeance=852521
constant integer ORDER_vengeanceinstant=852524
constant integer ORDER_vengeanceoff=852523
constant integer ORDER_vengeanceon=852522
constant integer ORDER_volcano=852669
constant integer ORDER_voodoo=852503
constant integer ORDER_ward=852504
constant integer ORDER_waterelemental=852097
constant integer ORDER_wateryminion=852598
constant integer ORDER_web=852211
constant integer ORDER_weboff=852213
constant integer ORDER_webon=852212
constant integer ORDER_whirlwind=852128
constant integer ORDER_windwalk=852129
constant integer ORDER_wispharvest=852214
constant integer ORDER_scrollofspeed=852285
constant integer ORDER_cancel=851976
constant integer ORDER_moveslot1=852002
constant integer ORDER_moveslot2=852003
constant integer ORDER_moveslot3=852004
constant integer ORDER_moveslot4=852005
constant integer ORDER_moveslot5=852006
constant integer ORDER_moveslot6=852007
constant integer ORDER_useslot1=852008
constant integer ORDER_useslot2=852009
constant integer ORDER_useslot3=852010
constant integer ORDER_useslot4=852011
constant integer ORDER_useslot5=852012
constant integer ORDER_useslot6=852013
constant integer ORDER_skillmenu=852000
constant integer ORDER_stunned=851973
constant integer ORDER_instant1=851991 //?
constant integer ORDER_instant2=851987 //?
constant integer ORDER_instant3=851975 //?
constant integer ORDER_instant4=852019 //?
endglobals
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 3.1.0.1
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
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")
//! 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 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)
endmethod
//set tb[389034] = 8192
method operator []= takes integer key, Table tb returns nothing
call SaveInteger(ht, this, key, tb)
endmethod
//set b = tb.has(2493223)
method has takes integer key returns boolean
return HaveSavedInteger(ht, this, key)
endmethod
//call tb.remove(294080)
method remove takes integer key returns nothing
call RemoveSavedInteger(ht, this, 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=665
//TESH.alwaysfold=0
library Missile /* v2.0.2
*************************************************************************************
*
* Creating custom projectiles in Warcraft III
*
* Major goal:
* 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.) Global configuration
* 3.) Function configuration
*
* Credits to Dirac, emjlr3, AceHart, Bribe, Nestharus, Maghteridon96 and Vexorian.
*
*************************************************************************************
*
* */ requires /*
*
* - Missile requires nothing
*
*************************************************************************************
*
* Optional requirements listed can reduce overall code generation,
* add safety mechanisms, 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: ( Useful for Missile )
* */ optional ErrorMessage /* github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage
*
* b). Fatal error protection ( Case: unit out of world bounds )
* - WorldBounds is safer than BoundSentinel.
* - WorldBounds adds more overhead than BoundSentinel.
* */ optional WorldBounds /* githubusercontent.com/nestharus/JASS/master/jass/Systems/WorldBounds/script.j
* */ optional BoundSentinel /* wc3c.net/showthread.php?t=102576
*
* c). Handle recycling ( Speed gain, memory management ):
* - uses MissileRecylcer > Dummy > xedummy.
* */ optional MissileRecycler /* hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
* */ optional Dummy /* github.com/nestharus/JASS/blob/master/jass/Systems/Dummy/Dummy.w3x
* */ optional xedummy /* wc3c.net/showthread.php?t=101150
*
* d). Misc ( Absolutely not needed, just listed to avoid an onIndex event )
* */ optional UnitIndexer /* github.com/nestharus/JASS/tree/master/jass/Systems/Unit%20Indexer
*
************************************************************************************
*
* 1. Import instruction
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* • Copy Missile into to your map.
* • You need a dummy unit, using Vexorians "dummy.mdx".
* This unit must use the locust and crow form ability. ( Aloc & Amrf )
* ¯¯¯¯
* 2. Global configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Seven constants to setup!
*/
globals
/**
* Missiles are moved periodically. 1./32. is recommended.
* • Too short timeout values may cause performance issues.
* • Too large timeout values may look fishy.
*/
public constant real TIMER_TIMEOUT = 1./32.
/**
* Owner of all Missile dummies. Should be a neutral player in your map.
*/
public constant player NEUTRAL_PASSIVE = Player(15)
/**
* Raw code of the dummy unit. Object Editor ( F6 )
* • Must be correct, otherwise missiles can neither be recycled nor destroyed.
* • Units of other type ids will not be thrown into the recycler bin.
*/
public constant integer DUMMY_UNIT_ID = 'dumi'
/**
* The maximum collision size used in your map. If unsure use 197. ( town hall collision )
* • Applies for all types of widgets.
* • A precise value can improve Missile's performance,
* since smaller values enumerate less widgtes per loop per missile.
*/
public constant real MAXIMUM_COLLISION_SIZE = 197.
/**
* Collision types for missiles. ( Documentation only )
* Missile decides internally each loop which type of collision is used.
* • Uses circular collision dectection for speed < collision. ( good accuracy, best performance )
* • Uses rectangle collision for speed >= collision. ( best accuracy, bad performance )
*/
public constant integer COLLISION_TYPE_CIRCLE = 0
public constant integer COLLISION_TYPE_RECTANGLE = 1
/**
* Determines when rectangle collision is used to detect nearby widgets.
* • The smaller the factor the earlier rectangle collision is used. ( by default 1. )
* • Formula: speed >= collision*Missile_COLLISION_ACCURACY_FACTOR
*/
public constant real COLLISION_ACCURACY_FACTOR = 1.
// Optional toogles:
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// Set booleans listed below to "true" and the compiler will write
// the feature into your map. Otherwise this code is completly ignored.
// • Yay, I want that --> "true"
// • Naah that's useless for me --> "false"
/**
* USE_COLLISION_Z_FILTER enables z axis checks for widget collision. ( Adds minimal overhead )
* Use it when you need:
* • Missiles flying over or under widgets.
* • Determine between flying and walking units.
*/
public constant boolean USE_COLLISION_Z_FILTER = true
/**
* WRITE_DELAYED_MISSILE_RECYCLING enables a delayed dummy recycling system. ( Very recommended )
* Use it if:
* • You use a dummy recycling library like MissileRecycler, Dummy or xedummy.
* • You want to properly display death animations of effects added to missiles.
*/
public constant boolean WRITE_DELAYED_MISSILE_RECYCLING = true
/**
* DELAYED_MISSILE_DEATH_ANIMATION_TIME is delay in seconds,
* Missile holds back a dummy, before recycling it.
* • The time does not have to be precises.
* • Requires WRITE_DELAYED_MISSILE_RECYCLING = true
*/
private constant real DELAYED_MISSILE_DEATH_ANIMATION_TIME = 2.
/**
* USE_DESTRUCTABLE_FILTER and USE_ITEM_FILTER are redundant constants from previous Missile versions.
* They do nothing, but remain for backwards compatibilty.
* From Missile version 1.5 on any widget collision is always enabled.
*/
public constant boolean USE_DESTRUCTABLE_FILTER = true
public constant boolean USE_ITEM_FILTER = true
endglobals
/*
* 3. Function configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Four functions to setup!
*/
/**
* GetUnitBodySize(unit) returns a fictional value for z - axis collision.
* You have two options:
* • One constant value shared over all units.
* • Dynamic values based on handle id, type id, unit user data, scaling or other parameters.
*/
function GetUnitBodySize takes unit whichUnit returns real
return 100.// Other example: return LoadReal(hash, GetHandleId(whichUnit), KEY_UNIT_BODY_SIZE)
endfunction
/**
* Same as GetUnitBodySize, but for destructables.
* Using occluder height is an idea of mine. Of course you can use your own value.
*/
function GetDestructableHeight takes destructable d returns real
return GetDestructableOccluderHeight(d)// Other example: return 100.
endfunction
/**
* Same as GetUnitBodySize, but for items.
* Again it is up to you to figure out a fictional item height.
*/
function GetItemHeight takes item i returns real
return 20.
endfunction
/**
* Unit indexers and missiles: ( Only if you do not use a dummy recycling library )
* ===========================
* It is most likely intended that projectiles do not run through a unit indexing snippet.
* ToogleUnitIndexer runs:
* • Directly before a dummy is created.
* • Directly after dummy unit creation.
*
* Please return the previous setup of your indexing tool ( enabled, disabled ),
* so Missile can properly reset it to the original state.
*/
private function ToogleUnitIndexer takes boolean enable returns boolean
local boolean prev = true//UnitIndexer.enabled
// set UnitIndexer.enabled = enable
return prev
endfunction
/**
* 4. API
* ¯¯¯¯¯¯
*/
//! novjass ( Disables the compiler until the next endnovjass )
// Custom type Missile for your projectile needs.
struct Missile extends array
// Constants:
// ==========
//
readonly static constant string ORIGIN = "origin"
// • Attach point name for fxs on dummies.
readonly static constant real HIT_BOX = (2./3.)
// • Fictional hit box for homing missiles.
// while 0 would be the toe and 1 the head of a unit.
// Available creators:
// ===================
//
static method createEx takes unit missileDummy, real impactX, real impactY, real impactZ returns Missile
// • Core creator method.
// • May launches any unit.
// • Units of type Missile_DUMMY_UNIT_ID get recycled in the end.
static method create takes real x, real y, real z, real angleInRadians, real distanceToTravel, real endZ returns Missile
// • Converts arguments to fit into createEx, then calls createEx.
static method createXYZ takes real x, real y, real z, real impactX, real impactY, real impactZ returns Missile
// • Converts arguments to fit into createEx, then calls createEx.
// Available destructors:
// ======================
//
return true
// • Core destructor.
// • Returning true in any of the interface methods of the MissileStruct module
// will destroy that instance instantly.
method destroy takes nothing returns nothing
// • Destroys the missile during the next timer callback.
method terminate takes nothing returns nothing
// • Destroys the missile instantly.
// Fields you can set and read directly:
// =====================================
//
unit source
unit target // For homing missiles.
real distance // Distance traveled.
player owner // Pseudo owner for faster onCollide evaluation. The proper dummy owner remains PLAYER_NEUTRAL_PASSIVE.
real speed // Vector lenght for missile movement in plane x / y. ( DOES NOT TAKE THE TIMER TIMEOUT IN ACCOUNT )
real acceleration
real damage
integer data // For data transfer set and read data.
boolean recycle // Is automatically set to true, when a Missile reaches it's destination.
real turn // Set a turn rate for missiles.
// Neither collision nor collisionZ accept values below zero.
real collision // Collision size in plane x / y.
real collisionZ // Collision size in z - axis.
// Fields you can only read:
// =========================
//
readonly boolean allocated
readonly unit dummy// The dummy unit of this missile.
// Position members for you needs.
readonly MissilePosition origin// Grants access to readonly members of MissilePosition,
readonly MissilePosition impact// which are "x", "y", "z", "angle", "distance", "slope" and the pitch angle "alpha".
readonly real terrainZ
readonly real x
readonly real y
readonly real z
readonly real angle// Current angle in radians.
// Method operators for set and read:
// ==================================
//
method operator model= takes string modelFile returns nothing
method operator model takes nothing returns string
// • Adds an effect handle on a missile dummy to it's "origin".
// • You can read the file path.
// • For multiple effects access "this.dummy" in your struct.
method operator scale= takes real value returns nothing
method operator scale takes nothing returns real
// • Set and read the scaling of the dummy unit.
method operator curve= takes real value returns nothing
method operator curve takes nothing returns real
// • Enables curved movement for your missile. ( Pass in radians, NOT degrees )
// • Do not pass in PI/2.
method operator arc= takes real value returns nothing
method operator arc takes nothing returns real
// • Enables arcing movement for your missile. ( Pass in radians, NOT degrees )
// • Do not pass in PI/2.
// Methods for missile movement:
// =============================
//
method bounce takes nothing returns nothing
// • Moves the MissilePosition "origin" to the current missile coordinates.
// • Resets the distance traveled to 0.
method deflect takes real tx, real ty returns nothing
// • Deflect the missile towards tx, ty. Then calls bounce.
method deflectEx takes real tx, real ty, real tz returns nothing
// • Deflect the missile towards tx, ty, tz. Then calls bounce.
method flightTime2Speed takes real duration returns nothing
// • Converts a fly time to a vector lenght for member "speed".
// • Does not take acceleration into account. ( Disclaimer )
method setMovementSpeed takes real value returns nothing
// • Converts Warcraft III movement speed to a vector lenght for member "speed".
// Methods for missile collision: ( all are hashtable entries )
// ==============================
// By default a widget can only be hit once per missile.
//
method hitWidget takes widget w returns nothing
// • Saves a widget in the memory as hit by this instance.
method hasHitWidget takes widget w returns boolean
// • Returns true, if "w" has been hit by this instance.
method removeHitWidget takes widget w returns nothing
// • Removes a widget from this missile's memory for widget collision. ( Can hit "w" again )
method flushHitWidgets takes nothing returns nothing
// • Flushes a missile's memory for widget collision. ( All widgets can be hit again )
method enableHitAfter takes widget w, real seconds returns nothing
// • Automatically calls removeHitWidget(w) after "seconds" time. ( Can hit "w" again after given time )
// Module MissileStruct:
// =====================
//
module MissileStruct
// • Enables the entire missile interface for that struct.
// Interface in structs: ( Must implement module MissileStruct )
// =====================
//
// • Write one, many or all of the static method below into your struct.
// • Missiles launched in this struct will run those methods, when their events fire.
//
// • All of those static methods must return a boolean.
// a) return true --> destroys the missile instance instantly.
// b) return false --> keep on flying.
// Available static method:
// ========================
//
static method onCollide takes Missile missile, unit hit returns boolean
// • Runs for units in collision range of a missile.
static method onDestructable takes Missile missile, destructable hit returns boolean
// • Runs for destructables in collision range of a missile.
// • Runs after onDestructableFilter ( if a filter is declared )
static method onDestructableFilter takes nothing returns boolean
// • Runs before onDestructable as filter function.
// • Runs for destructables in collision range + Missile_MAX_COLLISION_RANGE.
// • Get the filter destructable via "GetFilterDestructable()"
// • Designed only for improving code read-ability.
static method onItem takes Missile missile, item hit returns boolean
// • Runs for items in collision range of a missile.
// • Runs after onItemFilter ( if a filter is declared )
static method onDestructableFilter takes nothing returns boolean
// • Runs before onItem as filter function.
// • Runs for items in collision range + Missile_MAX_COLLISION_RANGE.
// • Get the filter item via "GetFilterItem()"
// • Designed only for improving code read-ability.
static method onTerrain takes Missile missile returns boolean
// • Runs when a missile collides with the terrain. ( Ground and cliffs )
static method onFinish takes Missile missile returns boolean
// • Runs only when a missile reaches it's destination.
// • However does not run, if a Missile is destroyed in another method.
static method onPeriod takes Missile missile returns boolean
// • Runs every Missile_TIMER_TIMEOUT seconds.
static method onRemove takes Missile missile returns boolean
// • Runs when a missile is destroyed.
// • Unlike onFinish, onRemove will runs ALWAYS when a missile is destroyed!!!
//
// For onRemove the returned boolean has a unique meaning:
//
// • Return true will recycle this missile delayed. ( only if WRITE_DELAYED_MISSILE_RECYCLING = true )
// • Return false will recycle this missile right away.
static method launch takes Missile toLaunch returns nothing
// • Well ... Launches this Missile.
// • Missile "toLaunch" will behave as declared in the struct. ( static methods from above )
// Misc: ( From the global setup )
// =====
//
// Constants:
// ==========
//
public constant real TIMER_TIMEOUT
public constant player NEUTRAL_PASSIVE
public constant integer DUMMY_UNIT_ID
public constant real MAXIMUM_COLLISION_SIZE
public constant boolean USE_COLLISION_Z_FILTER
public constant boolean WRITE_DELAYED_MISSILE_RECYCLING
public constant boolean USE_DESTRUCTABLE_FILTER
public constant boolean USE_ITEM_FILTER
// Functions:
// ==========
//
public function GetLocZ takes real x, real y returns real
function GetUnitBodySize takes unit whichUnit returns real
function GetDestructableHeight takes destructable d returns real
function GetItemHeight takes item i returns real
//========================================================================
// Missile system. Make changes carefully.
//========================================================================
//! endnovjass ( Enables the compiler )
// Hello and welcome to Missile.
// I wrote a guideline for every piece of code inside Missile, so you
// can easily understand how the code gets compiled and evaluated.
//
// Let's go!
globals
// Core constant handle variables of Missile.
private constant trigger CORE = CreateTrigger()
private constant timer TMR = CreateTimer()
private constant location LOC = Location(0., 0.)
private constant rect RECT = Rect(0., 0., 0., 0.)
private constant group GROUP = CreateGroup()
private constant hashtable HASH = InitHashtable()
// For starting and stopping the timer.
private integer active = 0
// Arrays for data structure.
private integer array instances
private Missile array missileStack
private boolexpr array expression
private triggercondition array condition
private integer array remove
// Arrays for widgets filter functions in structs.
private boolexpr array destFilter
private boolexpr array itemFilter
// Rectangle filter functions in Missile.
private boolexpr unitFilter
endglobals
public function GetLocZ takes real x, real y returns real
call MoveLocation(LOC, x, y)
return GetLocationZ(LOC)
endfunction
// For WRITE_DELAYED_MISSILE_RECYCLING = true Missile will hold back dummy for DELAYED_MISSILE_DEATH_ANIMATION_TIME,
// until the are recylced. ( placed in a static if )
//
//! runtextmacro optional WRITE_MISSILE_RECYCLE_BIN("WRITE_DELAYED_MISSILE_RECYCLING", "DELAYED_MISSILE_DEATH_ANIMATION_TIME")
// The code of this macro boxes a missiles position and does the required trigonometry.
//
//! runtextmacro WRITE_MISSILE_POSITION_CODE()
// Missiles structure works like a linked list with the folling methods:
// allocateCollection(), allocateNode(), insertEnd(node) and remove()
//
private keyword MissileStructure
struct Missile extends array
implement MissileStructure
// Constants:
// ==========
//
// Attach point name for effects created via model=.
readonly static constant string ORIGIN = "origin"
// Set a ficitional hit box in range of 0 to 1,
// while 0 is the toe and 1 the head of a unit.
readonly static constant real HIT_BOX = (2./3.)
// DEBUG_MODE only members:
// ========================
//
// Checks for double launching. Throws an error message.
debug boolean launched
// Private members:
// ================
//
// The position of a missile using curve= does not
// match the position used by Missile's trigonometry.
// Therefore we need this member two times.
// Readable x / y / z for your needs and xPrivate / yPrivate for cool mathematics.
private real xPrivate
private real yPrivate
private real xPrev
private real yPrev
// Readonly members:
// =================
//
// Prevents a double free case.
readonly boolean allocated
// The dummy unit.
readonly unit dummy
// Position members for you needs.
readonly MissilePosition origin// Grants access to readonly members of MissilePosition,
readonly MissilePosition impact// which are "x", "y", "z", "angle", "distance", "slope" and "alpha".
readonly real terrainZ
readonly real x
readonly real y
readonly real z
readonly real angle// Current angle
// Collision detection type. ( Evaluated new in each loop )
readonly integer collisionType// Current collision type ( circular or rectangle )
// Members you can set:
// ====================
//
unit source
unit target // For homing missiles.
real distance // Distance traveled.
player owner // Pseudo owner for faster onCollide evaluation. The proper dummy owner is PLAYER_NEUTRAL_PASSIVE
real speed // Vector lenght for missile movement in plane x / y.
real acceleration
real damage
integer data // For data transfer set and read data.
boolean recycle // Is set to true, when a Missile reaches it's destination.
real turn // Set a turn rate for a missile.
// Members which are monitored: ( DEBUG_MODE )
// ============================
//
static if not DEBUG_MODE then
real collision // Collision size in plane x / y.
real collisionZ // Collision size in z - axis.
else
// Check for collision < 0.
debug private real collisionDebug
method operator collision= takes real value returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError((value < 0), "Missile", "collision=", "collision", this, "Found collision below zero [" + R2S(value) + "]!")
endif
debug set collisionDebug = value
endmethod
method operator collision takes nothing returns real
debug return collisionDebug
endmethod
// Check for collisionZ < 0.
debug private real collisionZDebug
method operator collisionZ= takes real value returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError((value < 0), "Missile", "collisionZ=", "collisionZ", this, "found collisionZ below zero [" + R2S(value) + "]!")
endif
debug set collisionZDebug = value
endmethod
method operator collisionZ takes nothing returns real
debug return collisionZDebug
endmethod
endif
// Operator overloading:
// =====================
//
// Effect handle on a missile dummy. For multiple effects attaching, access "dummy" in your struct.
private effect sfx
private string path
method operator model= takes string modelFile returns nothing
if (sfx != null) then
call DestroyEffect(sfx)
endif
set path = modelFile
set sfx = AddSpecialEffectTarget(modelFile, dummy, ORIGIN)
endmethod
method operator model takes nothing returns string
return path
endmethod
real open
// Enables curved movement for your missile. PI/2 is not allowed.
method operator curve= takes real value returns nothing
set open = Tan(value)*origin.distance
endmethod
method operator curve takes nothing returns real
if (origin.distance == 0.) then
return 0.// It's a limit of sequence and crashes the thread. Intuitively chosen 0 as return value.
endif
return Atan(open/origin.distance)
endmethod
real height
// Enables arcing movement for your missile. PI/2 is not allowed.
method operator arc= takes real value returns nothing
set height = Tan(value)*origin.distance/4.
endmethod
method operator arc takes nothing returns real
if (origin.distance == 0.) then
return 0.// It's a limit of sequence and crashes the thread. Intuitively chosen 0 as return value.
endif
return Atan(4.*height/origin.distance)
endmethod
private real scaleValue
method operator scale= takes real value returns nothing
call SetUnitScale(dummy, value, 1., 1.)
set scaleValue = value
endmethod
method operator scale takes nothing returns real
return scaleValue
endmethod
// Methods:
// ========
//
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 - angle
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
// Please do not pass in 0.
method flightTime2Speed takes real duration returns nothing
set speed = RMaxBJ(0.00000001, (origin.distance - distance)*Missile_TIMER_TIMEOUT/duration)
endmethod
method setMovementSpeed takes real value returns nothing
set speed = value*Missile_TIMER_TIMEOUT
endmethod
boolean wantDestroy// For "true" a missile is destroyed on the next timer callback. 100% safe.
method destroy takes nothing returns nothing
set wantDestroy = true
endmethod
// Instantly destroys a missile.
method terminate takes nothing returns nothing
set allocated = false
call remove()// Throws an error for invalid nodes.
call impact.destroy()
call origin.destroy()
//
call DestroyEffect(sfx)
call FlushChildHashtable(HASH, this)
if (GetUnitTypeId(dummy) == Missile_DUMMY_UNIT_ID) then
// MissileRecycler > Dummy > xe.
static if LIBRARY_MissileRecycler then
call RecycleMissile(dummy)
elseif LIBRARY_Dummy and Dummy.create.exists then
call Dummy[dummy].destroy()
elseif LIBRARY_xedummy and xedummy.release.exists then
call xedummy.release(dummy)
else
call RemoveUnit(dummy)
endif
endif
//
set sfx = null
set source = null
set target = null
set dummy = null
set owner = null
endmethod
// Runs in createEx.
//! textmacro MISSILE_RESET_ALL_MEMBERS
set path = null
set speed = 0.
set acceleration = 0.
set distance = 0.
set height = 0.
set turn = 0.
set open = 0.
set collision = 0.
set collisionZ = 0.
set collisionType = 0
set scaleValue = 1.
set wantDestroy = false
set recycle = false
//! endtextmacro
// Launches a dummy of your choice.
static method createEx takes unit missileDummy, real impactX, real impactY, real impactZ returns thistype
local thistype this = thistype.allocateNode()
local real originX = GetUnitX(missileDummy)
local real originY = GetUnitY(missileDummy)
local real originZ = GetUnitFlyHeight(missileDummy)
set allocated = true
//
//! runtextmacro MISSILE_RESET_ALL_MEMBERS()
//
set origin = MissilePosition.create(originX, originY, originZ)
set impact = MissilePosition.create(impactX, impactY, impactZ)
call MissilePosition.link(origin, impact)
set xPrivate = originX
set yPrivate = originY
set x = originX
set y = originY
set z = originZ
set angle = origin.angle
set dummy = missileDummy
set stackSize = 0
call SetUnitFlyHeight(missileDummy, origin.z - Missile_GetLocZ(originX, originY), 0.)
//
static if LIBRARY_ErrorMessage then
debug call ThrowError((GetUnitTypeId(missileDummy) == 0), "Missile", "createEx", "missileDummy", this, "Invalid missile dummy unit (null)!")
endif
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 z, real face returns unit
local boolean prev = ToogleUnitIndexer(false)
set bj_lastCreatedUnit = CreateUnit(Missile_NEUTRAL_PASSIVE, Missile_DUMMY_UNIT_ID , x, y, face)
call ToogleUnitIndexer(prev)
call SetUnitX(bj_lastCreatedUnit, x)
call SetUnitY(bj_lastCreatedUnit, y)
call UnitAddAbility(bj_lastCreatedUnit, 'Amrf')
call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0.)
call PauseUnit(bj_lastCreatedUnit, true)
return bj_lastCreatedUnit
endmethod
endif
// MissileRecylcer > Dummy > xe > Missile.
//! textmacro MISSILE_GET_DUMMY_FROM_LIBRARY
static if LIBRARY_MissileRecycler then
return createEx(GetRecycledMissile(x, y, z, angle*bj_RADTODEG), impactX, impactY, impactZ)
elseif LIBRARY_Dummy and Dummy.create.exists then
local Dummy dummy = Dummy.create(x, y, angle*bj_RADTODEG)
call SetUnitFlyHeight(dummy.unit, z, 0.)
return createEx(dummy.unit, impactX, impactY, impactZ)
elseif LIBRARY_xedummy and xedummy.new.exists then
set bj_lastCreatedUnit = xedummy.new(Missile_NEUTRAL_PASSIVE, x, y, angle*bj_RADTODEG)
call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0.)
return createEx(bj_lastCreatedUnit, impactX, impactY, impactZ)
else
return createEx(Missile.newMissileUnit(x, y, z, angle*bj_RADTODEG), impactX, impactY, impactZ)
endif
//! endtextmacro
// Wrapper to createEx.
static method create takes real x, real y, real z, real angle, real distance, real impactZ returns thistype
local real impactX = x + distance*Cos(angle)
local real impactY = y + distance*Sin(angle)
// Get Dummy.
//! runtextmacro MISSILE_GET_DUMMY_FROM_LIBRARY()
endmethod
// Wrapper to createEx.
static method createXYZ takes real x, real y, real z, real impactX, real impactY, real impactZ returns thistype
local real angle = Atan2(impactY - y, impactX - x)
// Get Dummy.
//! runtextmacro MISSILE_GET_DUMMY_FROM_LIBRARY()
endmethod
// Called from each active struct every Missile_TIMER_TIMEOUT seconds.
// In this method performance matters.
// • Locals used for faster multiple lookups.
// • Short variable names.
method move takes nothing returns nothing
local MissilePosition loc
local real a
local real d
local real s
local real v
local unit u
local real h
local real tx
local real ty
local real dex
loop
exitwhen (0 == this)
set loc = origin
set h = height
// Missile target.
set u = 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)
//
// Uses a fictional hit box of GetUnitBodySize()*HIT_BOX for the z offset.
call origin.move(x, y, z)
call impact.move(tx, ty, GetUnitFlyHeight(u) + GetUnitBodySize(u)*Missile.HIT_BOX)
set distance = loc.distance - SquareRoot((tx - xPrivate)*(tx - xPrivate) + (ty - yPrivate)*(ty - yPrivate))
else
set a = loc.angle
set target = null
endif
else
set a = loc.angle
endif
set u = dummy
// Missile turn.
if (0 != turn) and (Cos(angle - a) < Cos(turn)) then
if (Sin(a - angle) >= 0) then
set angle = angle + turn
else
set angle = angle - turn
endif
else
set angle = a
endif
// Update position members.
set v = speed
set d = loc.distance
//call BJDebugMsg(R2S(d))
if (distance + v > d) then
set s = d + 0.000001
else
set s = distance + v
endif
set distance = s
//
set tx = xPrivate + v*Cos(angle)
set ty = yPrivate + v*Sin(angle)
set speed = v + acceleration
set xPrivate = tx
set yPrivate = ty
// Detect the collision type.
if (v < collision*Missile_COLLISION_ACCURACY_FACTOR) then
set collisionType = Missile_COLLISION_TYPE_CIRCLE
else
set collisionType = Missile_COLLISION_TYPE_RECTANGLE
endif
// Missile curve.
if (open != 0) then
set a = 4*open*s*(d - s)/(d*d)
set tx = tx + a*Cos(angle + 1.57)
set ty = ty + a*Sin(angle + 1.57)
call SetUnitFacing(u, (angle + Atan(-((4*open)*(2*s - d))/(d*d)))*bj_RADTODEG)
else
call SetUnitFacing(u, angle*bj_RADTODEG)
endif
// Missile arc.
call MoveLocation(LOC, tx, ty)
set terrainZ = GetLocationZ(LOC)
if (h != 0) or (loc.slope != 0) then
set z = loc.z - terrainZ + loc.slope*s
set dex = loc.alpha
if (h != 0) then
set z = z + (4*h*s*(d - s)/(d*d))
set dex = dex - Atan(((4*h)*(2*s - d))/(d*d))*bj_RADTODEG
endif
if (GetUnitTypeId(u) == Missile_DUMMY_UNIT_ID) then
call SetUnitAnimationByIndex(u, R2I(dex + 90.5))
endif
else
set z = loc.z - terrainZ
endif
// Missile position.
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
// set wantDestroy = true
else
call SetUnitX(u, tx)
call SetUnitY(u, ty)
endif
else
call SetUnitX(u, tx)
call SetUnitY(u, ty)
endif
call SetUnitFlyHeight(u, z, 0.)
// Update missile x / y ( readonly )
set xPrev = x
set yPrev = y
set x = tx
set y = ty
// Check for destination reached.
if (s >= d) then
set recycle = true
endif
set this = next
endloop
set u = null
endmethod
// Widget collision API:
// =====================
//
// Runs automatically on widget collision.
method hitWidget takes widget w returns nothing
call SaveWidgetHandle(HASH, this, GetHandleId(w), w)
endmethod
// All widget which have been hit return true.
method hasHitWidget takes widget w returns boolean
return HaveSavedHandle(HASH, this, GetHandleId(w))
endmethod
// Removes a widget from the missile's memory of hit widgets. ( This widget can be hit again )
method removeHitWidget takes widget w returns nothing
call RemoveSavedHandle(HASH, this, GetHandleId(w))
endmethod
// Flushes a missile's memory for collision. ( All widgets can be hit again )
method flushHitWidgets takes nothing returns nothing
call FlushChildHashtable(HASH, this)
endmethod
// Tells missile to call removeHitWidget(w) after "seconds" time.
// Does not apply to widgets, which are already hit by this missile.
readonly integer stackSize
method enableHitAfter takes widget w, real seconds returns nothing
local integer id = GetHandleId(w)
local integer dex
if (id == 0) then
return
endif
if HaveSavedInteger(HASH, this, id) then
set dex = LoadInteger(HASH, this, id)
else
set dex = stackSize
set stackSize = stackSize + 1
call SaveInteger(HASH, this, id, dex)
call SaveInteger(HASH, this, dex, id)
endif
call SaveReal(HASH, this, id, seconds)// Set time.
endmethod
method updateStack takes nothing returns nothing
local integer dex = 0
local integer id
local real time
loop
exitwhen (dex == stackSize)
set id = LoadInteger(HASH, this, dex)
set time = LoadReal(HASH, this, id) - Missile_TIMER_TIMEOUT
if (time <= 0.) or not HaveSavedHandle(HASH, this, id) then
set stackSize = stackSize - 1
set id = LoadInteger(HASH, this, stackSize)
call SaveInteger(HASH, this, dex, id)
call SaveInteger(HASH, this, id, dex)
// Enables hit.
call RemoveSavedHandle(HASH, this, id)
// Remove data from stack.
call RemoveSavedReal(HASH, this, id)
call RemoveSavedInteger(HASH, this, id)
call RemoveSavedInteger(HASH, this, stackSize)
set dex = dex - 1
else
call SaveReal(HASH, this, id, time)
endif
set dex = dex + 1
endloop
endmethod
// Widget collision code:
// ======================
//
// Transfer data to action functions.
private static Missile temp = 0
private static boolean circle = true
//
// 1.) Rectangle collision for fast moving missiles with small collision radius.
//
// Runs for destructables and items in a rectangle.
// Checks if widget w is in collision range of a missile.
// Is not precise in z - axis collision.
private method checkWidgetRectangle takes widget w, real height returns boolean
local real wx = GetWidgetX(w)
local real wy = GetWidgetY(w)
local real wz = Missile_GetLocZ(wx, wy) - terrainZ
local real dx = x - xPrev
local real dy = y - yPrev
local real s = (dx*(wx - xPrev) + dy*(wy - yPrev))/(dx*dx + dy*dy)
static if Missile_USE_COLLISION_Z_FILTER then
local real cz = collisionZ
else
local real cz = collision
endif //
if (s < 0) then
set s = 0.
elseif (s > 1) then
set s = 1.
endif
set dx = (xPrev + s*dx) - wx
set dy = (yPrev + s*dy) - wy
set s = dx*dx + dy*dy
if (s < bj_enumDestructableRadius) and (wz + height >= z - cz) and (wz <= z + cz) then
set bj_enumDestructableRadius = s
return true
endif
return false
endmethod
//
// 2.) Circular collision detection for all other missiles.
//
// Runs for destructables and items in a circle.
// Z - axis collision is precise.
private method checkWidgetCircle takes widget w, real height returns boolean
local real wx = GetWidgetX(w)
local real wy = GetWidgetY(w)
local real wz = Missile_GetLocZ(wx, wy) - terrainZ
local real d = (x - wx)*(x - wx) + (y - wy)*(y - wy)
static if Missile_USE_COLLISION_Z_FILTER then
local real cz = collisionZ
else
local real cz = collision
endif //
if (d < bj_enumDestructableRadius) and (wz + height >= z - cz) and (wz <= z + cz) then
set bj_enumDestructableRadius = d
return true
endif
return false
endmethod
//
// 3.) Action functions inside the widget enumeration thread.
//
// Runs for every enumerated destructable.
// • Searches the closest available destructables
// • Distance formula based on the Pythagorean theorem.
//
private static method enumDests takes nothing returns nothing
local destructable d = GetEnumDestructable()
if HaveSavedHandle(HASH, temp, GetHandleId(d)) then
set d = null
return
endif
if (circle) then
if temp.checkWidgetCircle(d, GetDestructableHeight(d)) then
set bj_destRandomCurrentPick = d
endif
// Runs rectangle collision check.
elseif temp.checkWidgetRectangle(d, GetDestructableHeight(d)) then
set bj_destRandomCurrentPick = d
endif
set d = null
endmethod
//
// Runs for every enumerated item.
// • Searches the closest available item.
// • Distance formula based on the Pythagorean theorem.
//
private static method enumItems takes nothing returns nothing
local item i = GetEnumItem()
if HaveSavedHandle(HASH, temp, GetHandleId(i)) then
set i = null
return
endif
if (circle) then
if temp.checkWidgetCircle(i, GetItemHeight(i)) then
set bj_itemRandomCurrentPick = i
endif
// Runs rectangle collision check.
elseif temp.checkWidgetRectangle(i, GetItemHeight(i)) then
set bj_itemRandomCurrentPick = i
endif
set i = null
endmethod
//
// 4.) Filter function for rectangle unit collision.
//
// Runs for every enumerated units.
// • Filters out units which are not in collision range in plane x / y.
//
private static method filterUnits takes nothing returns boolean
local thistype this = thistype.temp
//
local unit u = GetFilterUnit()
local real a
local real b
local real s
local boolean is
// Missile knows this unit already.
if HaveSavedBoolean(HASH, this, GetHandleId(u)) then
set u = null
return false
endif
//
set a = x - xPrev
set b = y - yPrev
set s = (a*(GetUnitX(u) - xPrev) + b*(GetUnitY(u)- yPrev))/(a*a + b*b)
if(s < 0) then
set s = 0.
elseif(s > 1) then
set s = 1.
endif
set is = IsUnitInRangeXY(u, xPrev + s*a, yPrev + s*b, collision)
set u = null
return is
endmethod
//
// 5.) Proper rect preparation.
//
// For rectangle.
private method prepareRectRectangle takes nothing returns nothing
local real x1 = xPrev
local real y1 = yPrev
local real x2 = x
local real y2 = y
local real d = collision + Missile_MAXIMUM_COLLISION_SIZE
// What is min, what is max ...
if(x1 < x2) then
if( y1 < y2) then
call SetRect(RECT, x1 - d, y1 - d, x2 + d, y2 + d)
else
call SetRect(RECT, x1 - d, y2 - d, x2 + d, y1 + d)
endif
else
if( y1 < y2) then
call SetRect(RECT, x2 - d, y1 - d, x1 + d, y2 + d)
else
call SetRect(RECT, x2 - d, y2 - d, x1 + d, y1 + d)
endif
endif
endmethod
//
// For circular.
private method prepareRectCircle takes nothing returns nothing
local real d = collision + Missile_MAXIMUM_COLLISION_SIZE
call SetRect(RECT, x - d, y - d, x + d, y + d)
endmethod
//
// 6.) API for the MissileStruct iteration.
//
method groupEnumUnitsRectangle takes nothing returns nothing
call prepareRectRectangle()
set thistype.temp = this
call GroupEnumUnitsInRect(GROUP, RECT, unitFilter)
endmethod
//
// Prepares destructable enumeration, then runs enumDests.
method checkDestCollision takes boolexpr expr returns nothing
set circle = (collisionType == Missile_COLLISION_TYPE_CIRCLE)
if (circle) then
call prepareRectCircle()
else
call prepareRectRectangle()
endif
//
set thistype.temp = this
set bj_destRandomCurrentPick = null
set bj_enumDestructableRadius = collision*collision + 1.
call EnumDestructablesInRect(RECT, expr, function thistype.enumDests)
endmethod
//
// Prepares item enumeration, then runs enumItems.
method checkItemCollision takes boolexpr expr returns nothing
set circle = (collisionType == Missile_COLLISION_TYPE_CIRCLE)
if (circle) then
call prepareRectCircle()
else
call prepareRectRectangle()
endif
//
set thistype.temp = this
set bj_itemRandomCurrentPick = null
set bj_enumDestructableRadius = collision*collision + 1.
call EnumItemsInRect(RECT, expr, function thistype.enumItems)
endmethod
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.
private static method onInit takes nothing returns nothing
static if LIBRARY_ErrorMessage then
debug local boolean prev = ToogleUnitIndexer(false)
debug local unit dummy = CreateUnit(Missile_NEUTRAL_PASSIVE, Missile_DUMMY_UNIT_ID, 0, 0, 0)
debug call ToogleUnitIndexer(prev)
//
debug call ThrowError((GetUnitTypeId(dummy) != Missile_DUMMY_UNIT_ID), "Missile", "DEBUG_MISSILE", "typeid", 0, "Global setup for public integer DUMMY_UNIT_ID is incorrect! This map currently can't use Missile!")
debug call ThrowError((Missile_MAXIMUM_COLLISION_SIZE < 0), "Missile", "DEBUG_MISSILE", "collision", 0, "Global setup for public real MAXIMUM_COLLISION_SIZE is incorrect, below zero! This map currently can't use Missile!")
debug call RemoveUnit(dummy)
debug set dummy = null
endif
//
set unitFilter = Filter(function thistype.filterUnits)
endmethod
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 interface module "MissileStruct"
//
// This comes now!
// Debug code taken from List ( full 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
// Boolean expressions per struct:
// ===============================
//
// Filter function for item enumeration. ( Runs only if a filter is declared in the struct )
private function MissileCreateItemFilter takes integer structId, code func returns nothing
set itemFilter[structId] = Filter(func)
endfunction
// Filter function for destructable enumeration. ( Runs only if a filter is declared in the struct )
private function MissileCreateDestructableFilter takes integer structId, code func returns nothing
set destFilter[structId] = Filter(func)
endfunction
// Condition function for the core trigger evaluation. ( Runs for all struct using module MissileStruct )
private function MissileCreateExpression takes integer structId, code func returns nothing
set expression[structId] = Condition(func)
endfunction
// Creates a collection for a struct. ( Runs for all struct using module MissileStruct )
private function MissileCreateCollection takes integer structId returns nothing
set missileStack[structId] = Missile.allocateCollection()
endfunction
// Core:
// =====
//
// Fires every Missile_TIMER_TIMEOUT.
private function Fire takes nothing returns nothing
local integer i = remove[0]
set remove[0] = 0
loop
exitwhen i == 0
if (instances[i] == 0) and (condition[i] != null) then
call TriggerRemoveCondition(CORE, condition[i])
set condition[i] = null
set active = active - 1
endif
set i = remove[i]
endloop
if (active == 0) then
call PauseTimer(TMR)
else
call TriggerEvaluate(CORE)
endif
endfunction
// Conditionally starts the timer.
private function StartPeriodic takes integer structId returns nothing
if (instances[structId] == 0) then
if (active == 0) then
call TimerStart(TMR, Missile_TIMER_TIMEOUT, true, function Fire)
endif
if (condition[structId] == null) then
set condition[structId] = TriggerAddCondition(CORE, expression[structId])
set active = active + 1
endif
endif
set instances[structId] = instances[structId] + 1
endfunction
// Conditionally stops the timer.
private function StopPeriodic takes integer structId returns nothing
set instances[structId] = instances[structId] - 1
if (instances[structId] == 0) then
set remove[structId] = remove[0]
set remove[0] = structId
endif
endfunction
// Module:
// =======
//
module MissileStruct
// Runs check during compile time.
static if DEBUG_MODE then
static if thistype.onMissile.exists then
Error Message from library Missile in struct thistype !
thistype.onMissile is a reserved name for Missile, once you implemented MissileStruct.
thistype.onMissile is currently not supported by library Missile.
Please delete or re-name that method.
endif
endif
// Called from missileIterate. "P" to avoid code collision.
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) == Missile_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
// Runs every Missile_TIMER_TIMEOUT for this struct.
private static method missileIterateP takes nothing returns boolean
local Missile this = missileStack[thistype.typeid].first
local Missile node
local real collideZ
local boolean b
local unit u
call this.move()// Moves all missiles of this collection.
loop
exitwhen (0 == this)
set node = this.next// The linked list should not lose the next node.
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
if (this.stackSize > 0) then
call this.updateStack()
endif
// Runs unit collision.
static if thistype.onCollide.exists then
if (this.allocated) and (0 != this.collision) then
set b = (this.collisionType == Missile_COLLISION_TYPE_RECTANGLE)
if b then
call this.groupEnumUnitsRectangle()
else
call GroupEnumUnitsInRange(GROUP, this.x, this.y, this.collision + Missile_MAXIMUM_COLLISION_SIZE, null)
endif
loop
set u = FirstOfGroup(GROUP)
exitwhen u == null
call GroupRemoveUnit(GROUP, u)
if not HaveSavedHandle(HASH, this, GetHandleId(u)) then
if ((IsUnitInRange(u, this.dummy, this.collision)) or (b)) then
// Eventually run z collision checks.
static if Missile_USE_COLLISION_Z_FILTER then
set collideZ = Missile_GetLocZ(GetUnitX(u), GetUnitY(u)) + GetUnitFlyHeight(u) - this.terrainZ
if (collideZ + GetUnitBodySize(u) >= this.z - this.collisionZ) and (collideZ <= this.z + this.collisionZ) then
// Mark as hit.
call SaveUnitHandle(HASH, this, GetHandleId(u), u)
if thistype.onCollide(this, u) then
call thistype.missileTerminateP(this)
exitwhen true
endif
endif
else
// Runs unit collision without z collision checks.
call SaveUnitHandle(HASH, this, GetHandleId(u), u)
if thistype.onCollide(this, u) then
call thistype.missileTerminateP(this)
exitwhen true
endif
endif
endif
endif
endloop
endif
endif
// Runs destructable collision.
static if thistype.onDestructable.exists then
// Check if the missile is not terminated.
if (this.allocated) and (0 != this.collision) then
call this.checkDestCollision(destFilter[thistype.typeid])
if (bj_destRandomCurrentPick != null) then
call SaveDestructableHandle(HASH, this, GetHandleId(bj_destRandomCurrentPick), bj_destRandomCurrentPick)
if thistype.onDestructable(this, bj_destRandomCurrentPick) then
call missileTerminateP(this)
endif
endif
endif
endif
// Runs item collision.
static if thistype.onItem.exists then
// Check if the missile is not terminated.
if (this.allocated) and (0 != this.collision) then
call this.checkItemCollision(itemFilter[thistype.typeid])
if (bj_itemRandomCurrentPick != null) then
call SaveItemHandle(HASH, this, GetHandleId(bj_itemRandomCurrentPick), bj_itemRandomCurrentPick)
if thistype.onItem(this, bj_itemRandomCurrentPick) then
call missileTerminateP(this)
endif
endif
endif
endif
// Runs when the destination is reached.
if (this.recycle) and (this.allocated) then
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
// Runs on terrian collision.
static if thistype.onTerrain.exists then
if (this.allocated) and (this.z < 0) and thistype.onTerrain(this) then
call missileTerminateP(this)
endif
endif
// Runs every Missile_TIMER_TIMEOUT.
static if thistype.onPeriod.exists then
if (this.allocated) and thistype.onPeriod(this) then
call missileTerminateP(this)
endif
endif
endif
set this = node
endloop
set u = null
return false
endmethod
// Places the launcher in your struct.
static method launch takes Missile missile returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(missile.launched, "thistype", "launch", "missile.launched", missile, "This missile was already launched before!")
endif
debug set missile.launched = true
//
call missileStack[thistype.typeid].insertEnd(missile)
call StartPeriodic(thistype.typeid)
endmethod
// Runs onInit. Creates the collection, trigger condition and widget filters.
private static method onInit takes nothing returns nothing
call MissileCreateCollection(thistype.typeid)
call MissileCreateExpression(thistype.typeid, function thistype.missileIterateP)
static if thistype.onDestructableFilter.exists and thistype.onDestructable.exists then
call MissileCreateDestructableFilter(thistype.typeid, function thistype.onDestructableFilter)
endif
static if thistype.onItemFilter.exists and thistype.onItem.exists then
call MissileCreateItemFilter(thistype.typeid, function thistype.onItemFilter)
endif
endmethod
endmodule
// Missile position:
// =================
//
// Simple trigonometry.
//! textmacro WRITE_MISSILE_POSITION_CODE
struct MissilePosition extends array
private static integer array recycler
private static integer alloc = 0
// Readonly members you can access.
readonly real x
readonly real y
readonly real z
readonly real angle
readonly real distance
readonly real slope
readonly real alpha
// Creates an origin - impact link.
private thistype ref
private static method math takes thistype a, thistype b returns boolean
// Set a.
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// For distance == 0 the thread crashes!
set a.x = a.x + .0001
return false// Runs the thread again.
endif
set a.alpha = Atan(a.slope)*bj_RADTODEG
// Set b.
set b.angle = a.angle + bj_PI
set b.distance = a.distance
set b.slope = -a.slope
set b.alpha = -a.alpha
return true
endmethod
static method link takes thistype a, thistype b returns nothing
set a.ref = b
set b.ref = a
loop
exitwhen math(a, b)
endloop
endmethod
method move takes real toX, real toY, real toZ returns nothing
set x = toX
set y = toY
set z = toZ + Missile_GetLocZ(toX, toY)
if (ref != this) then
loop
exitwhen math(this, ref)
endloop
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
// Delayed dummy recycling:
// ========================
//
// Ensures proper fx death animations.
//! textmacro WRITE_MISSILE_RECYCLE_BIN takes DO_THIS, AFTER_TIME
static if $DO_THIS$ then
private struct RecycleBin extends array
private static constant timer t = CreateTimer()
private static integer max = 0
private static unit array dummy
private static real array time
private static method onPeriodic takes nothing returns nothing
local integer dex = 0
loop
exitwhen dex == thistype.max
set thistype.time[dex] = thistype.time[dex] - 1
if (0 >= thistype.time[dex]) then
static if LIBRARY_MissileRecycler then
call RecycleMissile(thistype.dummy[dex])
elseif Dummy.create.exists and LIBRARY_Dummy then
call Dummy[thistype.dummy[dex]].destroy()
elseif LIBRARY_xedummy and xedummy.release.exists then
call xedummy.release(thistype.dummy[dex])
else
call RemoveUnit(thistype.dummy[dex])
endif
set thistype.dummy[dex] = null
set thistype.max = thistype.max - 1
set thistype.dummy[dex] = thistype.dummy[thistype.max]
set thistype.time[dex] = thistype.time[thistype.max]
set thistype.dummy[thistype.max] = null
set dex = dex - 1
if (0 == thistype.max) then
call PauseTimer(thistype.t)
endif
endif
set dex = dex + 1
endloop
endmethod
static method recycle takes unit toRecycle returns nothing
if (0 == thistype.max) then
call TimerStart(thistype.t, 1., true, function thistype.onPeriodic)
endif
set thistype.dummy[max] = toRecycle
set thistype.time[max] = $AFTER_TIME$ + TimerGetRemaining(thistype.t)
set thistype.max = thistype.max + 1
endmethod
endstruct
endif
//! endtextmacro
// The end!
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
private boolean didinit = false
endglobals
private keyword init
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
set tT[0]=CreateTimer()
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
if ( didinit ) then
return
else
set didinit = true
endif
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library_once WorldBounds /* v2.0.0.0
************************************************************************************
*
* struct WorldBounds extends array
* readonly static integer maxX
* readonly static integer maxY
* readonly static integer minX
* readonly static integer minY
* readonly static integer centerX
* readonly static integer centerY
* readonly static rect world
* readonly static region worldRegion
*
************************************************************************************/
private module WorldBoundInit
private static method onInit takes nothing returns nothing
set world=GetWorldBounds()
set maxX=R2I(GetRectMaxX(world))
set maxY=R2I(GetRectMaxY(world))
set minX=R2I(GetRectMinX(world))
set minY=R2I(GetRectMinY(world))
set centerX=R2I((maxX+minX)/2)
set centerY=R2I((minY+maxY)/2)
set worldRegion=CreateRegion()
call RegionAddRect(worldRegion,world)
endmethod
endmodule
struct WorldBounds extends array
readonly static integer maxX
readonly static integer maxY
readonly static integer minX
readonly static integer minY
readonly static integer centerX
readonly static integer centerY
readonly static rect world
readonly static region worldRegion
implement WorldBoundInit
endstruct
endlibrary
//TESH.scrollpos=19
//TESH.alwaysfold=0
library DummyCaster /* v2.0.0.1
*************************************************************************************
*
* Dummy caster for casting spells
*
* Spells must have 0 cooldown, 92083 range, and cost 0 mana. Spells must be instant and
* can't share the same order as other spells on the dummy caster.
*
*************************************************************************************
*
* */uses/*
* */ optional UnitIndexer /* hiveworkshop.com/forums/jass-functions-413/unit-indexer-172090/
*
************************************************************************************
* SETTINGS
*/
globals
constant integer UNITS_DUMMY_CASTER = 'n002'
/*************************************************************************************
*
* PLAYER_OWNER
*
* Owner of dummy caster
*
*************************************************************************************/
private constant player PLAYER_OWNER = Player(15)
endglobals
/*
************************************************************************************
*
* Dummy at position 32256,32256
*
* struct DummyCaster extends array
*
* method cast takes player castingPlayer, integer abilityLevel, integer order, real x, real y returns boolean
* - call DummyCaster[abilityId].cast(...)
* method castTarget takes player castingPlayer, integer abilityLevel, integer order, widget t returns boolean
* - call DummyCaster[abilityId].castTarget(...)
* method castPoint takes player castingPlayer, integer abilityLevel, integer order, real x, real y returns boolean
* - call DummyCaster[abilityId].castPoint(...)
*
************************************************************************************/
globals
private unit u
endglobals
private module N
private static method onInit takes nothing returns nothing
static if LIBRARY_UnitIndexer then
set UnitIndexer.enabled=false
set u=CreateUnit(PLAYER_OWNER,UNITS_DUMMY_CASTER,0,0,0)
set UnitIndexer.enabled=true
else
set u=CreateUnit(PLAYER_OWNER,UNITS_DUMMY_CASTER,0,0,0)
endif
call SetUnitPosition(u,32256,32256)
endmethod
endmodule
struct DummyCaster extends array
implement N
private static method prep takes integer a, player p, integer l returns nothing
call UnitAddAbility(u, a)
if (1 < l) then
call SetUnitAbilityLevel(u, a, l)
endif
if (null != p) then
call SetUnitOwner(u, p, false)
endif
endmethod
private static method finish takes integer a returns nothing
call SetUnitOwner(u, PLAYER_OWNER, false)
call UnitRemoveAbility(u, a)
endmethod
method cast takes player p, integer level, integer order, real x, real y returns boolean
local boolean b
call SetUnitX(u, x)
call SetUnitY(u, y)
call prep(this, p, level)
set b = IssueImmediateOrderById(u,order)
call finish(this)
call SetUnitPosition(u, 32256, 32256)
return b
endmethod
method castTarget takes player p, integer level, integer order, widget t returns boolean
local boolean b
call prep(this, p, level)
set b = IssueTargetOrderById(u,order,t)
call finish(this)
return b
endmethod
method castPoint takes player p, integer level, integer order, real x, real y returns boolean
local boolean b
call prep(this, p, level)
set b = IssuePointOrderById(u,order,x,y)
call finish(this)
return b
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
/**************************************************************
*
* 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=24
//TESH.alwaysfold=0
library SpellIndex/* v1.1
************************************************************
*
* Makes spell indexing global and nulls members automatically.
* A screen freeze is more likely than an overflow.
*
* API:
* --> SpellIndex.create() and index.destroy()
*
************************************************************
*
* */ uses /*
*
* */ Table /* hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
* */ Missile /* hiveworkshop.com/forums/jass-resources-412/missile-265370/
* */ TimerUtils /* wc3c.net/showthread.php?t=101322
* */ DummyCaster /* github.com/nestharus/JASS/tree/master/jass/Systems/DummyCaster
* */ WorldBounds /* github.com/nestharus/JASS/tree/master/jass/Systems/WorldBounds
* */ SpellEffectEvent /* hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/
* */ RegisterPlayerUnitEvent /* hiveworkshop.com/forums/showthread.php?t=203338
*
***************************************************************
*
* Credits to Bribe, Nestharus, Maghteridon96 and Vexorian.
*
***************************************************************/
native UnitAlive takes unit id returns boolean
struct SpellIndex
//* implement your Alloc module here.
static constant group GLOBAL_GROUP = bj_lastCreatedGroup
//* Add or remove struct members you to needs.
//* Units.
unit source
unit target
player user
//* Effects.
effect fx
ubersplat splat
lightning flash
//* Timer.
timer clock
//* Damage options.
real damage
real collision
//* Misc.
real time
integer count
integer level
integer phase
//* Conditionally destroy an handle on instance.destroy().
//! textmacro SPELL_INDEX_CHECK_MEMBER takes VAR, TYPE
if $VAR$ != null then
call Destroy$TYPE$($VAR$)
set $VAR$ = null
endif
//! endtextmacro
method destroy takes nothing returns nothing
//! runtextmacro SPELL_INDEX_CHECK_MEMBER("fx", "Effect")
//! runtextmacro SPELL_INDEX_CHECK_MEMBER("splat", "Ubersplat")
//! runtextmacro SPELL_INDEX_CHECK_MEMBER("flash", "Lightning")
set target = null
set clock = null
set source = null
call deallocate()
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TerrainPathability initializer Init
//******************************************************************************
//* BY: Rising_Dusk, edited by BPower
//*
//* This script can be used to detect the type of pathing at a specific point.
//* It is valuable to do it this way because the IsTerrainPathable is very
//* counterintuitive and returns in odd ways and aren't always as you would
//* expect. This library, however, facilitates detecting those things reliably
//* and easily.
//*
//******************************************************************************
//*
//* > function IsTerrainDeepWater takes real x, real y returns boolean
//* > function IsTerrainShallowWater takes real x, real y returns boolean
//* > function IsTerrainLand takes real x, real y returns boolean
//* > function IsTerrainPlatform takes real x, real y returns boolean
//* > function IsTerrainWalkable takes real x, real y returns boolean
//*
//* These functions return true if the given point is of the type specified
//* in the function's name and false if it is not. For the IsTerrainWalkable
//* function, the MAX_RANGE constant below is the maximum deviation range from
//* the supplied coordinates that will still return true.
//*
//* The IsTerrainPlatform works for any preplaced walkable destructable. It will
//* return true over bridges, destructable ramps, elevators, and invisible
//* platforms. Walkable destructables created at runtime do not create the same
//* pathing hole as preplaced ones do, so this will return false for them. All
//* other functions except IsTerrainWalkable return false for platforms, because
//* the platform itself erases their pathing when the map is saved.
//*
//* After calling IsTerrainWalkable(x, y), the following two global variables
//* gain meaning. They return the X and Y coordinates of the nearest walkable
//* point to the specified coordinates. These will only deviate from the
//* IsTerrainWalkable function arguments if the function returned false.
//*
//* Variables that can be used from the library:
//* [real] TerrainPathability_X
//* [real] TerrainPathability_Y
//*
globals
private constant real MAX_RANGE = 100.
private constant integer DUMMY_ITEM_ID = 'wolg'
endglobals
globals
private item Item = null
private rect Find = null
private item array stack
private integer size = 0
public real X = 0.
public real Y = 0.
endglobals
function IsTerrainDeepWater takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
endfunction
function IsTerrainShallowWater takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
endfunction
function IsTerrainLand takes real x, real y returns boolean
return IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)
endfunction
function IsTerrainPlatform takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
endfunction
private function HideItem takes nothing returns nothing
if IsItemVisible(GetEnumItem()) then
set stack[size] = GetEnumItem()
call SetItemVisible(stack[size], false)
set size = size + 1
endif
endfunction
function IsTerrainWalkable takes real x, real y returns boolean
//Hide any items in the area to avoid conflicts with our item
call MoveRectTo(Find, x, y)
call EnumItemsInRect(Find ,null, function HideItem)
//Try to move the test item and get its coords
call SetItemPosition(Item, x, y) //Unhides the item
set X = GetItemX(Item)
set Y = GetItemY(Item)
call SetItemVisible(Item, false)//Hide it again
//Unhide any items hidden at the start
loop
exitwhen (0 == size)
set size = size - 1
call SetItemVisible(stack[size], true)
set stack[size] = null
endloop
//Return walkability
return (X-x)*(X-x)+(Y-y)*(Y-y) <= MAX_RANGE and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
endfunction
private function Init takes nothing returns nothing
set Find = Rect(0., 0., 128., 128.)
set Item = CreateItem(DUMMY_ITEM_ID, 0, 0)
call SetItemVisible(Item, false)
endfunction
endlibrary
library IsTerrainWalkable uses TerrainPathability
endlibrary
//TESH.scrollpos=39
//TESH.alwaysfold=0
library CameraEQNoise /* v1.0
*************************************************************************************
*
* Create timed "cameraEQnoise".
*
* The code is not 100% local traffic safe. Do not use
* the listed API in a GetLocalPlayer block!
*
*************************************************************************************
*
* */ requires /*
*
* */ TimerUtils /*
*
************************************************************************************
*
* 1. Import instruction
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Copy CameraEQNoise and TimerUtils into to your map.
* If you are using TimerUtilsEx, change the requirement
* from TimerUtils to TimerUtilsEx.
**
* 2. API
* ¯¯¯¯¯¯
*
* function CameraSetEQNoise takes player whichPlayer, real magnitude, real duration returns nothing
* - Equal to the blizzard bj CameraSetEQNoiseForPlayer, but includes a timer callback.
* - Do not use this function in a local traffic ( GetLocalPlayer() ) block.
*
* function StopCameraEQNoise takes player whichPlayer returns nothing
* - Equal to the blizzard bj CameraClearNoiseForPlayer, but releases a running timer.
*
************************************************************************************************
*/
globals
private timer array clock
endglobals
function StopCameraEQNoise takes player whichPlayer returns nothing
local integer id = GetPlayerId(whichPlayer)
if (clock[id] != null) then
call ReleaseTimer(clock[id])
set clock[id] = null
endif
if (GetLocalPlayer() == whichPlayer) then
call CameraSetSourceNoise(0, 0)
call CameraSetTargetNoise(0, 0)
endif
endfunction
private function TimerExpire takes nothing returns nothing
call StopCameraEQNoise(Player(GetTimerData(GetExpiredTimer())))
endfunction
function CameraSetEQNoise takes player whichPlayer, real magnitude, real duration returns nothing
local real richter = magnitude
local integer id = GetPlayerId(whichPlayer)
local real pow
if (richter > 5.) then
set richter = 5.
elseif (richter < 2.) then
set richter = 2.
endif
call StopCameraEQNoise(whichPlayer)
set clock[id] = NewTimerEx(id)
call TimerStart(clock[id], duration, false, function TimerExpire)
set pow = magnitude*Pow(10, richter)
if (GetLocalPlayer() == whichPlayer) then
call CameraSetTargetNoiseEx(magnitude*2., pow, true)
call CameraSetSourceNoiseEx(magnitude*2., pow, true)
endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library IsDestructableTree uses optional UnitIndexer /* v1.3.1
*************************************************************************************
*
* Detect whether a destructable is a tree or not.
*
***************************************************************************
*
* Credits
*
* To PitzerMike
* -----------------------
*
* for IsDestructableTree
*
*************************************************************************************
*
* Functions
*
* function IsDestructableTree takes destructable d returns boolean
*
* function IsDestructableAlive takes destructable d returns boolean
*
* function IsDestructableDead takes destructable d returns boolean
*
* function IsTreeAlive takes destructable tree returns boolean
* - May only return true for trees.
*
* function KillTree takes destructable tree returns boolean
* - May only kill trees.
*
*/
globals
private constant integer HARVESTER_UNIT_ID = 'hpea'//* human peasant
private constant integer HARVEST_ABILITY = 'Ahrl'//* ghoul harvest
private constant integer HARVEST_ORDER_ID = 0xD0032//* harvest order ( 852018 )
private constant player NEUTRAL_PLAYER = Player(PLAYER_NEUTRAL_PASSIVE)
private unit harvester = null
endglobals
function IsDestructableTree takes destructable d returns boolean
//* 851973 is the order id for stunned, it will interrupt the preceding harvest order.
return (IssueTargetOrderById(harvester, HARVEST_ORDER_ID, d)) and (IssueImmediateOrderById(harvester, 851973))
endfunction
function IsDestructableDead takes destructable d returns boolean
return (GetWidgetLife(d) <= 0.405)
endfunction
function IsDestructableAlive takes destructable d returns boolean
return (GetWidgetLife(d) > .405)
endfunction
function IsTreeAlive takes destructable tree returns boolean
return IsDestructableAlive(tree) and IsDestructableTree(tree)
endfunction
function KillTree takes destructable tree returns boolean
if (IsTreeAlive(tree)) then
call KillDestructable(tree)
return true
endif
return false
endfunction
private function Init takes nothing returns nothing
static if LIBRARY_UnitIndexer then//* You may adapt this to your own indexer.
set UnitIndexer.enabled = false
endif
set harvester = CreateUnit(NEUTRAL_PLAYER, HARVESTER_UNIT_ID, 0, 0, 0)
static if LIBRARY_UnitIndexer then
set UnitIndexer.enabled = true
endif
call UnitAddAbility(harvester, HARVEST_ABILITY)
call UnitAddAbility(harvester, 'Aloc')
call ShowUnit(harvester, false)
endfunction
//* Seriously?
private module Inits
private static method onInit takes nothing returns nothing
call Init()
endmethod
endmodule
private struct I extends array
implement Inits
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
/*
* Read Me
* ---------
*
* In order to use the vJass spells your map requires the following native
*********************************************************************************
*/ native UnitAlive takes unit id returns boolean /*
*********************************************************************************
*
* Vexorians JassHelper will ignore double defined natives.
* Cohadars JassHelper will throw an error.
*
* If you are using Cohadars JassHelper you have to delete
* all double defined UnitAlive declarations.
*/
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Fireball initializer Init uses SpellIndex, Missile /*v2.0
*************************************************************************************
*
* The caster hurls a fireball towards the target location,
* dealing damage in a small aoe on impact.
*
*************************************************************************************/
//**
//* User settings:
//* ==============
globals
private constant integer FIREBALL_ABILITY = 'A007'
//* Damage options.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
//* Constant missile options.
private constant real FIREBALL_FLY_HEIGHT = 65.
//* Effect options.
private constant string ON_EXPLODE_FX = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
endglobals
//* Filter valid target units.
private function FilterUnits takes unit target, player owner returns boolean
return UnitAlive(target) and IsUnitEnemy(target, owner) and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
endfunction
//* Set the travel distance.
private constant function GetFlyDistance takes integer level returns real
return 800. + (200*level)
endfunction
//* Set the explosion aoe on impact.
private constant function GetExplosionRadius takes integer level returns real
return 200. + (0*level)
endfunction
//* Customize all missile members to your needs. For example there is also arc & curve
private function CustomizeMissile takes Missile missile, integer level returns nothing
set missile.speed = 20. + 0.*level
set missile.damage = 70. + 70.*level
set missile.collision = 32.
set missile.model = "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl"
set missile.scale = 1.
endfunction
//========================================================================
//* Fireball code. Make changes carefully.
//========================================================================
//* Uses Missile's API. Add or remove whatever you need.
private struct Fireball extends array
//* Damages close unit on missile removal.
private static method onRemove takes Missile missile returns boolean
local unit u
call GroupEnumUnitsInRange(SpellIndex.GLOBAL_GROUP, missile.x, missile.y, GetExplosionRadius(missile.data), null)
loop
set u = FirstOfGroup(SpellIndex.GLOBAL_GROUP)
exitwhen u == null
call GroupRemoveUnit(SpellIndex.GLOBAL_GROUP, u)
if FilterUnits(u, missile.owner) then
call UnitDamageTarget(missile.source, u, missile.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif
endloop
//* Run Effects.
call DestroyEffect(AddSpecialEffectTarget(ON_EXPLODE_FX, missile.dummy, "origin"))
return true
endmethod
//* Runs on missile collide with any unit. Explodes on valid targets.
private static method onCollide takes Missile missile, unit hit returns boolean
return (FilterUnits(hit, missile.owner))
endmethod
implement MissileStruct
endstruct
private function OnEffect takes nothing returns nothing
local unit source = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(source, FIREBALL_ABILITY)
local real x = GetUnitX(source)
local real y = GetUnitY(source)
local real angle = Atan2(GetSpellTargetY() - y, GetSpellTargetX() - x)
//* Create a new missile and assign its members.
local Missile missile = Missile.create(x, y, FIREBALL_FLY_HEIGHT, angle, GetFlyDistance(level), FIREBALL_FLY_HEIGHT)
//* Allows high level configuration.
call CustomizeMissile(missile, level)
//* Override user settings. These missile members are reserved.
set missile.source = source
set missile.owner = GetTriggerPlayer()
set missile.data = level
call Fireball.launch(missile)
set source = null
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectEvent(FIREBALL_ABILITY, function OnEffect)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library IceSiege initializer Init uses SpellIndex /*v2.0
*************************************************************************************
*
* The caster covers himself with a amor of ice.
* The armor slows and damages nearby foes.
*
*************************************************************************************
*
* uses TimerUtils, UnitIndexer
* optional DummyCaster, SpellEffectEvent, Alloc
*
*************************************************************************************/
//**
//* User settings:
//* ==============
globals
private constant integer ICE_SIEGE_ABILITY = 'A001'
private constant real TIMER_TIMEOUT = 1.0//* The interval between each periodic damage event
//* Damage options.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
//* Effect options.
private constant string ON_CASTER_FX = "Abilities\\Spells\\Undead\\FrostArmor\\FrostArmorTarget.mdl"
private constant string FX_ATTACH_POINT = "chest"
//* Buff variables.
private constant integer BUFF_CAST_ID = 'A004'//* Raw code of the Ice Siege Apply buff ability - Object Editor (F6)
private constant integer ORDER_ID = 852226//* Order of the Ice Siege Apply buff ability - Trigger Editor (OrderIds) --> Frostnova
endglobals
//* Filter out which units should recieve period damage.
private function FilterUnits takes unit target, player p returns boolean
return (UnitAlive(target)) and (IsUnitEnemy(target, p)) and (not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE))
endfunction
//* Set the damage.
private constant function GetDamage takes integer level returns real
return 45. + (10*level)
endfunction
//* Set how often periodic damage is applied.
private constant function GetWaves takes integer level returns integer
return 4 + 0*level
endfunction
//* Set the collision size of the spell.
private constant function GetRange takes integer level returns integer
return 400 + 100*level
endfunction
//========================================================================
//* Ice siege code. Make changes carefully.
//========================================================================
private function OnPeriodic takes nothing returns nothing
local SpellIndex dex = GetTimerData(GetExpiredTimer())
local unit u
if (dex.count > 0) and UnitAlive(dex.source) then
set dex.count = dex.count - 1
call GroupEnumUnitsInRange(SpellIndex.GLOBAL_GROUP, GetUnitX(dex.source), GetUnitY(dex.source), dex.collision, null)
loop
set u = FirstOfGroup(SpellIndex.GLOBAL_GROUP)
exitwhen u == null
call GroupRemoveUnit(SpellIndex.GLOBAL_GROUP, u)
if (FilterUnits(u, dex.user)) then
call DummyCaster[BUFF_CAST_ID].castTarget(dex.user, 1, ORDER_ID, u)
call UnitDamageTarget(dex.source, u, dex.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif
endloop
else
call dex.destroy()//* Nulls all members automatically.
call ReleaseTimer(GetExpiredTimer())
endif
endfunction
private function OnEffect takes nothing returns nothing
local integer level = GetUnitAbilityLevel(GetTriggerUnit(), ICE_SIEGE_ABILITY)
//* Allocate a spell index and assign members.
local SpellIndex dex = SpellIndex.create()
set dex.user = GetTriggerPlayer()
set dex.source = GetTriggerUnit()
set dex.damage = GetDamage(level)
set dex.collision = GetRange(level)
set dex.count = GetWaves(level)
set dex.fx = AddSpecialEffectTarget(ON_CASTER_FX, dex.source, FX_ATTACH_POINT)
call TimerStart(NewTimerEx(dex), TIMER_TIMEOUT, true, function OnPeriodic)
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectEvent(ICE_SIEGE_ABILITY, function OnEffect)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Chaosflare initializer Init uses SpellIndex /* v2.0
*************************************************************************************
*
* The caster concentrates his power to let loose a powerful beam of fire
* towards the target, dealing damage and burning the target.
*
*************************************************************************************/
//**
//* User settings:
//* ==============
globals
private constant integer CHAOSFLARE_ABILITY = 'A002'
private constant real TIMER_TIMEOUT = 1.0//* The interval between two dot damage events.
//* Damage options.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
//* Effects options.
private constant string FLAME_FX = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl"
private constant real SPACE_BETWEEN_FLAMES = 75.
private constant integer MAX_FLAMES = 15//* Performance protection.
private constant string ON_CASTER_FX = "Abilities\\Spells\\Other\\Volcano\\VolcanoDeath.mdl"
private constant string ON_TARGET_FX = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
private constant string DOT_FX = "Abilities\\Spells\\NightElf\\Immolation\\ImmolationDamage.mdl"
private constant string DOT_FX_ATTACH_POINT = "origin"
//* Buff variables.
private constant integer BUFF_ID = 'B000'//* Raw code of the Chaosflare buff - Object Editor (F6)
private constant integer BUFF_CAST_ID = 'A000'//* Raw code of the Chaosflare Apply buff ability - Object Editor (F6)
private constant integer ORDER_ID = 852075//* Order of the Chaosflare Apply buff ability - Trigger Editor (OrderIds) --> slow
endglobals
//* Set the impact damage.
private constant function GetImpactDamage takes integer level returns real
return 45. + (10*level)
endfunction
//* Set the periodic damage.
private constant function GetDamageOverTime takes integer level returns real
return 20. + (10*level)
endfunction
//* Set how often damage over time is applied.
private constant function GetDamageOverTimeCount takes integer level returns integer
return 4 + 0*level
endfunction
//========================================================================
//* Chaosflare code. Make changes carefully.
//========================================================================
private function OnPeriodic takes nothing returns nothing
local SpellIndex dex = GetTimerData(GetExpiredTimer())
if (dex.count <= 0) or not UnitAlive(dex.target) or (GetUnitAbilityLevel(dex.target, BUFF_ID) == 0) then
call UnitRemoveAbility(dex.target, BUFF_ID)
call ReleaseTimer(GetExpiredTimer())
call dex.destroy()//* Nulls members automatically.
return
//* Deal damage.
elseif (GetUnitTypeId(dex.source) != 0) then
call UnitDamageTarget(dex.source, dex.target, dex.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
call DestroyEffect(AddSpecialEffectTarget(DOT_FX, dex.target, DOT_FX_ATTACH_POINT))
endif
set dex.count = dex.count - 1
endfunction
private function OnEffect takes nothing returns nothing
local unit source = GetTriggerUnit()
local unit target = GetSpellTargetUnit()
local real posX = GetUnitX(source)
local real posY = GetUnitY(source)
local real angle = Atan2(GetUnitY(target) - posY, GetUnitX(target) - posX)
local real cos = SPACE_BETWEEN_FLAMES*Cos(angle)
local real sin = SPACE_BETWEEN_FLAMES*Sin(angle)
local integer level = GetUnitAbilityLevel(source, CHAOSFLARE_ABILITY)
local integer i = 0
local SpellIndex dex
//* Run effects.
call DestroyEffect(AddSpecialEffect(ON_CASTER_FX, posX, posY))
call DestroyEffect(AddSpecialEffectTarget(ON_TARGET_FX, target, "origin"))
loop
exitwhen (IsUnitInRangeXY(target, posX, posY, SPACE_BETWEEN_FLAMES)) or (i == MAX_FLAMES)
set posX = posX + cos
set posY = posY + sin
call DestroyEffect(AddSpecialEffect(FLAME_FX, posX, posY))
set i = i + 1
endloop
//* Deal damage and apply the buff. Conditionally start the timer.
if UnitDamageTarget(source, target, GetImpactDamage(level), false, false, ATTACK_TYPE, DAMAGE_TYPE, null) then
if DummyCaster[BUFF_CAST_ID].castTarget(GetOwningPlayer(source), 1, ORDER_ID, target) then
//* Allocate a spell instance.
set dex = SpellIndex.create()
set dex.source = source
set dex.target = target
set dex.count = GetDamageOverTimeCount(level)
set dex.damage = GetDamageOverTime(level)
call TimerStart(NewTimerEx(dex), TIMER_TIMEOUT, true, function OnPeriodic)
endif
endif
set source = null
set target = null
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectEvent(CHAOSFLARE_ABILITY, function OnEffect)
endfunction
endlibrary
//TESH.scrollpos=12
//TESH.alwaysfold=0
library ChaoticBoulder initializer Init uses SpellIndex, IsDestructableTree /*v1.0
*************************************************************************************
*
* Hurls a boulder of magma towards the targeted point,
* splitting on impact into tiny fragments that launch into the sky.
*
*************************************************************************************/
//**
//* User settings:
//* ==============
globals
private constant integer CHAOTIC_BOULDER_ABILITY = 'A00D'
//* Damage options.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
//* Effect options.
private constant string ON_EFFECT_FX = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
private constant string ON_DAMAGE_BIG_FX = "Objects\\Spawnmodels\\Other\\NeutralBuildingExplosion\\NeutralBuildingExplosion.mdl"
endglobals
//* Filter valid target units.
private function FilterUnits takes unit target, player owner returns boolean
return UnitAlive(target) and IsUnitEnemy(target, owner) and not IsUnitType(target, UNIT_TYPE_FLYING)
endfunction
//* Set the travel distance.
private function GetSmallBoulderFlyDistance takes integer level returns real
return GetRandomReal(50., 200.)
endfunction
//* Set the explosion radius on impact.
private constant function GetExplosionRadius takes integer level returns real
return 200. + (0.*level)
endfunction
//* Set the amount of fragments.
private function GetSmallBoulderCount takes integer level returns integer
return GetRandomInt(4, 6) + 2*level
endfunction
//* Customize all missile members to your needs.
private function CustomizeSmallMissile takes Missile missile, real distance, integer level returns nothing
call missile.flightTime2Speed(GetRandomReal(1., 1.5)*distance/200.)
set missile.damage = 10. + 10.*level
set missile.collision = 96.
set missile.arc = GetRandomReal(80., 85.)*bj_DEGTORAD
set missile.model = "Abilities\\Spells\\Other\\Volcano\\VolcanoMissile.mdl"
set missile.scale = 0.8
endfunction
//* Customize all missile members to your needs.
private function CustomizeBigMissile takes Missile missile, real distance, integer level returns nothing
call missile.flightTime2Speed(GetRandomReal(1., 2.)*distance/800.)
set missile.damage = 200. + 70.*level
set missile.arc = 45.*bj_DEGTORAD + RMinBJ(20., distance*.1)*bj_DEGTORAD
set missile.collision = 128.
set missile.model = "Abilities\\Spells\\Other\\Volcano\\VolcanoMissile.mdl"
set missile.scale = 1.2
endfunction
//========================================================================
//* Chaotic boulder code. Make changes carefully.
//========================================================================
//* Uses Missile's API. Add or remove whatever you need.
private struct ChaoticBoulder extends array
//* Only living trees are valid targets.
private static method onDestructable takes Missile missile, destructable hit returns boolean
if IsDestructableTree(hit) and (GetWidgetLife(hit) > .405) then
call KillDestructable(hit)
endif
return false
endmethod
private static method createSmallBoulders takes Missile big returns nothing
local integer level = GetUnitAbilityLevel(big.source, CHAOTIC_BOULDER_ABILITY)
local integer dex = 0
local integer max = GetSmallBoulderCount(level)
local Missile small
loop
exitwhen (dex >= max)
set small = Missile.create(big.x, big.y, 0., GetRandomReal(-bj_PI, bj_PI), GetSmallBoulderFlyDistance(level), 0.)
call CustomizeSmallMissile(small, small.origin.distance, level)
set small.source = big.source
set small.owner = big.owner
set small.data = 0
call thistype.launch(small)
set dex = dex + 1
endloop
endmethod
//* Damages close unit on missile removal.
private static method onRemove takes Missile missile returns boolean
local unit u
call GroupEnumUnitsInRange(SpellIndex.GLOBAL_GROUP, missile.x, missile.y, missile.collision, null)
loop
set u = FirstOfGroup(SpellIndex.GLOBAL_GROUP)
exitwhen u == null
call GroupRemoveUnit(SpellIndex.GLOBAL_GROUP, u)
if FilterUnits(u, missile.owner) then
call UnitDamageTarget(missile.source, u, missile.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif
endloop
//* Detect big boulder.
if (missile.data == -1) then
//* Runs effects.
call DestroyEffect(AddSpecialEffect(ON_DAMAGE_BIG_FX, missile.x, missile.y))
call createSmallBoulders(missile)
endif
return true
endmethod
implement MissileStruct
endstruct
private function OnEffect takes nothing returns nothing
local unit source = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(source, CHAOTIC_BOULDER_ABILITY)
local real x = GetUnitX(source)
local real y = GetUnitY(source)
//* Create the main missile.
local Missile missile = Missile.createXYZ(x, y, GetUnitFlyHeight(source), GetSpellTargetX(), GetSpellTargetY(), 0.)
call CustomizeBigMissile(missile, missile.origin.distance, level)
set missile.source = source
set missile.data = -1
set missile.owner = GetTriggerPlayer()
call ChaoticBoulder.launch(missile)
//* Runs Effects.
call DestroyEffect(AddSpecialEffect(ON_EFFECT_FX, x, y))
set source = null
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectEvent(CHAOTIC_BOULDER_ABILITY, function OnEffect)
endfunction
endlibrary
//TESH.scrollpos=3
//TESH.alwaysfold=0
library BoneSpirit initializer Init uses SpellIndex, Missile /*v2.0
*************************************************************************************
*
* The Spirit tracks down a target, or finds one of its own.
* On impact the spirit stuns the target.
* © Blizzard Entertainment, Diablo II
*
*************************************************************************************/
//**
//* User settings:
//* ==============
globals
private constant integer BONE_SPIRIT_ABILITY = 'A008'
//* Damage options.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
private constant boolean HIT_TARGET_ONLY = false//* If false the spirit may also collide with any enemy unit in this path.
//* Missile options. For true it only hit the unit it's chasing after.
private constant real SPIRIT_FLY_HEIGHT = 65.
private constant real UNIT_DETECTION_AOE = 400.//* Searches nerby enemies within this range.
private constant real COLLISION_SIZE = 32.
private constant real DETECTION_DELAY = 0.3//* Short delay after losing a target, before new target is considered.
private constant real SPIRIT_SCALE = 0.9
private constant string SPIRIT_MODEL = "Abilities\\Spells\\Undead\\Possession\\PossessionMissile.mdl"
//* Buff variables.
private constant integer BUFF_CAST_ID = 'A009'//* Raw code of the Bone Spirit apply buff ability - Object Editor (F6)
private constant integer ORDER_ID = 852095//* Order of the Bone Spirit apply buff ability - Trigger Editor (OrderIds) --> thunderbolt
endglobals
//* Filter out which units should be damaged.
private function FilterUnits takes unit target, player p returns boolean
return (UnitAlive(target)) and (IsUnitEnemy(target, p)) and (not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE))
endfunction
//* Set the maximum fly distance.
private constant function GetFlyDistance takes integer level returns real
return 800. + (250*level)
endfunction
//* Set the movement speed for the spirit.
private constant function GetMovementSpeed takes integer level returns real
return 440. + (0*level)
endfunction
//* Set the impact damage.
private constant function GetImpactDamage takes integer level returns real
return 0. + (90.*level)
endfunction
//========================================================================
//* Bone spirit code. Make changes carefully.
//========================================================================
globals
//* "tempOwner" is always set to the owner of the spirit before "ConsiderUnitFiltered" is called.
private player tempOwner = null
endglobals
//* Filters valid targets for the spirit.
private function ConsiderUnitsFiltered takes nothing returns boolean
return FilterUnits(GetFilterUnit(), tempOwner)
endfunction
//* Uses Missile's API. Add or remove whatever you need.
private struct BoneSpirit extends array
static boolexpr filter
static real array delay
//* Removes the effect delayed.
private static method onRemove takes Missile missle returns boolean
return true
endmethod
//* Runs on missile collide with any unit.
private static method onCollide takes Missile missile, unit hit returns boolean
if (FilterUnits(hit, missile.owner)) then
static if HIT_TARGET_ONLY then
if (hit != missile.target) then
return false
endif
endif
call DummyCaster[BUFF_CAST_ID].castTarget(missile.owner, 1, ORDER_ID, hit)
call UnitDamageTarget(missile.source, hit, missile.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
return true
endif
return false
endmethod
//* Checks for new valid targets.
private static method onPeriod takes Missile missile returns boolean
set delay[missile] = delay[missile] - Missile_TIMER_TIMEOUT
if not UnitAlive(missile.target) and (delay[missile] <= 0.) then
set delay[missile] = DETECTION_DELAY
set tempOwner = missile.owner
call GroupEnumUnitsInRange(SpellIndex.GLOBAL_GROUP, missile.x, missile.y, UNIT_DETECTION_AOE , thistype.filter)
if (FirstOfGroup(SpellIndex.GLOBAL_GROUP) != null) then
set bj_groupRandomConsidered = 0
set bj_groupRandomCurrentPick = null
call ForGroup(SpellIndex.GLOBAL_GROUP, function GroupPickRandomUnitEnum)
call GroupClear(SpellIndex.GLOBAL_GROUP)//* Not really required. GroupEnum functions always clear groups.
set missile.target = bj_groupRandomCurrentPick
set bj_groupRandomCurrentPick = null
endif
endif
return false
endmethod
implement MissileStruct
endstruct
function OnEffect takes nothing returns nothing
local unit source = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(source, BONE_SPIRIT_ABILITY)
local real x = GetUnitX(source)
local real y = GetUnitY(source)
local real angle = Atan2(GetSpellTargetY() - y, GetSpellTargetX() - x)
//* Create a new missile and assign members.
local Missile missile = Missile.create(x, y, SPIRIT_FLY_HEIGHT, angle, GetFlyDistance(level), SPIRIT_FLY_HEIGHT)
set missile.damage = GetImpactDamage(level)
set missile.collision = COLLISION_SIZE
set missile.source = source
set missile.target = GetSpellTargetUnit()
set missile.owner = GetTriggerPlayer()
set missile.speed = GetMovementSpeed(level)*Missile_TIMER_TIMEOUT
set missile.model = SPIRIT_MODEL
set missile.scale = SPIRIT_SCALE
call BoneSpirit.launch(missile)
//*
set BoneSpirit.delay[missile] = DETECTION_DELAY
set source = null
endfunction
private function Init takes nothing returns nothing
set BoneSpirit.filter = Filter(function ConsiderUnitsFiltered)
call RegisterSpellEffectEvent(BONE_SPIRIT_ABILITY, function OnEffect)
endfunction
endlibrary
//TESH.scrollpos=15
//TESH.alwaysfold=0
library ReapersScythe initializer Init uses SpellIndex /*v2.0
*************************************************************************************
*
* Brings a target to full realization of its own mortality,
* dealing damage based on how much life the target is missing. Stuns for 1.5 second.
*
*************************************************************************************/
//**
//* User settings:
//* ==============
globals
private constant integer REAPERS_SCYTHE_ABILITY = 'A005'
//* Damage type options.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
//* Effect options.
private constant string ON_GROUND_FX = "Doodads\\BlackCitadel\\Props\\RuneArt\\RuneArt1.mdl"
private constant string GROUND_FX_ATTACH_POINT = "origin"
private constant string ON_DAMAGE_FX = "Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl"
private constant string DAMAGE_FX_ATTACH_POINT = "origin"
//* Buff variables. You may want to integrate you own stun system.
private constant real STUN_DURATION = 1.5//* Must also be adjusted in the object editor.
private constant integer BUFF_ID = 'B001'//* Raw code of the Reaper's Scythe buff - Object Editor (F6)
private constant integer BUFF_CAST_ID = 'A006'//* Raw code of the Reaper's Scythe Apply Buff ability - Object Editor (F6)
private constant integer ORDER_ID = 852095//* Order of the Reaper's Scythe Apply Buff ability - Trigger Editor (OrderIds) --> thunderbolt
endglobals
//* Set the damage factor. The original Dota setup would be 0.4, 0.6, 0.9
private constant function GetDamageFactor takes integer level returns real
return 0.2 + (0.2*level)
endfunction
//========================================================================
//* Reaper's scythe code. Make changes carefully.
//========================================================================
private function Callback takes nothing returns nothing
local SpellIndex dex = GetTimerData(GetExpiredTimer())
local real life = GetUnitState(dex.target, UNIT_STATE_MAX_LIFE) - GetWidgetLife(dex.target)
if UnitAlive(dex.target) and (GetUnitTypeId(dex.source) != 0) then
call DestroyEffect(AddSpecialEffectTarget(ON_DAMAGE_FX, dex.target, DAMAGE_FX_ATTACH_POINT))
call UnitDamageTarget(dex.source, dex.target, dex.damage*life, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif
call dex.destroy()//* Null automatically all members.
call ReleaseTimer(GetExpiredTimer())
endfunction
private function OnEffect takes nothing returns nothing
//* Get event variables now, because DummyCaster will override them.
local unit target = GetSpellTargetUnit()
local unit source = GetTriggerUnit()
local SpellIndex dex
//* Conditionally create a spell index.
if DummyCaster[BUFF_CAST_ID].castTarget(GetTriggerPlayer(), 1, ORDER_ID, target) then
set dex = SpellIndex.create()
set dex.source = source
set dex.target = target
//* Hardcoded ubersplat handle. I think you'll like it.
set dex.splat = CreateUbersplat(GetUnitX(target), GetUnitY(target), "HFS1", 255, 255, 255, 150, true, true)
set dex.fx = AddSpecialEffectTarget(ON_GROUND_FX, target, GROUND_FX_ATTACH_POINT)
set dex.damage = GetDamageFactor(GetUnitAbilityLevel(source, REAPERS_SCYTHE_ABILITY))
call SetUbersplatRenderAlways(dex.splat, true)
call TimerStart(NewTimerEx(dex), STUN_DURATION, false, function Callback)
endif
set source = null
set target = null
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectEvent(REAPERS_SCYTHE_ABILITY, function OnEffect)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Leap initializer Init uses SpellIndex, IsTerrainWalkable, CameraEQNoise/*v1.0
*************************************************************************************
*
* Leap into the air, dealing damage to all close enemies of your destination
* and slowing their movement speed by 20% for 3 seconds.
*
*************************************************************************************/
//**
//* User settings:
//* ==============
//* The slow duration is hardcoded in the leap buff. Change it there.
globals
private constant integer LEAP_ABILITY = 'A003'
//* Damage type options.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
//* Effect options
private constant string ON_LANDING_FX = "Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl"
private constant string WHILE_LEAPING_FX = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile_mini.mdl"
private constant string FX_ATTACH_POINT = "weapon"
//* Buff variables.
private constant integer BUFF_CAST_ID = 'A00B'//* Raw code of the Leap Apply buff ability - Object Editor (F6)
private constant integer ORDER_ID = 852075//* Order of the Leap Apply buff ability - Trigger Editor (OrderIds) --> slow
endglobals
//* Set the impact damage.
private constant function GetImpactDamage takes integer level returns real
return 0. + 70*level
endfunction
//* Set the arc of the unit. Play a bit with the values to find a good setup.
private function GetArc takes real distance returns real
return 45.*bj_DEGTORAD + RMinBJ(20., distance*.1)*bj_DEGTORAD
endfunction
//* Set the damage collision radius on leap finish.
private function GetDamageRadius takes integer level returns real
return 190. + 0.*level
endfunction
//* Set the total air time. Returning 0. is invalid!
//* The current setup is --> minimum + (distance/2)/movement speed.
private function GetAirTime takes real distance, integer level returns real
return .3 + (distance*.5)/500.
endfunction
//* Set the offset for the damage effects. It's an empirical value.
private constant function GetUnitWeaponOffset takes unit source returns real
return 75.
endfunction
//* Read the animation time out from the object editor field for your unit.
//* The current setup is --> slam animation time blademaster 1.133
private constant function GetUnitAnimationTime takes unit source returns real
return 1.133
endfunction
//* Filter valid targets.
private function FilterUnits takes unit target, player owner returns boolean
return UnitAlive(target) and IsUnitEnemy(target, owner) and not IsUnitType(target, UNIT_TYPE_FLYING)
endfunction
//* Code which should run on finish leap.
private function OnFinishLeap takes unit source returns nothing
if UnitAlive(source) then
call CameraSetEQNoise(GetOwningPlayer(source), 3., .33)
endif
endfunction
//========================================================================
//* Leap code. Make changes carefully.
//========================================================================
//* Quite useful, you may outsource it and make it a public function.
private function IsPointJumpable takes real x, real y returns boolean
if not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then
return IsTerrainWalkable(x, y)
endif
return false
endfunction
globals
private sound error
endglobals
//* Uses Missile's API. For a destructable enum, add .onDestructable and .onDestructableFilter.
private struct Leap extends array
//* Runs on finish.
static method onFinish takes Missile missile returns boolean
local unit source = missile.dummy
local real unitX = GetUnitX(source)
local real unitY = GetUnitY(source)
local real posX = unitX + GetUnitWeaponOffset(source)*Cos(missile.angle)
local real posY = unitY + GetUnitWeaponOffset(source)*Sin(missile.angle)
local unit u
//* Restore pathing.
call SetUnitPathing(source, true)
if UnitAlive(source) then
//* Run effects.
call DestroyEffect(AddSpecialEffect(ON_LANDING_FX, posX, posY))
call GroupEnumUnitsInRange(SpellIndex.GLOBAL_GROUP, posX, posY, missile.collision, null)
loop
set u = FirstOfGroup(SpellIndex.GLOBAL_GROUP)
exitwhen u == null
call GroupRemoveUnit(SpellIndex.GLOBAL_GROUP, u)
if (FilterUnits(u, missile.owner)) then
//* Make pathing space for the leaping unit.
call SetUnitPosition(u, GetUnitX(u), GetUnitY(u))
//* Deal damage and apply the buff.
if (UnitDamageTarget(source, u, missile.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)) then
call DummyCaster[BUFF_CAST_ID].castTarget(missile.owner, 1, ORDER_ID, u)
endif
endif
endloop
endif
call SetUnitPosition(source, unitX, unitY)
call OnFinishLeap(source)
call SetUnitTimeScale(source, 1.)
call SpellIndex(missile.data).destroy()
set source = null
return true
endmethod
implement MissileStruct
endstruct
private function OnEffect takes nothing returns nothing
local string prefix = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n|cffffcc00"
local unit source = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(source, LEAP_ABILITY)
local real time
local real distance
local Missile missile
//* Check for terrain pathability.
if IsPointJumpable(GetSpellTargetX(), GetSpellTargetY()) then
//* Make sure the caster can fly.
if UnitAddAbility(source, 'Amrf') and UnitRemoveAbility(source, 'Amrf') then
endif
//* Transform the source unit into a Missile instance.
set missile = Missile.createEx(source, GetSpellTargetX(), GetSpellTargetY(), GetUnitDefaultFlyHeight(source))
set distance = missile.origin.distance
set time = GetAirTime(distance, level)
set missile.arc = GetArc(distance)
set missile.speed = distance/time*Missile_TIMER_TIMEOUT
set missile.owner = GetTriggerPlayer()
set missile.damage = GetImpactDamage(level)
set missile.collision = GetDamageRadius(level)
call Leap.launch(missile)
//* Add effect.
set missile.data = SpellIndex.create()
set SpellIndex(missile.data).fx = AddSpecialEffectTarget(WHILE_LEAPING_FX, source, FX_ATTACH_POINT)
//*
call SetUnitTimeScale(source, GetUnitAnimationTime(source)*.5/time)
call SetUnitPathing(source, false)
else
//* Invalid jump location!
call PauseUnit(source, true)
call IssueImmediateOrderById(source, 851972)
call PauseUnit(source, false)
//* Simulate Warcraft III error message.
if GetLocalPlayer() == GetTriggerPlayer() then
call StartSound(error)
call ClearTextMessages()
endif
call DisplayTimedTextToPlayer(GetTriggerPlayer(), .52, .96, 2., prefix + GetUnitName(source) + " can't jump there!|r")
endif
set source = null
endfunction
private function Init takes nothing returns nothing
set error = CreateSoundFromLabel("InterfaceError", false, false, false, 10, 10)
call RegisterSpellEffectEvent(LEAP_ABILITY, function OnEffect)
endfunction
endlibrary
//TESH.scrollpos=81
//TESH.alwaysfold=0
library Whirlwind initializer Init uses SpellIndex /* v2.0
*************************************************************************************
*
* The caster performs a deadly dance, dealing damage to all nearby units.
* Units using whirlwind need a spin-walk animation i.e the blademaster.
*
*************************************************************************************/
//**
//* User settings:
//* ==============
globals
//* Only works for immolation based abilities.
private constant integer WHIRLWIND_ABILITY = 'A00A'
private constant integer WHIRLWIND_BUFF = 'B003'
//* Do you wish to disable the default attack during whirlwind?
private constant boolean DISABLE_ATTACK = true
//* Damage type options.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
//* Set how often damage is dealt. Adjust the damage along with the timeout.
private constant real TIMER_TIMEOUT = 0.25
endglobals
//* Set the damage dealt per timer timeout.
private constant function GetDamage takes integer level returns real
return 0.00 + level*50.00
endfunction
//* Set the collision size.
private constant function GetCollision takes integer level returns real
return 200.
endfunction
//* Filter valid target units.
private function FilterUnits takes unit target, player owner returns boolean
return IsUnitEnemy(target, owner) and UnitAlive(target)
endfunction
//========================================================================
//* Whirlwind code. Make changes carefully.
//========================================================================
globals
//* Reference casters.
private Table table
endglobals
private function Clear takes SpellIndex dex returns nothing
static if DISABLE_ATTACK then
if (dex.count == 0) then
call UnitRemoveAbility(dex.source, 'Abun')
endif
endif
call table.remove(GetHandleId(dex.source))
call AddUnitAnimationProperties(dex.source, "spin", false)
call UnitRemoveAbility(dex.source, WHIRLWIND_BUFF)
call ReleaseTimer(dex.clock)
call dex.destroy()
endfunction
private function OnPeriodic takes nothing returns nothing
local SpellIndex dex = GetTimerData(GetExpiredTimer())
local unit u
if UnitAlive(dex.source) and (GetUnitAbilityLevel(dex.source, WHIRLWIND_BUFF) != 0) then
call GroupEnumUnitsInRange(SpellIndex.GLOBAL_GROUP, GetUnitX(dex.source), GetUnitY(dex.source), dex.collision, null)
loop
set u = FirstOfGroup(SpellIndex.GLOBAL_GROUP)
exitwhen null == u
call GroupRemoveUnit(SpellIndex.GLOBAL_GROUP, u)
if FilterUnits(u, dex.user) then
call UnitDamageTarget(dex.source, u, dex.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif
endloop
else
call Clear(dex)
endif
endfunction
private function OnEffect takes nothing returns nothing
local unit source = GetTriggerUnit()
local SpellIndex dex = SpellIndex.create()
set dex.source = source
set dex.level = GetUnitAbilityLevel(source, WHIRLWIND_ABILITY)
set dex.damage = GetDamage(dex.level)
set dex.collision = GetCollision(dex.level)
set dex.user = GetTriggerPlayer()
//* Run effects.
call SetUnitAnimation(source, "spin")
call AddUnitAnimationProperties(source, "spin", true)
static if DISABLE_ATTACK then
set dex.count = GetUnitAbilityLevel(source, 'Abun')
if (dex.count == 0) then
call UnitAddAbility(source, 'Abun')
endif
endif
set dex.clock = NewTimerEx(dex)
call TimerStart(dex.clock, TIMER_TIMEOUT, true, function OnPeriodic)
//*
set table[GetHandleId(source)] = dex
set source = null
endfunction
private function OnOrder takes nothing returns nothing
local unit source = GetTriggerUnit()
if (GetIssuedOrderId() == 852178) and (GetUnitAbilityLevel(source, WHIRLWIND_BUFF) != 0) and table.has(GetHandleId(source)) then
//* For pause units the clear function will not work at all.
if not (IsUnitPaused(source)) then
call Clear(table[GetHandleId(source)])
endif
endif
set source = null
endfunction
private function Init takes nothing returns nothing
set table = Table.create()
call RegisterSpellEffectEvent(WHIRLWIND_ABILITY, function OnEffect)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function OnOrder)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library FistOfThunder initializer Init uses SpellIndex /* v1.0
*************************************************************************************
*
* Slams the ground in front of you, dealing damage to close enemies.
* Emerging lightnings deal extra damage, when colliding with an enemy.
*
*************************************************************************************/
//**
//* User settings:
//* ==============
globals
private constant integer FIST_OF_THUNDER_ABILITY = 'A00C'
//* Damage options.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
//* Set the lightning end z.
private constant real MISSILE_END_Z = 175.
//* Effect options.
private constant string LIGHTNING_MODEL = "CLPB"
private constant string SLAM_CENTER_FX = "Abilities\\Spells\\Orc\\Purge\\PurgeBuffTarget.mdl"
private constant real SLAM_CENTER_FX_DURATION = 2.
//* Concept.
private constant boolean DESTROY_MISSILE_ON_COLLIDE = true
endglobals
//* Set the slam collision size.
private constant function GetSlamCollisionSize takes integer level returns real
return 180.
endfunction
//* Set the damage dealt on slam.
private constant function GetSlamDamage takes integer level returns real
return 40.*level - 10.
endfunction
//* Set the travel distance for each missile.
private constant function GetMissileFlyDistance takes integer level returns real
return 600. + (50*level)
endfunction
//* Set the amount of Missiles based on the ability level.
private constant function GetMissileCount takes integer level returns integer
return 4 + 2*level
endfunction
//* Filter valid target units.
private function FilterUnits takes unit target, player owner returns boolean
return UnitAlive(target) and IsUnitEnemy(target, owner) and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
endfunction
//* Customize all missile members to your needs.
private function CustomizeMissile takes Missile missile, integer level returns nothing
set missile.speed = 22. + 0.*level
set missile.damage = 10. + 10.*level
set missile.collision = 32.
set missile.model = "Abilities\\Weapons\\SpiritOfVengeanceMissile\\SpiritOfVengeanceMissile.mdl"
set missile.scale = 1.
endfunction
//* These effects run on spell effect event. Customize them to your needs.
private function RunOnSlamEffects takes unit source, real slamX, real slamY returns nothing
//* Runs sounds.
local string file = "Abilities\\Spells\\Orc\\LightningBolt\\LightningBolt.wav"
local sound s
if (GetRandomInt(0, 1) == 0) then
set file = "Abilities\\Spells\\Orc\\LightningShield\\LightningShieldTarget.wav"
endif
set s = CreateSound(file, false, false, false, 10, 10, "")
call AttachSoundToUnit(s, source)
call StartSound(s)
call KillSoundWhenDone(s)
//* Run special effects.
call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl", slamX, slamY))
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Human\\StormBolt\\StormBoltMissile.mdl", source, "weapon,right"))
//*
set s = null
endfunction
//* These effects run when a missile ( source ) collides with a unit ( hit )
private function RunOnCollideEffects takes unit source, unit hit returns nothing
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl", hit, "origin"))
endfunction
//========================================================================
//* Fist of Thunder code. Make changes carefully.
//========================================================================
//* Uses Missile's API. For a destructable enum, add .onDestructable and .onDestructableFilter.
private struct FistOfThunder extends array
//* Releases the lightning model.
private static method onRemove takes Missile missile returns boolean
call SpellIndex(missile.data).destroy()
return true
endmethod
static method onCollide takes Missile missile, unit hit returns boolean
if FilterUnits(hit, missile.owner) then
call UnitDamageTarget(missile.source, hit, missile.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
call RunOnCollideEffects(missile.dummy, hit)
return DESTROY_MISSILE_ON_COLLIDE
endif
return false
endmethod
static method onPeriod takes Missile missile returns boolean
local MissilePosition pos = missile.origin
call MoveLightningEx(SpellIndex(missile.data).flash, true, pos.x, pos.y, pos.z, missile.x, missile.y, missile.z + missile.terrainZ)
return false
endmethod
implement MissileStruct
endstruct
private function Callback takes nothing returns nothing
static if LIBRARY_TimerUtilsEx then
call SpellIndex(ReleaseTimer(GetExpiredTimer())).destroy()
else
call SpellIndex(GetTimerData(GetExpiredTimer())).destroy()
call ReleaseTimer(GetExpiredTimer())
endif
endfunction
private function CreateMissiles takes unit source, player user, real x, real y, integer level returns nothing
local integer dex = 0
local integer end = GetMissileCount(level)
local real spacing = bj_PI*2/end
local real angle = GetRandomReal(-bj_PI, bj_PI)
local real distance = GetMissileFlyDistance(level)
local Missile missile
loop
exitwhen (dex == end)
//* Create a new missile and assign it's members.
set missile = Missile.create(x, y, 0., angle, distance, MISSILE_END_Z)
call CustomizeMissile(missile, level)
set missile.source = source
set missile.owner = user
set missile.data = SpellIndex.create()
set SpellIndex(missile.data).flash = AddLightningEx(LIGHTNING_MODEL, true, x, y, missile.origin.z, x, y, missile.z + missile.terrainZ)
call FistOfThunder.launch(missile)
//* Next missile.
set angle = angle + spacing
set dex = dex + 1
endloop
endfunction
private function OnEffect takes nothing returns nothing
local unit source = GetTriggerUnit()
local player user = GetTriggerPlayer()
local integer level = GetUnitAbilityLevel(source, FIST_OF_THUNDER_ABILITY)
local real x = GetSpellTargetX()
local real y = GetSpellTargetY()
local real damage = GetSlamDamage(level)
local SpellIndex dex = SpellIndex.create()
local unit u
//* Runs Effects.
call RunOnSlamEffects(source, x, y)
call GroupEnumUnitsInRange(SpellIndex.GLOBAL_GROUP, x, y, GetSlamCollisionSize(level), null)
loop
set u = FirstOfGroup(SpellIndex.GLOBAL_GROUP)
exitwhen u == null
call GroupRemoveUnit(SpellIndex.GLOBAL_GROUP, u)
if (FilterUnits(u, user)) then
call UnitDamageTarget(source, u, damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif
endloop
//* Create all missiles.
call CreateMissiles(source, user, x, y, level)
//* Create center model
set dex.fx = AddSpecialEffect(SLAM_CENTER_FX, x, y)
call TimerStart(NewTimerEx(dex), SLAM_CENTER_FX_DURATION, false, function Callback)
set user = null
set source = null
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectEvent(FIST_OF_THUNDER_ABILITY, function OnEffect)
endfunction
endlibrary
//TESH.scrollpos=75
//TESH.alwaysfold=0
library GuidedArrow initializer Init uses SpellIndex, Missile /* v2.0
*************************************************************************************
*
* The caster fires an arrow which tracks down it's target.
* On impact the arrow eventually pierces and hits the target again.
*
*************************************************************************************/
//**
//* User settings:
//* ==============
globals
private constant integer GUIDED_ARROW_ABILITY = 'A00E'
// Damage options.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
// Effect options.
private constant string ON_TARGET_FX = "Abilities\\Spells\\NightElf\\Barkskin\\BarkSkinTarget.mdl"
private constant string FX_ATTACH_POINT = "overhead"
// Constant missile options.
private constant real ARROW_ACTION_DELAY = 0.3 // Timeout before the arrow starts to turn.
private constant real UNIT_DETECTION_AOE = 400.// Area in which the arrow searches for targets.
private constant real ARROW_FLY_RUN_OUT_DISTANCE = 350.// Distance traveled if the target dies.
private constant real ARROW_FLY_HEIGHT = 65.
private constant real ARROW_TURN_RATE = 8.*bj_DEGTORAD
private constant real ALLOW_HIT_AFTER = 1. // Minimum amount of seconds until the missile can damage again.
endglobals
// Filter valid target units.
private function FilterUnits takes unit target, player owner returns boolean
return UnitAlive(target) and IsUnitEnemy(target, owner)
endfunction
// Set the maximum travel distance. Does no longer count, when a traget is found.
private constant function GetFlyDistance takes integer level returns real
return 800. + (200*level)
endfunction
// Set the number of pierce events.
private function GetPiercingCount takes integer level returns integer
return GetRandomInt(0, 3) + 1*level
endfunction
//* Customize all missile members to your needs.
private function CustomizeMissile takes Missile missile, integer level returns nothing
set missile.speed = 20. + 0.*level
set missile.damage = 0. + 30.*level
set missile.collision = 32.
set missile.model = "Abilities\\Spells\\Other\\BlackArrow\\BlackArrowMissile.mdl"
set missile.scale = 1.
endfunction
//========================================================================
// Guided arrow code. Make changes carefully.
//========================================================================
globals
// "tempOwner" is always set to the owner of the arrow before "ConsiderUnitFiltered" is called.
private player tempOwner = null
endglobals
// Filters valid targets for the arrow.
private function ConsiderUnitsFiltered takes nothing returns boolean
return FilterUnits(GetFilterUnit(), tempOwner)
endfunction
private struct GuidedArrow extends array
static boolexpr filter
// Runs when the missile is deallocated.
private static method onRemove takes Missile missile returns boolean
call SpellIndex(missile.data).destroy()
return true
endmethod
// Runs when the maximum range is reached.
private static method onFinish takes Missile missile returns boolean
local SpellIndex dex = missile.data
return (dex.phase == 0) or (GetUnitTypeId(dex.target) == 0)
endmethod
// Runs when a missile collides with a unit.
private static method onCollide takes Missile missile, unit hit returns boolean
local SpellIndex dex = missile.data
// Only the target is valid.
if (hit == missile.target) then
// Allows the missile to hit the target again after 1 second.
call missile.enableHitAfter(hit, ALLOW_HIT_AFTER)
call UnitDamageTarget(missile.source, hit, missile.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
set missile.target = null
set missile.turn = 0
// Reduce the total amount of pierces.
set dex.count = dex.count - 1
endif
return (dex.count < 0)
endmethod
private static method searchTarget takes Missile missile returns nothing
local SpellIndex dex = missile.data
//
set tempOwner = missile.owner
call GroupEnumUnitsInRange(SpellIndex.GLOBAL_GROUP, missile.x, missile.y, UNIT_DETECTION_AOE , thistype.filter)
if (FirstOfGroup(SpellIndex.GLOBAL_GROUP) != null) then
set bj_groupRandomConsidered = 0
set bj_groupRandomCurrentPick = null
call ForGroup(SpellIndex.GLOBAL_GROUP, function GroupPickRandomUnitEnum)
call GroupClear(SpellIndex.GLOBAL_GROUP)
//
set missile.target = bj_groupRandomCurrentPick
set dex.target = bj_groupRandomCurrentPick
set dex.time = ARROW_ACTION_DELAY
set missile.turn = ARROW_TURN_RATE
// Just in case this unit was already hit, while missile.target was null.
// Remember: onCollide runs before onPeriod.
call missile.removeHitWidget(bj_groupRandomCurrentPick)
//
set bj_groupRandomCurrentPick = null
if (dex.fx != null) then
call DestroyEffect(dex.fx)
set dex.fx = null
endif
set dex.fx = AddSpecialEffectTarget(ON_TARGET_FX, dex.target, FX_ATTACH_POINT)
endif
endmethod
// Runs every timer interval. Let's break the mechanics down.
private static method onPeriod takes Missile missile returns boolean
local SpellIndex dex = missile.data
// In this phase no target was found yet. And no pierce event did take place.
if (dex.target == null) and (dex.count >= 0) then
// Check the action delay.
if (dex.time > 0.) then
set dex.time = dex.time - Missile_TIMER_TIMEOUT
return false
endif
call searchTarget(missile)
//
// In this phase we have a target stored on dex.target, but the missile doesn't know that.
elseif (missile.target == null) and (UnitAlive(dex.target)) then
// Check the action delay.
if (dex.time > 0.) then
set dex.time = dex.time - Missile_TIMER_TIMEOUT
return false
endif
set dex.time = ARROW_ACTION_DELAY
set missile.turn = ARROW_TURN_RATE
set missile.target = dex.target
//
// In this phase the target died. Let the missile run out.
elseif not UnitAlive(dex.target) then
set dex.count = -1
set dex.target = null
set missile.turn = 0.
set missile.target = null
set missile.collision = 0.
if (dex.phase == 1) then
set dex.phase = 0
call missile.origin.move(missile.x, missile.y, missile.z)
call missile.impact.move(missile.x + Cos(missile.angle)*ARROW_FLY_RUN_OUT_DISTANCE, missile.y + Sin(missile.angle)*ARROW_FLY_RUN_OUT_DISTANCE, missile.z)
endif
endif
return false
endmethod
implement MissileStruct
endstruct
private function OnEffect takes nothing returns nothing
local unit source = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(source, GUIDED_ARROW_ABILITY)
local real x = GetUnitX(source)
local real y = GetUnitY(source)
local real angle = Atan2(GetSpellTargetY() - y, GetSpellTargetX() - x)
local SpellIndex dex = SpellIndex.create()
// Create a new missile and assign its members.
local Missile missile = Missile.create(x, y, ARROW_FLY_HEIGHT, angle, GetFlyDistance(level), ARROW_FLY_HEIGHT)
// Allows configuration.
call CustomizeMissile(missile, level)
// Override user settings. These missile members are reserved.
set missile.source = source
set missile.owner = GetTriggerPlayer()
set missile.data = dex
set missile.target = null
call GuidedArrow.launch(missile)
// Assign data to the spell index.
set dex.count = GetPiercingCount(level)
set dex.time = ARROW_ACTION_DELAY
set dex.target = GetSpellTargetUnit()
if not FilterUnits(dex.target, missile.owner) then
set dex.target = null
endif
set dex.phase = 1
// Run effects.
if (dex.target != null) then
set dex.fx = AddSpecialEffectTarget(ON_TARGET_FX, dex.target, FX_ATTACH_POINT)
endif
set source = null
endfunction
private function Init takes nothing returns nothing
set GuidedArrow.filter = Filter(function ConsiderUnitsFiltered)
call RegisterSpellEffectEvent(GUIDED_ARROW_ABILITY, function OnEffect)
endfunction
endlibrary
//TESH.scrollpos=59
//TESH.alwaysfold=0
scope map
globals
private real X = -1500
private real Y = -2280
private real X0 = 1177
private real Y0 = 1370
endglobals
private struct preload extends array
static method sfx takes nothing returns nothing
call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl", 2300,-2900))
call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\NightElf\\Immolation\\ImmolationDamage.mdl", 2300,-2900))
call DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl", 2300,-2900))
call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\Volcano\\VolcanoDeath.mdl", 2300,-2900))
call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Undead\\FrostArmor\\FrostArmorTarget.mdl", 2300,-2900))
call DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl", 2300,-2900))
call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl", 2300,-2900))
call DestroyEffect(AddSpecialEffect("Doodads\\BlackCitadel\\Props\\RuneArt\\RuneArt1.mdl", 2300,-2900))
call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Undead\\Possession\\PossessionMissile.mdl", 2300,-2900))
endmethod
endstruct
private struct xxx extends array
private static method spawn takes nothing returns nothing
local integer i = 0
local unit u
loop
exitwhen i == 4
set u = CreateUnit(Player(1), 'ugho', X, Y, 0)
call IssuePointOrder(u, "attack", X0, Y0)
set u = CreateUnit(Player(2), 'ugho', X0, Y0, 0)
call IssuePointOrder(u, "attack", X, Y)
set i = i + 1
endloop
set u = null
endmethod
private static method revive takes nothing returns boolean
local player p = GetTriggerPlayer()
local unit u
if p == Player(1) then
set u = CreateUnit(p, 'ugho', X, Y, 0)
call IssuePointOrder(u, "attack", X0, Y0)
elseif p == Player(2) then
set u = CreateUnit(p, 'ugho', X0, Y0, 0)
call IssuePointOrder(u, "attack", X, Y)
elseif IsUnitType(GetTriggerUnit(), UNIT_TYPE_HERO) then
call ReviveHero(GetTriggerUnit(), 0,0, true)
endif
set u = null
set p = null
return false
endmethod
private static method reset takes nothing returns boolean
call ClearTextMessages()
call GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, Player(0), null)
loop
set bj_lastCreatedUnit = FirstOfGroup(bj_lastCreatedGroup)
exitwhen bj_lastCreatedUnit == null
call GroupRemoveUnit(bj_lastCreatedGroup, bj_lastCreatedUnit)
call UnitResetCooldown(bj_lastCreatedUnit)
call SetUnitState(bj_lastCreatedUnit, UNIT_STATE_MANA, GetUnitState(bj_lastCreatedUnit, UNIT_STATE_MAX_MANA))
endloop
return false
endmethod
private static method NewHero takes integer unitId returns nothing
local unit u = CreateUnit(Player(0), unitId, 0, 0, 0)
call SetHeroLevel(u, 12, true)
set u = null
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.revive)
call thistype.spawn()
call NewHero('Hblm')
call NewHero('Uktl')
call NewHero('Obla')
call NewHero('Hmkg')
call NewHero('Hvwd')
call FogModifierStart(CreateFogModifierRect(Player(0), FOG_OF_WAR_VISIBLE, WorldBounds.world, false, false))
call TriggerRegisterPlayerEventEndCinematic(t, Player(0))
call TriggerAddCondition(t, Condition(function thistype.reset))
call BJDebugMsg("Press |cffdaa520ESC|r to reset cooldowns and refresh mana")
set t = null
call preload.sfx()
endmethod
endstruct
endscope