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.

JASS:

librarySpellEventinitializerInitrequiresTable//*****************************************************************//* SPELL EVENT LIBRARY 1.3//*//* written by: Anitarf//* requires: -Table//*//* Maps with many triggered spells require many triggers that run//* on spell events. Whenever a spell is cast, all those triggers//* need to be evaluated by the game even though only one actually//* needs to run. This library has been written to reduce the//* number of triggers in such maps; instead of having a trigger//* per spell, this library contains a single trigger which then//* runs only the code associated with the spell that's actually//* being cast.//*//* Perhaps more significant than the marginal speed gain is the//* feature that allows you to access all the spell event//* responses from all spell events, something that the native//* functions senselessly do not support. With this system you can//* for example easily get the target unit of the spell on the//* casting finish event.//*//* All functions following the Response function interface that//* is defined at the start of this library can be used to respond//* to spell events. You can register a response with one of the//* following functions, each for a different spell event://*//* function RegisterSpellChannelResponse takes integer spellId, Response r returns nothing//* function RegisterSpellCastResponse takes integer spellId, Response r returns nothing//* function RegisterSpellEffectResponse takes integer spellId, Response r returns nothing//* function RegisterSpellFinishResponse takes integer spellId, Response r returns nothing//* function RegisterSpellEndCastResponse takes integer spellId, Response r returns nothing//*//* The first event occurs at the very start of the spell, when//* the spell's casting time begins; most spells have 0 casting//* time, so in most cases this first event occurs at the same//* time as the second one, which runs when the unit actually//* begins casting a spell by starting its spell animation. The//* third event occurs when the spell effect actually takes place,//* which happens sometime into the unit's spell animation//* depending on the unit's "Animation - Cast Point" property.//* The fourth event runs if the unit finishes casting the spell//* uninterrupted, which might be important for channeling spells.//* The last event runs when the unit stops casting the spell,//* regardless of whether it finished casting or was interrupted.//*//* If you specify a spell id when registering a response then//* that response will only run when that ability is cast; only//* one function per ability per event is supported, if you//* register more responses then only the last one registered will//* be called. If, however, you pass 0 as the ability id parameter//* then the registered function will run for all spells. Up to//* 8190 functions can be registered this way for each event.//* These functions will be called before the ability's specific//* function in the order they were registered.//*//* This library provides its own event responses that work//* better than the Blizzard's bugged native cast event responses.//* They still won't work after a wait, but unlike Blizzard's//* natives they will work on all spell events.//*//* Here are usage examples for all event responses://*//* local integer a = SpellEvent.AbilityId//* local unit u = SpellEvent.CastingUnit//* local unit t = SpellEvent.TargetUnit//* local item i = SpellEvent.TargetItem//* local destructable d = SpellEvent.TargetDestructable//* local location l = SpellEvent.TargetLoc//* local real x = SpellEvent.TargetX//* local real y = SpellEvent.TargetY//* local boolean b = SpellEvent.CastFinished//*//* SpellEvent.TargetLoc is provided for odd people who insist on//* using locations, note that if you use it you have to cleanup//* the returned location yourself.//*//* SpellEvent.CastFinished boolean is intended only for the//* EndCast event as it tells you whether the spell finished or//* was interrupted.//*//*//* Note that a few spells such as Berserk and Wind Walk behave//* somewhat differently from regular spells: they are cast//* instantly without regard for cast animation times, they do not//* interrupt the unit's current order, as well as any spell it//* may be casting. SpellEvent 1.1 now handles such spells without//* errors provided they are truly instant (without casting time).//*//* It also turned out that a few rare abilities like Charge Gold//* & Lumber trigger a spell effect event, but not any other.//* SpellEvent 1.2 no longer ignores these lone effect events.//*//* It also turned out that removing the spell ability would run//* the endcast event, so if the ability was removed from one of//* the effect callbacks the spell event data would get cleaned//* up prematurely, SpellEvent 1.3 fixes this issue.//*****************************************************************// use the RegisterSpell*Response functions to add spell event responses to the librarypublicfunctioninterfaceResponsetakesnothingreturnsnothing// ================================================================privatekeywordeffectDoneprivatekeywordinitprivatekeywordgetprivatekeyworddestroyprivatestructspellEventprivatestaticHandleTablecasterTablebooleaneffectDone=falseintegerAbilityIdunitCastingUnitunitTargetUnititemTargetItem=nulldestructableTargetDestructable=nullrealTargetX=0.0realTargetY=0.0booleanCastFinished=falsereadonlybooleandestroyWhenDone=false// Some abilities like Berserk can be cast instantly without interrupting// the caster's current order, which includes any spells the caster may// already be casting. The following member allows the system to recover// the original spellEvent when such an instant spell overwrites it.privatespellEventinterruptmethodoperatorTargetLoctakesnothingreturnslocationreturnLocation(.TargetX, .TargetY)
endmethodprivatestaticmethodcreatetakesnothingreturnsspellEventreturnspellEvent.allocate()
endmethodstaticmethodinittakesnothingreturnsspellEventlocalspellEvents=spellEvent.allocate()
sets.AbilityId = GetSpellAbilityId()
sets.CastingUnit = GetTriggerUnit()
sets.TargetUnit = GetSpellTargetUnit()
ifs.TargetUnit != nullthensets.TargetX = GetUnitX(s.TargetUnit)
sets.TargetY = GetUnitY(s.TargetUnit)
elsesets.TargetDestructable = GetSpellTargetDestructable()
ifs.TargetDestructable != nullthensets.TargetX = GetDestructableX(s.TargetDestructable)
sets.TargetY = GetDestructableY(s.TargetDestructable)
elsesets.TargetItem = GetSpellTargetItem()
ifs.TargetItem != nullthensets.TargetX = GetItemX(s.TargetItem)
sets.TargetY = GetItemY(s.TargetItem)
elsesets.TargetX = GetSpellTargetX()
sets.TargetY = GetSpellTargetY()
endifendifendifsets.interrupt = spellEvent.casterTable[s.CastingUnit]
setspellEvent.casterTable[s.CastingUnit]=integer(s)
returnsendmethodmethoddestroytakesnothingreturnsnothingifSpellEvent!=0then// the library is in the middle of running callbacks, this can happen if// the spell ability gets removed from the unit in one of the callbacksset.destroyWhenDone=truereturnendifif.interrupt!=0then// this spell interrupted another spell, some instant spells can do thissetspellEvent.casterTable[.CastingUnit]=.interruptelsecallspellEvent.casterTable.flush(.CastingUnit)
endifset.CastingUnit=nullcall.deallocate()
endmethodstaticmethodgettakesunitcasterreturnsspellEventreturnspellEvent(spellEvent.casterTable[caster])
endmethodstaticmethodonInittakesnothingreturnsnothingset.casterTable=HandleTable.create()
endmethodendstructglobalsspellEventSpellEvent=0endglobals// ================================================================//! textmacro spellEvent_make takes nameglobalsprivateResponsearray$name$CallListprivateinteger$name$CallCount=0privateTable$name$Tableendglobalsprivatefunction$name$Callstakesintegeridreturnsnothinglocalintegeri=0localspellEventprevious=SpellEventsetSpellEvent=spellEvent.get(GetTriggerUnit())
loopexitwheni>=$name$CallCountcall$name$CallList[i].evaluate()
seti=i+1endloopif$name$Table.exists(id) thencallResponse($name$Table[id]).evaluate()
endififSpellEvent.destroyWhenDonethensetSpellEvent=0callspellEvent.get(GetTriggerUnit()).destroy()
endifsetSpellEvent=previousendfunctionfunctionRegisterSpell$name$ResponsetakesintegerspellId, ResponserreturnsnothingifspellId==0thenset$name$CallList[$name$CallCount]=rset$name$CallCount=$name$CallCount+1elseset$name$Table[spellId]=integer(r)
endifendfunction//! endtextmacro//! runtextmacro spellEvent_make("Channel")//! runtextmacro spellEvent_make("Cast")//! runtextmacro spellEvent_make("Effect")//! runtextmacro spellEvent_make("Finish")//! runtextmacro spellEvent_make("EndCast")// ================================================================globals// Morph abilities like Metamorphosis will cause an additional spell effect// event to run when the caster morphs back to its original form. To avoid// such duplicates, SpellEvent is designed to ignore any effect event that// does not have a matching channel event preceding it.// However, there are also rare abilities, like Charge Gold&Lumber, which// only cause an effect event to run, so these events must not be ignored// even though they occur without a matching channel event. This Table// tracks ability IDs of spells that did cause a channel event so that when// a spell is cast that doesn't cause one, its effect event is not ignored.privateTableCastAfterChannelendglobalsprivatefunctionChanneltakesnothingreturnsnothingcallspellEvent.init()
callChannelCalls(GetSpellAbilityId())
endfunctionprivatefunctionCasttakesnothingreturnsnothingcallCastCalls(GetSpellAbilityId())
endfunctionprivatefunctionEffecttakesnothingreturnsnothinglocalspellEvents=spellEvent.get(GetTriggerUnit())
localintegerid=GetSpellAbilityId()
ifs!=0andnots.effectDonethensets.effectDone=truecallEffectCalls(id)
ifnotCastAfterChannel.exists(id) thensetCastAfterChannel[id]=1endifelseifnotCastAfterChannel.exists(id) thensets = spellEvent.init()
callEffectCalls(id)
calls.destroy()
endifendfunctionprivatefunctionFinishtakesnothingreturnsnothingsetspellEvent.get(GetTriggerUnit()).CastFinished=truecallFinishCalls(GetSpellAbilityId())
endfunctionprivatefunctionEndCasttakesnothingreturnsnothingcallEndCastCalls(GetSpellAbilityId())
callspellEvent.get(GetTriggerUnit()).destroy()
endfunction// ================================================================privatefunctionInitTriggertakesplayerunitevente, codecreturnsnothinglocaltriggert=CreateTrigger()
callTriggerRegisterAnyUnitEventBJ( t, e )
callTriggerAddAction(t, c)
sett=nullendfunctionprivatefunctionInittakesnothingreturnsnothingsetChannelTable=Table.create()
setCastTable=Table.create()
setEffectTable=Table.create()
setFinishTable=Table.create()
setEndCastTable=Table.create()
callInitTrigger(EVENT_PLAYER_UNIT_SPELL_CHANNEL, functionChannel)
callInitTrigger(EVENT_PLAYER_UNIT_SPELL_CAST, functionCast)
callInitTrigger(EVENT_PLAYER_UNIT_SPELL_EFFECT, functionEffect)
callInitTrigger(EVENT_PLAYER_UNIT_SPELL_FINISH, functionFinish)
callInitTrigger(EVENT_PLAYER_UNIT_SPELL_ENDCAST, functionEndCast)
setCastAfterChannel=Table.create()
endfunctionendlibrary

