Channeling spell?

wraithseeker

Tired.
Reaction score
122
How do I make a channeling spell?

I tried with this trigger of mine but had a syntax error for some reason. If you all don't get how it work, explain how to make a chaneling spell or doing it another way.

JASS:
scope deathpact initializer Init

globals
    private constant integer SPELL = 'A00P'
    private constant string EFFECT = "MDX\\NewDarkPillar.mdx"
    private integer array Index
endglobals

private struct data
//! runtextmacro PUI()
    unit caster
    timer t
    
static method create takes nothing returns data
    local data d = data.allocate()
    set d.caster = GetTriggerUnit()
    set d = data[d.caster]
    set t = NewTimer()
    set data[d.caster] = d
    return d
endmethod
endstruct

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SPELL
endfunction

private function Actions takes nothing returns nothing
    local unit target = GetSpellTargetUnit()
    local unit u = GetTriggerUnit()
    local real TLife = GetWidgetLife(target) * 0.20
    local real TMana = GetUnitState(target,UNIT_STATE_MANA) *0.20
    local real x = GetUnitX(target)
    local real y = GetUnitY(target)
    call SetWidgetLife(u,GetWidgetLife(u)+ TLife)
    call SetUnitState(u,UNIT_STATE_MANA,GetUnitState(u,UNIT_STATE_MANA) + TMana)
    call DestroyEffect(AddSpecialEffect(EFFECT,x,y))
    set u = null
    set target = null
endfunction

private function EndCast takes nothing returns nothing
    local data d = data[d.caster]
    set data[d.caster] = d
    call ReleaseTimer(d.t)
endfunction

//===========================================================================
private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddAction( t, function Actions )
    call TriggerAddCondition(t,Condition(function Conditions))
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_ENDCAST)
    call TriggerAddCondition(t,Condition(function COnditions))
    call TriggerAddAction(t,function EndCast)
endfunction
endscope


Uses PUI.
 

Flare

Stops copies me!
Reaction score
662
1) Ahm, this syntax error you speak of? Just saying "oh, there's a syntax error in there... sonewhere" isn't much good.

2)
JASS:
private function EndCast takes nothing returns nothing //action for the second trigger you created at Init
    local data d = data[d.caster]
    set data[d.caster] = d
    call ReleaseTimer(d.t)
endfunction

  • Change the first reference to d.caster to GetTriggerUnit () - you can't really expect to reference the caster of the struct you haven't acquired yet :p
  • Second line is pointless, it's just re-stating the first line, except reversing the order >.>
  • You're not destroying the struct

3)
JASS:
static method create takes nothing returns data
    local data d = data.allocate()
    set d.caster = GetTriggerUnit()
    set d = data[d.caster]
    set t = NewTimer()
    set data[d.caster] = d
    return d
endmethod

'set d = data[d.caster]' - a value for data[d.caster] shouldn't exist at that point (or if it is, it will be referring to another instance) - it shouldn't be there

Also, general idea (IMO) for channelled spells would be to add the casters to a group, start a timer and, when the timer expires, call a ForGroup for the group of casters. Then, reference them with data[GetEnumUnit ()]. Then, for destruction
JASS:
function StopCast takes nothing returns nothing
  local data d = data[GetTriggerUnit ()]
  //clean up, and so on
  call d.destroy ()
endfunction


You could take a look at my Lightning Cannon spell (well, from the CasterFunc function down, everything about that isn't specifically related to the channelled component of the spell) if you want to see an full example in action

@kenny!: I don't think that works too well in some circumstances - I tried that before with my Mana Bomb spell to try and get rid of the dynamic trigger usage, but if the caster uses the same spell again, the order doesn't appear to change between stopping the first instance, and starting the second. Obviously, if a different order is issued, no harm done, but if duration is longer than cooldown on the scripted spell, sh*t may happen :p
 

Kenny

Back for now.
Reaction score
202
Yeah i guess thats right, but if i was doing it properly i would use a struct stack and maybe a short 0(n) search (i know they suck, but using them for a spell isnt too bad) to make sure the unit wont re-fire the skill or something.
 

wraithseeker

Tired.
Reaction score
122
JASS:
scope deathpact initializer Init

globals
    private constant integer SPELL = 'A00P'
    private constant string EFFECT = "MDX\\NewDarkPillar.mdx"
    private group caster = CreateGroup()
endglobals

private struct data
//! runtextmacro PUI()
    unit caster
    unit target
    timer t
    
static method create takes nothing returns data
    local data d = data.allocate()
    set d.caster = GetTriggerUnit()
    set d.target = GetSpellTargetUnit()
    set d = data[d.caster]
    set d.t = NewTimer()
    set data[d.caster] = d
    call GroupAddUnit(caster,d.caster)
    return d
endmethod

    method onDestroy takes nothing returns nothing
        call ReleaseTimer(.t)
    endmethod
endstruct

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SPELL
endfunction

private function Heal takes nothing returns nothing
    local data d = data[GetEnumUnit()]
    local real TLife
    local real TMana
    local real x
    local real y
    set TLife = GetWidgetLife(d.target) * 0.20
    set TMana = GetUnitState(d.target,UNIT_STATE_MANA) *0.20
    set x = GetUnitX(d.target)
    set y = GetUnitY(d.target)
    call SetWidgetLife(d.caster,GetWidgetLife(d.caster)+ TLife)
    call SetUnitState(d.caster,UNIT_STATE_MANA,GetUnitState(d.caster,UNIT_STATE_MANA) + TMana)
    call DestroyEffect(AddSpecialEffect(EFFECT,x,y))
    call d.destroy()
endfunction

private function Loop takes nothing returns nothing
    local data d = data(GetTimerData(GetExpiredTimer()))
    call ForGroup(caster,function Heal)
endfunction

private function Actions takes nothing returns nothing
    local data d
    local unit u
    if GetTriggerEventId() == EVENT_PLAYER_UNIT_SPELL_EFFECT then
        call SetTimerData(d.t,d)
        call TimerStart(d.t,3,false,function Loop)
    elseif GetTriggerEventId () == EVENT_PLAYER_UNIT_SPELL_ENDCAST then
        set u = GetTriggerUnit()
        call GroupRemoveUnit(caster,u)
        set d = data<u>
        call d.destroy()
        set u = null
    endif
endfunction

//===========================================================================
private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_ENDCAST)
    call TriggerAddAction( t, function Actions )
    call TriggerAddCondition(t,Condition(function Conditions))
