Warcraft 3 documentation

vJASS & Zinc Documentation
For the latest documentation about how it works vJASS and Zinc language layers for Warcraft III, please follow these links:
Jasshelper documentation - Zinc documentation - WC3 Optimizer documentation

SpellEvent

moyack · 710

0 Members and 1 Guest are viewing this topic.

Rating

Average Score - 5 / 5

« Created: December 25, 2017, 10:09:07 PM by moyack »

SpellEvent
on: January 30, 2012, 07:12:52 AM
Category: Execution
Language: vJASS

Related Topics or Resources



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.

Code: jass
  1. library SpellEvent initializer Init requires Table
  2.  
  3. //*****************************************************************
  4. //*  SPELL EVENT LIBRARY 1.3
  5. //*
  6. //*  written by: Anitarf
  7. //*  requires: -Table
  8. //*
  9. //*  Maps with many triggered spells require many triggers that run
  10. //*  on spell events. Whenever a spell is cast, all those triggers
  11. //*  need to be evaluated by the game even though only one actually
  12. //*  needs to run. This library has been written to reduce the
  13. //*  number of triggers in such maps; instead of having a trigger
  14. //*  per spell, this library contains a single trigger which then
  15. //*  runs only the code associated with the spell that's actually
  16. //*  being cast.
  17. //*
  18. //*  Perhaps more significant than the marginal speed gain is the
  19. //*  feature that allows you to access all the spell event
  20. //*  responses from all spell events, something that the native
  21. //*  functions senselessly do not support. With this system you can
  22. //*  for example easily get the target unit of the spell on the
  23. //*  casting finish event.
  24. //*
  25. //*  All functions following the Response function interface that
  26. //*  is defined at the start of this library can be used to respond
  27. //*  to spell events. You can register a response with one of the
  28. //*  following functions, each for a different spell event:
  29. //*
  30. //*    function RegisterSpellChannelResponse takes integer spellId, Response r returns nothing
  31. //*    function RegisterSpellCastResponse takes integer spellId, Response r returns nothing
  32. //*    function RegisterSpellEffectResponse takes integer spellId, Response r returns nothing
  33. //*    function RegisterSpellFinishResponse takes integer spellId, Response r returns nothing
  34. //*    function RegisterSpellEndCastResponse takes integer spellId, Response r returns nothing
  35. //*
  36. //*  The first event occurs at the very start of the spell, when
  37. //*  the spell's casting time begins; most spells have 0 casting
  38. //*  time, so in most cases this first event occurs at the same
  39. //*  time as the second one, which runs when the unit actually
  40. //*  begins casting a spell by starting its spell animation. The
  41. //*  third event occurs when the spell effect actually takes place,
  42. //*  which happens sometime into the unit's spell animation
  43. //*  depending on the unit's "Animation - Cast Point" property.
  44. //*  The fourth event runs if the unit finishes casting the spell
  45. //*  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 then
  50. //*  that response will only run when that ability is cast; only
  51. //*  one function per ability per event is supported, if you
  52. //*  register more responses then only the last one registered will
  53. //*  be called. If, however, you pass 0 as the ability id parameter
  54. //*  then the registered function will run for all spells. Up to
  55. //*  8190 functions can be registered this way for each event.
  56. //*  These functions will be called before the ability's specific
  57. //*  function in the order they were registered.
  58. //*
  59. //*  This library provides its own event responses that work
  60. //*  better than the Blizzard's bugged native cast event responses.
  61. //*  They still won't work after a wait, but unlike Blizzard's
  62. //*  natives they will work on all spell events.
  63. //*
  64. //*  Here are usage examples for all event responses:
  65. //*
  66. //*    local integer a = SpellEvent.AbilityId
  67. //*    local unit u = SpellEvent.CastingUnit
  68. //*    local unit t = SpellEvent.TargetUnit
  69. //*    local item i = SpellEvent.TargetItem
  70. //*    local destructable d = SpellEvent.TargetDestructable
  71. //*    local location l = SpellEvent.TargetLoc
  72. //*    local real x = SpellEvent.TargetX
  73. //*    local real y = SpellEvent.TargetY
  74. //*    local boolean b = SpellEvent.CastFinished
  75. //*
  76. //*  SpellEvent.TargetLoc is provided for odd people who insist on
  77. //*  using locations, note that if you use it you have to cleanup
  78. //*  the returned location yourself.
  79. //*
  80. //*  SpellEvent.CastFinished boolean is intended only for the
  81. //*  EndCast event as it tells you whether the spell finished or
  82. //*  was interrupted.
  83. //*
  84. //*
  85. //*  Note that a few spells such as Berserk and Wind Walk behave
  86. //*  somewhat differently from regular spells: they are cast
  87. //*  instantly without regard for cast animation times, they do not
  88. //*  interrupt the unit's current order, as well as any spell it
  89. //*  may be casting. SpellEvent 1.1 now handles such spells without
  90. //*  errors provided they are truly instant (without casting time).
  91. //*
  92. //*  It also turned out that a few rare abilities like Charge Gold
  93. //*  & 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 run
  97. //*  the endcast event, so if the ability was removed from one of
  98. //*  the effect callbacks the spell event data would get cleaned
  99. //*  up prematurely, SpellEvent 1.3 fixes this issue.
  100. //*****************************************************************
  101.  
  102.     // use the RegisterSpell*Response functions to add spell event responses to the library
  103.     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 interrupting
  128.         // the caster's current order, which includes any spells the caster may
  129.         // already be casting. The following member allows the system to recover
  130.         // the original spellEvent when such an instant spell overwrites it.
  131.         private spellEvent interrupt
  132.  
  133.         method operator TargetLoc takes nothing returns location
  134.             return Location(.TargetX, .TargetY)
  135.         endmethod
  136.        
  137.         private static method create takes nothing returns spellEvent
  138.             return spellEvent.allocate()
  139.         endmethod
  140.         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.             else
  149.                 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.                 else
  154.                     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.                     else
  159.                         set s.TargetX = GetSpellTargetX()
  160.                         set s.TargetY = GetSpellTargetY()
  161.                     endif
  162.                 endif
  163.             endif
  164.             set s.interrupt = spellEvent.casterTable[s.CastingUnit]
  165.             set spellEvent.casterTable[s.CastingUnit]=integer(s)
  166.             return s
  167.         endmethod
  168.         method destroy takes nothing returns nothing
  169.             if SpellEvent!=0 then
  170.                 // the library is in the middle of running callbacks, this can happen if
  171.                 // the spell ability gets removed from the unit in one of the callbacks
  172.                 set .destroyWhenDone=true
  173.                 return
  174.             endif
  175.             if .interrupt!=0 then
  176.                 // this spell interrupted another spell, some instant spells can do this
  177.                 set spellEvent.casterTable[.CastingUnit]=.interrupt
  178.             else
  179.                 call spellEvent.casterTable.flush(.CastingUnit)
  180.             endif
  181.             set .CastingUnit=null
  182.             call .deallocate()
  183.         endmethod
  184.  
  185.         static method get takes unit caster returns spellEvent
  186.             return spellEvent(spellEvent.casterTable[caster])
  187.         endmethod
  188.         static method onInit takes nothing returns nothing
  189.             set .casterTable=HandleTable.create()
  190.         endmethod
  191.     endstruct
  192.    
  193.     globals
  194.         spellEvent SpellEvent=0
  195.     endglobals
  196.    
  197. // ================================================================
  198.  
  199.     //! textmacro spellEvent_make takes name
  200.     globals
  201.         private Response array $name$CallList
  202.         private integer $name$CallCount=0
  203.         private Table $name$Table
  204.     endglobals
  205.  
  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.         loop
  211.             exitwhen i>=$name$CallCount
  212.             call $name$CallList[i].evaluate()
  213.             set i=i+1
  214.         endloop
  215.         if $name$Table.exists(id) then
  216.             call Response($name$Table[id]).evaluate()
  217.         endif
  218.         if SpellEvent.destroyWhenDone then
  219.             set SpellEvent=0
  220.             call spellEvent.get(GetTriggerUnit()).destroy()
  221.         endif
  222.         set SpellEvent=previous
  223.     endfunction
  224.  
  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.         else
  230.             set $name$Table[spellId]=integer(r)
  231.         endif
  232.     endfunction
  233.     //! endtextmacro
  234.  
  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.     globals
  244.         // Morph abilities like Metamorphosis will cause an additional spell effect
  245.         // event to run when the caster morphs back to its original form. To avoid
  246.         // such duplicates, SpellEvent is designed to ignore any effect event that
  247.         // does not have a matching channel event preceding it.
  248.         // However, there are also rare abilities, like Charge Gold&Lumber, which
  249.         // only cause an effect event to run, so these events must not be ignored
  250.         // even though they occur without a matching channel event. This Table
  251.         // tracks ability IDs of spells that did cause a channel event so that when
  252.         // a spell is cast that doesn't cause one, its effect event is not ignored.
  253.         private Table CastAfterChannel
  254.     endglobals
  255.  
  256.     private function Channel takes nothing returns nothing
  257.         call spellEvent.init()
  258.         call ChannelCalls(GetSpellAbilityId())
  259.     endfunction
  260.  
  261.     private function Cast takes nothing returns nothing
  262.         call CastCalls(GetSpellAbilityId())
  263.     endfunction
  264.  
  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.             endif
  274.         elseif not CastAfterChannel.exists(id) then
  275.             set s = spellEvent.init()
  276.             call EffectCalls(id)
  277.             call s.destroy()
  278.         endif
  279.     endfunction
  280.  
  281.     private function Finish takes nothing returns nothing
  282.         set spellEvent.get(GetTriggerUnit()).CastFinished=true
  283.         call FinishCalls(GetSpellAbilityId())
  284.     endfunction
  285.  
  286.     private function EndCast takes nothing returns nothing
  287.         call EndCastCalls(GetSpellAbilityId())
  288.         call spellEvent.get(GetTriggerUnit()).destroy()
  289.     endfunction
  290.  
  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.     endfunction
  299.     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.     endfunction
  312.  
  313. endlibrary