Usage example:

scopeQuenchLifeinitializerInit// A single target channeling spell that kills the target unit if the channeling is finished uninterrupted.globalsprivateconstantintegerABILITY_ID = 'A000'privateconstantstringDEATH_EFFECT = "WriteTheEffectModelPathHere"privateconstantstringEFFECT_ATTACHMENT = "chest"endglobalsprivatefunctionOnCasttakesnothingreturnsnothinglocalunitt=SpellEvent.TargetUnit//you couldn't get the target unit on this event without this scriptcallAddSpecialEffectTarget(DEATH_EFFECT, t, EFFECT_ATTACHMENT) //even silly example spells need some eyecandycallKillUnit(t) //the unit won't give any bounty because I'm too lazy to use the damage natives, this is just an example anywaysett=nullendfunctionprivatefunctionInittakesnothingreturnsnothingcallRegisterSpellFinishResponse(ABILITY_ID, OnCast)
endfunctionendscope

Yes, if all your spells are made with triggers and use this library then you can even do this:

librarySpellChaosinitializerInitglobalsprivateconstantrealCHANCE_FOR_SPELLS_TO_MISFIRE = 0.1privateconstantrealCHANCE_FOR_SPELLS_TO_FIZZLE = 0.1endglobalsprivatefunctionOnChanneltakesnothingreturnsnothinglocalunitt = SpellEvent.TargetUnitifGetRandomReal(0.0,1.0) < CHANCE_FOR_SPELLS_TO_FIZZLEthensetSpellEvent.AbilityId = 0//we can do this, or we can even transmute the spell into another spell. Crazy, isn't it?//of course, doing this at any time other than at the very start of the spell could cause bugs,//like a spell not finishing correctly because only half of it's code would run, for example.elseift != nullandGetRandomReal(0.0,1.0) < CHANCE_FOR_SPELLS_TO_MISFIREthensetSpellEvent.TargetUnit = SpellEvent.CastingUnit//redirect any spell, how awesome is that?endifsett = nullendfunctionprivatefunctionInittakesnothingreturnsnothingcallRegisterSpellChannelResponse(0, OnChannel) //again, changing spell response values should only be done on the first spell event//before any spell-specific code runs, else you could run in trouble.endfunctionendscope