endfunction
endscope</u>


Didn't work.
 

Pyrogasm

There are some who would use any excuse to ban me.
Reaction score
134
You forgot to set d in Actions:

JASS:
private function Actions takes nothing returns nothing
    local data d
    local unit u
    if GetTriggerEventId() == EVENT_PLAYER_UNIT_SPELL_EFFECT then
        call SetTimerData(d.t,d)
        call TimerStart(d.t,3,false,function Loop)
    elseif GetTriggerEventId () == EVENT_PLAYER_UNIT_SPELL_ENDCAST then
        set u = GetTriggerUnit()
        call GroupRemoveUnit(caster,u)
        set d = data<u>
        call d.destroy()
        set u = null
    endif
endfunction

//Should be:
private function Actions takes nothing returns nothing
    local unit u = GetTriggerUnit()
    local data d

    if GetTriggerEventId() == EVENT_PLAYER_UNIT_SPELL_EFFECT then
        set d = data.create()
        set data<u> = d     // <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite1" alt=":)" title="Smile    :)" loading="lazy" data-shortname=":)" />
        call SetTimerData(d.t,d)
        call TimerStart(d.t,3,false,function Loop)
    elseif GetTriggerEventId () == EVENT_PLAYER_UNIT_SPELL_ENDCAST then
        call GroupRemoveUnit(caster,u)
        call d.destroy()
        set u = null
    endif
endfunction</u></u>
 

Flare

Stops copies me!
Reaction score
662
What about in the elseif? d isn't initialized prior to the d.destroy call, so unitialized variable error (if you have Grimoire running in WC3 that is :p)

Also, in your Loop function
Code:
private function Loop takes nothing returns nothing
    [COLOR="Red"]local data d = data(GetTimerData(GetExpiredTimer()))[/COLOR]
    call ForGroup(caster,function Heal)
endfunction
That line isn't really necessary - all the data is stored on the unit, so you don't need to attach to the timer. With what you're doing, you are starting a timer every instance that does ForGroup on a group used for storage (rather than instant enumeration then emptying), so you have X timers calling ForGroup, for X units, so you do things far more often than needed (and with more instances, you cause more trouble). If you had 5 instances, you would end up handling each instance 5 times (since each timer expires and calls ForGroup on caster once for each timer that expired)

If you want to stick with timer attachment, you would just do
JASS:
local data d = GetTimerData (GetExpiredTimer ())

in function Loop and handle all the actions there (without that ForGroup (casters, codeFunc) call), and leave the data[whichUnit] stuff in the Actions function, so you can tell which instance ended i.e.
JASS:
function Loop takes nothing returns nothing
local data d = GetTimerData (GetExpiredTimer ())
//other locals
call SomeFunc (...)
call AnotherFunc (...) //i.e. whatever you will be doing over the spell duration
//and so on
endfunction


function Actions takes nothing returns nothing
local data d
local unit u = GetTriggerUnit ()
if GetTriggerEventId () == EVENT_PLAYER_UNIT_SPELL_EFFECT then
 set d = data.create ()
 set data<u> = d
 call SetTimerData (d.t, d)
 call TimerStart (...)
elseif GetTriggerEventId () == EVENT_PLAYER_UNIT_ENDCAST then
 set d = data<u>
 call d.destroy ()
endif
set u = null
endfunction</u></u>


If you stick with the ForGroup method
JASS:
function GroupFunc takes nothing returns nothing
local data d = data[GetEnumUnit ()]
call SomeFunc (...)
call AnotherFunc (...)
endfunction

function TimerCallback takes nothing returns nothing
call ForGroup (casters, function GroupFunc)
endfunction

function Actions takes nothing returns nothing
local data d
local unit u = GetTriggerUnit ()
if GetTriggerEventId () == EVENT_PLAYER_UNIT_SPELL_EFFECT then
  set d = data.create ()
  set data<u> = d
  call GroupAddUnit (u, casters)
  if timerRunning == false then //probably the simplest way to handle timer starting (timerRunning being some global I decided not to add to this code <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite7" alt=":p" title="Stick Out Tongue    :p" loading="lazy" data-shortname=":p" />)
    call TimerStart (globalTimer, timeOut, true, function TimerCallback)
    set timerRunning = true //could probably be replaced with a &#039;if TimerGetElapsed (globalTimer) &gt; 0 then&#039; check although neither are all that difficult really <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite7" alt=":p" title="Stick Out Tongue    :p" loading="lazy" data-shortname=":p" />
  endif
elseif GetTriggerEventId () == EVENT_PLAYER_UNIT_ENDCAST then
  call GroupRemoveUnit (u, casters)
  call d.destroy ()
  if FirstOfGroup (casters) == null then
    call PauseTimer (globalTimer)
    set timerRunning = false
  endif
endif
set u = null
endfunction</u>
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top