Usage example
Code: jass
  1. scope QuenchLife initializer Init
  2. // A single target channeling spell that kills the target unit if the channeling is finished uninterrupted.
  3.  
  4.     globals
  5.         private constant integer ABILITY_ID = 'A000'
  6.         private constant string DEATH_EFFECT = "WriteTheEffectModelPathHere"
  7.         private constant string EFFECT_ATTACHMENT = "chest"
  8.     endglobals
  9.  
  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.     endfunction
  16.  
  17.     private function Init takes nothing returns nothing
  18.         call RegisterSpellFinishResponse(ABILITY_ID, OnCast)
  19.     endfunction
  20. endscope
Yes, if all your spells are made with triggers and use this library then you can even do this
Code: jass
  1. library SpellChaos initializer Init
  2.     globals
  3.         private constant real CHANCE_FOR_SPELLS_TO_MISFIRE = 0.1
  4.         private constant real CHANCE_FOR_SPELLS_TO_FIZZLE = 0.1
  5.     endglobals
  6.  
  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.         endif
  16.         set t = null
  17.     endfunction
  18.  
  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.     endfunction
  23. endscope
« Last Edit: December 25, 2017, 10:10:13 PM by moyack »



 

Vivir aprendiendo.co - A place for learning stuff, in Spanish   Chaos Realm - The world of Game modders and wc3 addicts   Diplo, a gaming community   Power of Corruption, an altered melee featuring Naga and Demon. Play it now!!!   WC3JASS.com - The JASS Vault + vJASS and Zinc   Jetcraft - A Starcraft II mod   WormTastic Clan (wTc)