I agree, getting all cast event responses to work for Finish and EndCast events is the main advantage here, not the speed. Regarding that, this system will definitely be faster at a certain number of spells, but it might take a lot of spells and even then the advantage will be marginal. I guess my point regarding speed is that it won't be slower (except in maps with too few spells to matter) than an individual trigger per spell, so the features the script brings don't cost you anything in terms of map performance.

I really dislike the naming of "SpellEvent_GetTargetUnit()" and stuff, but I'm awful glad it flows so well with standard WC3 naming for functions. I think it might be better if instead of RegisterFinishResponse(ABILITY_ID, functionOnCast), it had something like RegisterSpellResponse(ABILITY_ID, RESPONSE_TYPE_FINISHED, functionOnCast). I realize it's a bit more typing, but I think it's more appropriate to change the integer in there than to change the function call you're using.

Outside of that, this is awesome. Approved.

EDIT:
You mispelled the keyword 'library' in the first post. Please fix that. (I could do it for you, but I would prefer you to)

I think it might be better if instead of RegisterFinishResponse(ABILITY_ID, functionOnCast), it had something like RegisterSpellResponse(ABILITY_ID, RESPONSE_TYPE_FINISHED, functionOnCast).

Don't approve the resource, then, since once it's approved I need to maintain backwards compatibility and there's no way I can ever change that, and I'm perfectly willing to do that if enough people think that makes for a better user interface, after all it would be a pretty lame public resource if people didn't use it because they didn't like the API. So, I'm moving this back to submissions to get more opinions.

