14 years
edited 8 years
I feel the documentation in the library explains it well enough, so there's not much I can add here.
It requires Table, by the way.
It requires Table, by the way.
1 library SpellEvent initializer Init requires Table
2 3 //*****************************************************************4 //* SPELL EVENT LIBRARY 1.35 //*6 //* written by: Anitarf7 //* requires: -Table8 //*9 //* Maps with many triggered spells require many triggers that run10 //* on spell events. Whenever a spell is cast, all those triggers11 //* need to be evaluated by the game even though only one actually12 //* needs to run. This library has been written to reduce the13 //* number of triggers in such maps; instead of having a trigger14 //* per spell, this library contains a single trigger which then15 //* runs only the code associated with the spell that's actually16 //* being cast.17 //*18 //* Perhaps more significant than the marginal speed gain is the19 //* feature that allows you to access all the spell event20 //* responses from all spell events, something that the native21 //* functions senselessly do not support. With this system you can22 //* for example easily get the target unit of the spell on the23 //* casting finish event.24 //*25 //* All functions following the Response function interface that26 //* is defined at the start of this library can be used to respond27 //* to spell events. You can register a response with one of the28 //* following functions, each for a different spell event:29 //*30 //* function RegisterSpellChannelResponse takes integer spellId, Response r returns nothing31 //* function RegisterSpellCastResponse takes integer spellId, Response r returns nothing32 //* function RegisterSpellEffectResponse takes integer spellId, Response r returns nothing33 //* function RegisterSpellFinishResponse takes integer spellId, Response r returns nothing34 //* function RegisterSpellEndCastResponse takes integer spellId, Response r returns nothing35 //*36 //* The first event occurs at the very start of the spell, when37 //* the spell's casting time begins; most spells have 0 casting38 //* time, so in most cases this first event occurs at the same39 //* time as the second one, which runs when the unit actually40 //* begins casting a spell by starting its spell animation. The41 //* third event occurs when the spell effect actually takes place,42 //* which happens sometime into the unit's spell animation43 //* depending on the unit's "Animation - Cast Point" property.44 //* The fourth event runs if the unit finishes casting the spell45 //* uninterrupted, which might be important for channeling spells.46 //* The last event runs when the unit stops casting the spell,47 //* regardless of whether it finished casting or was interrupted.48 //*49 //* If you specify a spell id when registering a response then50 //* that response will only run when that ability is cast; only51 //* one function per ability per event is supported, if you52 //* register more responses then only the last one registered will53 //* be called. If, however, you pass 0 as the ability id parameter54 //* then the registered function will run for all spells. Up to55 //* 8190 functions can be registered this way for each event.56 //* These functions will be called before the ability's specific57 //* function in the order they were registered.58 //*59 //* This library provides its own event responses that work60 //* better than the Blizzard's bugged native cast event responses.61 //* They still won't work after a wait, but unlike Blizzard's62 //* natives they will work on all spell events.63 //*64 //* Here are usage examples for all event responses:65 //*66 //* local integer a = SpellEvent.AbilityId67 //* local unit u = SpellEvent.CastingUnit68 //* local unit t = SpellEvent.TargetUnit69 //* local item i = SpellEvent.TargetItem70 //* local destructable d = SpellEvent.TargetDestructable71 //* local location l = SpellEvent.TargetLoc72 //* local real x = SpellEvent.TargetX73 //* local real y = SpellEvent.TargetY74 //* local boolean b = SpellEvent.CastFinished75 //*76 //* SpellEvent.TargetLoc is provided for odd people who insist on77 //* using locations, note that if you use it you have to cleanup78 //* the returned location yourself.79 //*80 //* SpellEvent.CastFinished boolean is intended only for the81 //* EndCast event as it tells you whether the spell finished or82 //* was interrupted.83 //*84 //*85 //* Note that a few spells such as Berserk and Wind Walk behave86 //* somewhat differently from regular spells: they are cast87 //* instantly without regard for cast animation times, they do not88 //* interrupt the unit's current order, as well as any spell it89 //* may be casting. SpellEvent 1.1 now handles such spells without90 //* errors provided they are truly instant (without casting time).91 //*92 //* It also turned out that a few rare abilities like Charge Gold93 //* & Lumber trigger a spell effect event, but not any other.94 //* SpellEvent 1.2 no longer ignores these lone effect events.95 //*96 //* It also turned out that removing the spell ability would run97 //* the endcast event, so if the ability was removed from one of98 //* the effect callbacks the spell event data would get cleaned99 //* up prematurely, SpellEvent 1.3 fixes this issue.100 //*****************************************************************101 102 // use the RegisterSpell*Response functions to add spell event responses to the library103 public function interface Response takes nothing returns nothing
104 105 // ================================================================106 107 private keyword effectDone
108 private keyword init
109 private keyword get
110 private keyword destroy
111 112 private struct spellEvent
113 private static HandleTable casterTable
114 boolean effectDone=false
115 116 integer AbilityId
117 unit CastingUnit
118 unit TargetUnit
119 item TargetItem=null
120 destructable TargetDestructable=null
121 real TargetX=0.0
122 real TargetY=0.0
123 boolean CastFinished=false
124 125 readonly boolean destroyWhenDone=false
126
127 // Some abilities like Berserk can be cast instantly without interrupting128 // the caster's current order, which includes any spells the caster may129 // already be casting. The following member allows the system to recover130 // the original spellEvent when such an instant spell overwrites it.131 private spellEvent interrupt132 133 method operator TargetLoc takes nothing returns location
134 return Location(.TargetX, .TargetY)
135 endmethod136
137 private static method create takes nothing returns spellEvent
138 return spellEvent.allocate()
139 endmethod140 static method init takes nothing returns spellEvent
141 local spellEvent s=spellEvent.allocate()
142 set s.AbilityId = GetSpellAbilityId()
143 set s.CastingUnit = GetTriggerUnit()
144 set s.TargetUnit = GetSpellTargetUnit()
145 if s.TargetUnit != null then
146 set s.TargetX = GetUnitX(s.TargetUnit)
147 set s.TargetY = GetUnitY(s.TargetUnit)
148 else149 set s.TargetDestructable = GetSpellTargetDestructable()
150 if s.TargetDestructable != null then
151 set s.TargetX = GetDestructableX(s.TargetDestructable)
152 set s.TargetY = GetDestructableY(s.TargetDestructable)
153 else154 set s.TargetItem = GetSpellTargetItem()
155 if s.TargetItem != null then
156 set s.TargetX = GetItemX(s.TargetItem)
157 set s.TargetY = GetItemY(s.TargetItem)
158 else159 set s.TargetX = GetSpellTargetX()
160 set s.TargetY = GetSpellTargetY()
161 endif162 endif163 endif164 set s.interrupt = spellEvent.casterTable[s.CastingUnit]
165 set spellEvent.casterTable[s.CastingUnit]=integer(s)
166 return s167 endmethod168 method destroy takes nothing returns nothing
169 if SpellEvent!=0 then
170 // the library is in the middle of running callbacks, this can happen if171 // the spell ability gets removed from the unit in one of the callbacks172 set .destroyWhenDone=true
173 return174 endif175 if .interrupt!=0 then
176 // this spell interrupted another spell, some instant spells can do this177 set spellEvent.casterTable[.CastingUnit]=.interrupt
178 else179 call spellEvent.casterTable.flush(.CastingUnit)
180 endif181 set .CastingUnit=null
182 call .deallocate()
183 endmethod184 185 static method get takes unit caster returns spellEvent
186 return spellEvent(spellEvent.casterTable[caster])
187 endmethod188 static method onInit takes nothing returns nothing
189 set .casterTable=HandleTable.create()
190 endmethod191 endstruct192
193 globals194 spellEvent SpellEvent=0
195 endglobals196
197 // ================================================================198 199 //! textmacro spellEvent_make takes name200 globals201 private Response array $name$CallList
202 private integer $name$CallCount=0
203 private Table $name$Table204 endglobals205 206 private function $name$Calls takes integer id returns nothing
207 local integer i=0
208 local spellEvent previous=SpellEvent
209 set SpellEvent=spellEvent.get(GetTriggerUnit())
210 loop211 exitwhen i>=$name$CallCount
212 call $name$CallList[i].evaluate()
213 set i=i+1
214 endloop215 if $name$Table.exists(id) then
216 call Response($name$Table[id]).evaluate()
217 endif218 if SpellEvent.destroyWhenDone then
219 set SpellEvent=0
220 call spellEvent.get(GetTriggerUnit()).destroy()
221 endif222 set SpellEvent=previous
223 endfunction224 225 function RegisterSpell$name$Response takes integer spellId, Response r returns nothing
226 if spellId==0 then
227 set $name$CallList[$name$CallCount]=r
228 set $name$CallCount=$name$CallCount+1
229 else230 set $name$Table[spellId]=integer(r)
231 endif232 endfunction233 //! endtextmacro234 235 //! runtextmacro spellEvent_make("Channel")236 //! runtextmacro spellEvent_make("Cast")237 //! runtextmacro spellEvent_make("Effect")238 //! runtextmacro spellEvent_make("Finish")239 //! runtextmacro spellEvent_make("EndCast")240 241 // ================================================================242 243 globals244 // Morph abilities like Metamorphosis will cause an additional spell effect245 // event to run when the caster morphs back to its original form. To avoid246 // such duplicates, SpellEvent is designed to ignore any effect event that247 // does not have a matching channel event preceding it.248 // However, there are also rare abilities, like Charge Gold&Lumber, which249 // only cause an effect event to run, so these events must not be ignored250 // even though they occur without a matching channel event. This Table251 // tracks ability IDs of spells that did cause a channel event so that when252 // a spell is cast that doesn't cause one, its effect event is not ignored.253 private Table CastAfterChannel254 endglobals255 256 private function Channel takes nothing returns nothing
257 call spellEvent.init()
258 call ChannelCalls(GetSpellAbilityId())
259 endfunction260 261 private function Cast takes nothing returns nothing
262 call CastCalls(GetSpellAbilityId())
263 endfunction264 265 private function Effect takes nothing returns nothing
266 local spellEvent s=spellEvent.get(GetTriggerUnit())
267 local integer id=GetSpellAbilityId()
268 if s!=0 and not s.effectDone then
269 set s.effectDone=true
270 call EffectCalls(id)
271 if not CastAfterChannel.exists(id) then
272 set CastAfterChannel[id]=1
273 endif274 elseif not CastAfterChannel.exists(id) then
275 set s = spellEvent.init()
276 call EffectCalls(id)
277 call s.destroy()
278 endif279 endfunction280 281 private function Finish takes nothing returns nothing
282 set spellEvent.get(GetTriggerUnit()).CastFinished=true
283 call FinishCalls(GetSpellAbilityId())
284 endfunction285 286 private function EndCast takes nothing returns nothing
287 call EndCastCalls(GetSpellAbilityId())
288 call spellEvent.get(GetTriggerUnit()).destroy()
289 endfunction290 291 // ================================================================292 293 private function InitTrigger takes playerunitevent e, code c returns nothing
294 local trigger t=CreateTrigger()
295 call TriggerRegisterAnyUnitEventBJ( t, e )
296 call TriggerAddAction(t, c)
297 set t=null
298 endfunction299 private function Init takes nothing returns nothing
300 set ChannelTable=Table.create()
301 set CastTable=Table.create()
302 set EffectTable=Table.create()
303 set FinishTable=Table.create()
304 set EndCastTable=Table.create()
305 call InitTrigger(EVENT_PLAYER_UNIT_SPELL_CHANNEL, function Channel)
306 call InitTrigger(EVENT_PLAYER_UNIT_SPELL_CAST, function Cast)
307 call InitTrigger(EVENT_PLAYER_UNIT_SPELL_EFFECT, function Effect)
308 call InitTrigger(EVENT_PLAYER_UNIT_SPELL_FINISH, function Finish)
309 call InitTrigger(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function EndCast)
310 set CastAfterChannel=Table.create()
311 endfunction312 313 endlibrary
Usage example
1 scope QuenchLife initializer Init
2 // A single target channeling spell that kills the target unit if the channeling is finished uninterrupted.3 4 globals5 private constant integer ABILITY_ID = 'A000'
6 private constant string DEATH_EFFECT = "WriteTheEffectModelPathHere"
7 private constant string EFFECT_ATTACHMENT = "chest"
8 endglobals9 10 private function OnCast takes nothing returns nothing
11 local unit t=SpellEvent.TargetUnit //you couldn't get the target unit on this event without this script
12 call AddSpecialEffectTarget(DEATH_EFFECT, t, EFFECT_ATTACHMENT) //even silly example spells need some eyecandy
13 call KillUnit(t) //the unit won't give any bounty because I'm too lazy to use the damage natives, this is just an example anyway
14 set t=null
15 endfunction16 17 private function Init takes nothing returns nothing
18 call RegisterSpellFinishResponse(ABILITY_ID, OnCast)
19 endfunction20 endscope
Yes, if all your spells are made with triggers and use this library then you can even do this
1 library SpellChaos initializer Init
2 globals3 private constant real CHANCE_FOR_SPELLS_TO_MISFIRE = 0.1
4 private constant real CHANCE_FOR_SPELLS_TO_FIZZLE = 0.1
5 endglobals6 7 private function OnChannel takes nothing returns nothing
8 local unit t = SpellEvent.TargetUnit
9 if GetRandomReal(0.0,1.0) < CHANCE_FOR_SPELLS_TO_FIZZLE then
10 set SpellEvent.AbilityId = 0 //we can do this, or we can even transmute the spell into another spell. Crazy, isn't it?
11 //of course, doing this at any time other than at the very start of the spell could cause bugs,12 //like a spell not finishing correctly because only half of it's code would run, for example.13 elseif t != null and GetRandomReal(0.0,1.0) < CHANCE_FOR_SPELLS_TO_MISFIRE then
14 set SpellEvent.TargetUnit = SpellEvent.CastingUnit //redirect any spell, how awesome is that?
15 endif16 set t = null
17 endfunction18 19 private function Init takes nothing returns nothing
20 call RegisterSpellChannelResponse(0, OnChannel) //again, changing spell response values should only be done on the first spell event
21 //before any spell-specific code runs, else you could run in trouble.22 endfunction23 endscope