I wondered when someone would submit something like this. It definitely has its uses, but I don't think you should make "marginal speed gain" a selling point (and considering the presence of cache and .evaluate, I'm actually skeptical of that claim in the first place, not that it matters either way in this kind of system).

As multiple triggers mean at least multiple Evaluate calls, I wouldn't be so skeptical.

I don't like the blizz event response-like madness or that it has to save all event responses everytime, even though most of them are not needed or are empty.

If the syntax for using event responses will be the ugly blizz-like one, I'd prefer to just use blizz' event responses rather than have to use those new hard to remember event responses that also consume extra time during events, I mean really, at least it would make it easier to port old spells to this.

Not a fan of making it SpellEvent_RegisterEventResponse instead of just RegisterEventResponse, it is a little large, and public stuff are already non-scoped, but oh well.

Don't approve the resource, then, since once it's approved I need to maintain backwards compatibility and there's no way I can ever change that, and I'm perfectly willing to do that if enough people think that makes for a better user interface, after all it would be a pretty lame public resource if people didn't use it because they didn't like the API. So, I'm moving this back to submissions to get more opinions.

You wouldn't lose backwards compatibility by adding a function call with different parameters, though. There'd be nothing to remove. I guess if you removed the public prefix for those functions, then it might be different.

You also still haven't fixed the spelling of library in the beginning of the script.

I don't like the blizz event response-like madness or that it has to save all event responses everytime, even though most of them are not needed or are empty.

Well, unless I require users to specify for every single spell what event responses it is expected to use (which I don't think is very practical), there's no way to avoid this. I could also make global switches for which event responses are used but in that case, as soon as one spell requires the targeted destructable, the system would have to call that event response on all spell events. In either of these two cases, you'd need a bunch of if statements checking whether an event response should be used, so it wouldn't be considerably faster than calling all event response functions indiscriminately every time.

There's also the option of only storing the event responses that don't work on the Finish and EndCast events and for the rest letting the user call the natives as needed, but the problem is that the only event responses that work on those events are GetSpellAbilityId (which the script needs to call anyway) and GetTriggerUnit (which pretty much every spell needs to call), so it's both faster to store those too as well as it standardizes all event responses to the same new format.

As for this format, I think wrapper functions which resemble the original event responses and get inlined anyway are better than direct struct access; I could do something with methods/method operators, where the user functions would take the struct as a parameter and then users would call them, something to the effect of setu = GetSpell.TargetUnit()//optional@ , but that doesn't look particularly different from wrapper functions to me.

Quote:

If the syntax for using event responses will be the ugly blizz-like one, I'd prefer to just use blizz' event responses rather than have to use those new hard to remember event responses that also consume extra time during events, I mean really, at least it would make it easier to port old spells to this.

Well, first of all, do event responses still work when you call a function via .evaluate/.execute? Second, how would reading a global array consume more time than calling an event response? Third, in neither of the previous two points is an issue, what's stopping you from using the original natives anyway?

Well, in any case, I'm open for syntax suggestions, that's why this is here.

Quote:

Not a fan of making it SpellEvent_RegisterEventResponse instead of just RegisterEventResponse, it is a little large, and public stuff are already non-scoped, but oh well.

Well, if the function names are specific enough to be unlikely to cause conflicts then the public keyword isn't needed, but on the other hand if you decide to use the public keyword you can shorten the function name. SpellEvent_RegisterEventResponse can easily be shortened to SpellEvent_RegisterResponse, while RegisterEventResponse alone is kinda too general and would need to be lengthened to RegisterSpellEventResponse anyway, which in the end brings us to about the same length. Now the only question is whether the underscore is too much of an aesthetic problem.

Quote:

Originally Posted by Rising_Dusk

You wouldn't lose backwards compatibility by adding a function call with different parameters, though. There'd be nothing to remove. I guess if you removed the public prefix for those functions, then it might be different.

I'm not really a fan of having two sets of functions that do the same thing. If there were some functional difference that'd be one thing but to have two functions that only differ in style is too much.

Quote:

You also still haven't fixed the spelling of library in the beginning of the script.

That's still blizz like stuff. I would wish this used arguments, you know, if you specified what responses you'd like, then arguments would be possible, maybe too hard.

Quote:

Well, first of all, do event responses still work when you call a function via .evaluate/.execute?

All but GetTriggeringTrigger.

Quote:

Second, how would reading a global array consume more time than calling an event response?

I don't care about speed, it is more about having to remember brand new event response names.

Quote:

Well, unless I require users to specify for every single spell what event responses it is expected to use (which I don't think is very practical)

I actually think it is practical, could use bitflags, well bitflags could be a little slow.

What annoys me the most is thelocation call, the others are in theory not that bad. Also, why store it as location? It could at least be helpful if it converted it to x,y already. It would be nice if you could tell it not to call the location thing.

Quote:

local spellEvent s=spellEvent.allocate()
set s.spellId=GetSpellAbilityId()
set s.caster=GetTriggerUnit()
set s.target=GetSpellTargetUnit()
set s.targetItem=GetSpellTargetItem()
set s.targetDest=GetSpellTargetDestructable()
set s.targetLoc=GetSpellTargetLoc()
set s.finished=false

Well, most of the times, you don't need any of them and could use the responses directly (SPELL_EFFECT/CAST) , the other times, you don't need all of them.

Guess spellId and caster are constants, but there are only 4 other things, it is not that bad:

That's still blizz like stuff. I would wish this used arguments, you know, if you specified what responses you'd like, then arguments would be possible, maybe too hard.

I'm not sure I understand. You mean the functions following the function interface would take the event responses as arguments? That'd be either a very long argument list or you'd need different functions for different types of spells (and a point-or-unit target spell like shockwave would still have a long argument list). I thought you hated long argument lists.

Quote:

I don't care about speed, it is more about having to remember brand new event response names.

Then how's it being Blizz like a problem? That last example was a Blizzard native with a "." in the middle. It was essentially the same thing, you can even use auto-completion and then add the dot after GetSpell (well, except for the GetSpell.TargetX/Y which wouldn't have a native counterpart). It's as intuitive as it can get.

Quote:

I actually think it is practical, could use bitflags, well bitflags could be a little slow.

What annoys me the most is thelocation call, the others are in theory not that bad. Also, why store it as location? It could at least be helpful if it converted it to x,y already. It would be nice if you could tell it not to call the location thing.

I don't see why would it annoy you. To get rid of it you need an additional GC call (to get the flag from the spellID in your example) and depending on the map there might be plenty of cases where after that you still need to call GetSpellTargetLoc. So the speed gain from doing it like this is marginal at best and you just said you don't care about speed.

it is annoying, it does handle allocation and deallocation, that's annoying.

Why?

Quote:

It is vJass, you only need one gc call.

True, but it complicates the library somewhat.

Anyway, what about my last example? Would a GetSpellTargetLoc() call for point-target spells (where it's needed anyway) and no-target spells (where it isn't), but not for widget-target spells be acceptable?