HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

vJass Woes

06-14-2008, 04:51 PM#1
darkwulfv
Okay, I've finally decided to really learn how to use vJass, so I started easy: Structs, scopes, libraries, and free globals.

Structs, libraries, and free globals work fine. No problems there. (I do have a question regarding structs, but that's later)

But for some reason that I still cannot figure out, and no matter how many times I've tried, (and I've tried lots of ways, too), I cannot get scopes to work. Period. Whenever I use them, the trigger never ever fires, whether the functions are public, private, or neither. But once I remove the scope from the trigger, (and everything associated), the trigger magically fires and works.

My code is as follows:
Collapse JASS:
scope EMPPulse

//Configuration Globals
globals
  private integer SpellID = 'A000' //ID of the Spell
  private integer BuffID = 'B000' //ID of the Spell's Buff (Required!!)
  private integer DummyID = 'e000' //ID of the dummy caster you use
  private integer DummySpellID = 'A001' //ID of the dummy stun spell
  private integer StunID = 'B001' //ID of the dummy stun spell's buff (not required, and only used for indentification if you want)
  private integer Strikes = 5 //Number of targets
  private real Damage = 0. //Damage dealt to each target
  private real Radius = 700. //Radius you're checking for units around the initial target
  private real Interval = .85 //Time delay between each bolt
  private string Graphic = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl" //Path of the effect used on targets
endglobals  
//End Configuration Globals

//If you have any more filter booleans to add, feel free to do so.
private function filter takes nothing returns boolean
  local unit u = GetFilterUnit()
  local boolean b1 = GetWidgetLife(u) > .405
  local boolean b2 = IsUnitType(u, UNIT_TYPE_MECHANICAL) == true
  local boolean b3 = IsUnitType(u, UNIT_TYPE_STRUCTURE) == false
  local boolean b4 = GetUnitAbilityLevel(u, 'avul') < 1
  local boolean b5 = IsUnitEnemy(u, GetOwningPlayer(GetTriggerUnit())) == true
  
  set u = null
  return b1 and b2 and b3 and b4 and b5
endfunction

//Don't touch anything beyond this point!
  
globals 
  private timer Timer = CreateTimer()
  private group Targets = CreateGroup()
  private unit array BoltTargets
endglobals
  
private struct EMP_Data
  static unit caster = null
  static unit target = null
  static unit previous = null
  static integer hit = 0
endstruct

private function Callback takes nothing returns nothing
  local unit u = EMP_Data.caster
  local unit u2 = EMP_Data.target
  local unit prev = EMP_Data.previous
  local unit u3 
  local integer hit = EMP_Data.hit
 
  if hit != Strikes and u2 != null then
    call UnitDamageTarget(u, u2, Damage, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
    if prev == null then
      set u3 = CreateDummyUnit(GetOwningPlayer(u), DummyID, GetUnitX(u), GetUnitY(u), DummySpellID, hit + 1, true)
      call IssueTargetOrder(u3, "thunderbolt", u2)
    else
      set u3 = CreateDummyUnit(GetOwningPlayer(u), DummyID, GetUnitX(prev), GetUnitY(prev), DummySpellID, hit + 1, true)
      call IssueTargetOrder(u3, "thunderbolt", u2)
      call DestroyEffect(AddSpecialEffectTarget(Graphic, prev, "origin"))
    endif
    set EMP_Data.hit = hit + 1
    set EMP_Data.target = BoltTargets[hit]
    set EMP_Data.previous = u2
  else
    call PauseTimer(GetExpiredTimer())
    call DestroyTimer(GetExpiredTimer())
  endif
  
  set u = null
  set u2 = null
  set u3 = null
  set prev = null
endfunction

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

private function Actions takes nothing returns nothing
  local unit u = GetSpellTargetUnit()
  local EMP_Data EMP = EMP_Data.create()
  local unit f
  local integer i
  
  set EMP.caster = GetTriggerUnit()
  set EMP.target = u
  
  loop
  exitwhen GetUnitAbilityLevel(u, BuffID) > 0
    call PolledWait(.2)
  endloop
  
  call GroupEnumUnitsInRange(Targets, GetUnitX(u), GetUnitY(u), Radius, Condition(function filter))
  loop
    set f = FirstOfGroup(Targets)
    exitwhen f == null
    set BoltTargets[i] = f
    call GroupRemoveUnit(Targets, f)
    set i = i + 1
  endloop
      
  call TimerStart(Timer, Interval, true, function Callback)
  
  call PolledWait(4.)
  
  call EMP.destroy()
  set u = null
  set f = null
endfunction

//===========================================================================
public function InitTrig_EMPPulse takes nothing returns nothing
  local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition(t, Condition( function Conditions ) )
    call TriggerAddAction(t, function Actions )
  set t = null
endfunction

endscope

Also, my question about structs: I can't use struct members at all under any circumstance unless I put "static" before it. But in every other code I've seen that uses structs the same way as me, (I think), there's no "static" before it. Am I some weird, rare exception?


PS: If you spot ways to better the code, be it normal jass or vJass, let me know. And I wouldn't be surprised if the code just sucks =/... or is done entirely wrong.
06-14-2008, 05:09 PM#2
Gwypaas
You can use
Collapse JASS:
scope myScope initializer myInit
     private function myInit takes nothing returns nothing
           // The stuff you need here
     endfunction
endscope
You can also use this: (The trigger this scope is placed in must have the same name as the scope does.)
Collapse JASS:
scope anotherScope
     public function InitTrig takes nothing returns nothing
          // Your stuff here.
     endfunction
endscope
06-14-2008, 05:22 PM#3
moyack
Theone problem is that you are using wrong the structs... you must remove the static word on the struct variables.

Collapse JASS:
private struct EMP_Data
  unit caster 
  unit target 
  unit previous 
  integer hit = 0
endstruct
06-14-2008, 06:24 PM#4
Vexorian
I should add a syntax error when 'InitTrig_' is a prefix of a public scope function, it is becoming a common mistake.
06-14-2008, 06:38 PM#5
darkwulfv
Quote:
Originally Posted by Moyack
one problem is that you are using wrong the structs... you must remove the static word on the struct variables.

Quote:
Originally Posted by Me
Also, my question about structs: I can't use struct members at all under any circumstance unless I put "static" before it.
To elaborate on that: If I try without "static", I get an error saying "blahblah is not a static member of struct MyStruct"

@Vex: So, what I should do is remove the "InitTrig_" and that will help something?

@Gwypaas: What would that do, exactly?
06-14-2008, 07:14 PM#6
darkwulfv
Bump/Update:

Okay, I removed the InitTrig_ and gave my scope an initializer (do all scopes require one?). The spell now works.... once. I still can't make my structs accept non-static members, and that might have something to do with it.

Here's the new code.
Collapse JASS:
scope EMPPulse initializer EMPPulse

//Configuration Globals
globals
  private integer SpellID = 'A000' //ID of the Spell
  private integer BuffID = 'B000' //ID of the Spell's Buff (Required!!)
  private integer DummyID = 'e000' //ID of the dummy caster you use
  private integer DummySpellID = 'A001' //ID of the dummy stun spell
  private integer StunID = 'B001' //ID of the dummy stun spell's buff (not required, and only used for indentification if you want)
  private integer Strikes = 5 //Number of targets
  private real Damage = 0. //Damage dealt to each target
  private real Radius = 700. //Radius you're checking for units around the initial target
  private real Interval = .85 //Time delay between each bolt
  private string Graphic = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl" //Path of the effect used on targets
endglobals  
//End Configuration Globals

//If you have any more filter booleans to add, feel free to do so.
private function filter takes nothing returns boolean
  local unit u = GetFilterUnit()
  local boolean b1 = GetWidgetLife(u) > .405
  local boolean b2 = IsUnitType(u, UNIT_TYPE_MECHANICAL) == true
  local boolean b3 = IsUnitType(u, UNIT_TYPE_STRUCTURE) == false
  local boolean b4 = GetUnitAbilityLevel(u, 'avul') < 1
  local boolean b5 = IsUnitEnemy(u, GetOwningPlayer(GetTriggerUnit())) == true
  
  set u = null
  return b1 and b2 and b3 and b4 and b5
endfunction

//Don't touch anything beyond this point!
  
globals 
  private timer Timer = CreateTimer()
  private group Targets = CreateGroup()
  private unit array BoltTargets
endglobals
  
private struct EMP_Data
   unit caster = null
   unit target = null
   unit previous = null
   integer hit = 0
endstruct

private function Callback takes nothing returns nothing
  local unit u = EMP_Data.caster
  local unit u2 = EMP_Data.target
  local unit prev = EMP_Data.previous
  local unit u3 
  local integer hit = EMP_Data.hit
 
  if hit != Strikes and u2 != null then
    call UnitDamageTarget(u, u2, Damage, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
    if prev == null then
      set u3 = CreateDummyUnit(GetOwningPlayer(u), DummyID, GetUnitX(u), GetUnitY(u), DummySpellID, hit + 1, true)
      call IssueTargetOrder(u3, "thunderbolt", u2)
    else
      set u3 = CreateDummyUnit(GetOwningPlayer(u), DummyID, GetUnitX(prev), GetUnitY(prev), DummySpellID, hit + 1, true)
      call IssueTargetOrder(u3, "thunderbolt", u2)
      call DestroyEffect(AddSpecialEffectTarget(Graphic, prev, "origin"))
    endif
    set EMP_Data.hit = hit + 1
    set EMP_Data.target = BoltTargets[hit]
    set EMP_Data.previous = u2
  else
    call PauseTimer(GetExpiredTimer())
  endif
  
  set u = null
  set u2 = null
  set u3 = null
  set prev = null
endfunction

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

private function Actions takes nothing returns nothing
  local unit u = GetSpellTargetUnit()
  local EMP_Data EMP = EMP_Data.create()
  local unit f
  local integer i = 0
  
  set EMP.caster = GetTriggerUnit()
  set EMP.target = u
  
  loop
  exitwhen GetUnitAbilityLevel(u, BuffID) > 0
    call PolledWait(.2)
  endloop
  
  call GroupEnumUnitsInRange(Targets, GetUnitX(u), GetUnitY(u), Radius, Condition(function filter))
  loop
    set f = FirstOfGroup(Targets)
    exitwhen f == null
    set BoltTargets[i] = f
    call GroupRemoveUnit(Targets, f)
    set i = i + 1
  endloop
      
  call TimerStart(Timer, Interval, true, function Callback)
  
  call PolledWait(4.)
  
  call EMP.destroy()
  set u = null
  set f = null
endfunction

//===========================================================================
public function EMPPulse takes nothing returns nothing
  local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition(t, Condition( function Conditions ) )
    call TriggerAddAction(t, function Actions )
  set t = null
endfunction

endscope
06-14-2008, 07:25 PM#7
chobibo
Collapse JASS:
public function EMPPulse takes nothing returns nothing
should be
Collapse JASS:
public function InitTrig takes nothing returns nothing
which would be then converted to normal jass
Collapse JASS:
function InitTrig_EMPPulse takes nothing returns nothing

The reason it's not working is because newgen named the function "InitTrig_EMPPulse" to "InitTrig_EMPPulse_EMPPulse"

Also create a global struct so you could access it again on the callback function, don't destroy it immediately after just storing to it:
Collapse JASS:
globals
     private EMP_Data Emp=0
endglobals

Dude you also need to attach the EMP_Data to the timer so you could access it again in the callback function, "Structs" return integers.

---> First, allocate an index/instance for the struct data using .create() method. structs are arrays (globals) having the same index e.g Array1[1], Array2[1]. Store data. Don't destroy the struct just yet, you're still using it.
---> Then attach it to something that remains constant when the data is needed to be accessed, I'm assuming that you need to attach the timer, I'm not sure though.
---> On the callback function, process data, then destroy the struct so the index/instance will be recycled.

Note: I may be wrong lol!

You don't need to null t because t is never destroyed, handle id's only leak when a local variable points to a handle after it gets destroyed.
Collapse JASS:
public function EMPPulse takes nothing returns nothing
  local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition(t, Condition( function Conditions ) )
    call TriggerAddAction(t, function Actions )
  set t = null//<---- !!
endfunction
06-14-2008, 07:41 PM#8
darkwulfv
Okay, I'll make that change.

And I don't destroy the struct until well after the spell would be completed, but I'll make a global anyways for the sake of doing so.

Nulling "t" is a habit I've formed. And what you said doesn't sound right...
Collapse JASS:
function lol takes nothing returns nothing
  local unit u = GetTriggerUnit()
  call KillUnit(u)
endfunction
So, by your definition, that wouldn't leak? Because from the first day i learned JASS I was told that does. (Not nulling your handle variables)

And I still can't get the damn spell to work more than once. After the first cast, nothing happens!

EDIT: I got the struct to work w/o the "static" prefix. No idea how.

EDIT/Off topic: It just occurred to me that "EMP Pulse" breaks down into "Electromagnetic Pulse Pulse". Heh.
06-14-2008, 07:51 PM#9
chobibo
It's actually InitTrig_EMPPulse_EMPPulse.

EDIT: I stand corrected.
06-14-2008, 07:58 PM#10
darkwulfv
Yes, after the spell would end. (note the polledwait of 4 seconds above it)

So, if I make a global struct, why would I need to attach it to a timer? Isn't a global... globally accessable? The way I have it now, it's just a global struct. I add things to it, and it works. One time. And no, I never destroy it anywhere. (New code doesn't kill it)
(If you can't tell, I'm pathetically new to this)

I've gotten structs to work before on a similar process (struct used to access data for a timer callback) and I had no trouble, nor did I need to make a global struct and/or attach it to the timer.
06-14-2008, 08:03 PM#11
chobibo
Dude you don't need the polled wait, just destroy the struct in the callback. You're using 2 timers instead of 1 per instance, which will waste processing power lol. Hey I'll review you're spell thouroughly I was mistaken in my statement that you destroyed it immediately. I'm sorry dude hope you understand, I am lazy lol!

EDIT:
The difference in nulling a unit handle to a trigger handle you just used as an example is that, when the unit dies the variable points to nothing, hence a handle id leaks, likewise, in an InitTrig function, the trigger isn't destroyed at all, so if the variable points to id 0x10000F then after 30 minutes, it still points there, the handle is intact, no leaks. I also null everything at the end of each function to ensure that no handle id's would leak, but I think you wanted to know that info so I shared it to you.
06-14-2008, 08:08 PM#12
darkwulfv
It's okay, people make mistakes.

I went back to the aforementioned spell that worked with structs and re-designed my spell to match the way it worked.

...

The spell works fine. ONCE. I still cannot figure out why in blazing hell it's not working more than once. Here is the latest code:
Note: The other spell didn't use scopes, if that means anything at all.
UPDATE: The spell does fire a second time. I can tell because the targeted unit gets hit by the dummy spell. However, the rest of the spell does nothing, and after that nothing happens at all when casted.

Yes, I'm using "static", because the damn syntax checker attacks me if I don't.
Collapse JASS:
scope EMPPulse

//Configuration Globals
globals
  private integer SpellID = 'A000' //ID of the Spell
  private integer BuffID = 'B000' //ID of the Spell's Buff (Required!!)
  private integer DummyID = 'e000' //ID of the dummy caster you use
  private integer DummySpellID = 'A001' //ID of the dummy stun spell
  private integer StunID = 'B001' //ID of the dummy stun spell's buff (not required, and only used for indentification if you want)
  private integer Strikes = 5 //Number of targets
  private real Damage = 0. //Damage dealt to each target
  private real Radius = 700. //Radius you're checking for units around the initial target
  private real Interval = .85 //Time delay between each bolt
  private string Graphic = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl" //Path of the effect used on targets
endglobals  
//End Configuration Globals

//If you have any more filter booleans to add, feel free to do so.
private function filter takes nothing returns boolean
  local unit u = GetFilterUnit()
  local boolean b1 = GetWidgetLife(u) > .405
  local boolean b2 = IsUnitType(u, UNIT_TYPE_MECHANICAL) == true
  local boolean b3 = IsUnitType(u, UNIT_TYPE_STRUCTURE) == false
  local boolean b4 = GetUnitAbilityLevel(u, 'avul') < 1
  local boolean b5 = IsUnitEnemy(u, GetOwningPlayer(GetTriggerUnit())) == true
  
  set u = null
  return b1 and b2 and b3 and b4 and b5
endfunction

//Don't touch anything beyond this point!
  
private struct EMP_Data
  static unit caster = null
  static unit target = null
  static unit previous = null
  static integer hit = 0
endstruct

globals 
  private timer Timer = CreateTimer()
  private group Targets = CreateGroup()
  private unit array BoltTargets
//  private EMP_Data EMP = 0
endglobals
  

private function Callback takes nothing returns nothing
  local unit u = EMP_Data.caster
  local unit u2 = EMP_Data.target
  local unit prev = EMP_Data.previous
  local unit u3 
  local integer hit = EMP_Data.hit
 
  if hit != Strikes and u2 != null then
    call UnitDamageTarget(u, u2, Damage, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
    if prev == null then
      set u3 = CreateDummyUnit(GetOwningPlayer(u), DummyID, GetUnitX(u), GetUnitY(u), DummySpellID, hit + 1, true)
      call IssueTargetOrder(u3, "thunderbolt", u2)
    else
      set u3 = CreateDummyUnit(GetOwningPlayer(u), DummyID, GetUnitX(prev), GetUnitY(prev), DummySpellID, hit + 1, true)
      call IssueTargetOrder(u3, "thunderbolt", u2)
      call DestroyEffect(AddSpecialEffectTarget(Graphic, prev, "origin"))
    endif
    set EMP_Data.hit = hit + 1
    set EMP_Data.target = BoltTargets[hit]
    set EMP_Data.previous = u2
  else
    call PauseTimer(Timer)
    call DestroyTimer(Timer)
    set Timer = CreateTimer()
  endif
  
  set u = null
  set u2 = null
  set u3 = null
  set prev = null
endfunction

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

private function Actions takes nothing returns nothing
  local unit u = GetSpellTargetUnit()
  local unit f
  local EMP_Data EMP = EMP_Data.create()
  local integer i = 0
  
  set EMP.caster = GetTriggerUnit()
  set EMP.target = u
  
  loop
  exitwhen GetUnitAbilityLevel(u, BuffID) > 0
    call PolledWait(.2)
  endloop
  
  call GroupEnumUnitsInRange(Targets, GetUnitX(u), GetUnitY(u), Radius, Condition(function filter))
  call GroupRemoveUnit(Targets, u)
  loop
    set f = FirstOfGroup(Targets)
    exitwhen f == null
    set BoltTargets[i] = f
    call GroupRemoveUnit(Targets, f)
    set i = i + 1
  endloop
      
  call TimerStart(Timer, Interval, true, function Callback)
  
  call PolledWait(6.)
  call EMP.destroy()
  
  set u = null
  set f = null
endfunction

//===========================================================================
public function InitTrig takes nothing returns nothing
  local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition(t, Condition( function Conditions ) )
    call TriggerAddAction(t, function Actions )
  set t = null
endfunction

endscope
06-14-2008, 08:14 PM#13
chobibo
May I edit your code dude?
06-14-2008, 08:15 PM#14
darkwulfv
Yeah sure, just point out what you change so I know. (Helps me learn)
06-14-2008, 08:22 PM#15
chobibo
Ok dude thanks.
I'll explain more about structs:
Collapse JASS:
private struct EMP_Data
  static unit caster = null
  static unit target = null
  static unit previous = null
  static integer hit = 0
endstruct
is in normal WE syntax
Collapse JASS:
globals
    unit array caster
    unit array target
    unit array previous
    integer array hit
endglobals

now when you call the method .create() an index is generated for the parallel arrays. allocators "may" look like this(I hope you know what textmacros are if not i'll post a non-text macro version just say it):
Collapse JASS:
library IndexGenerator
//! textmacro IndexGenerator takes ArrayName
globals
    //##IndexGenerator for $ArrayName$
    integer $ArrayName$_StackN=0
    integer $ArrayName$_StackMax=0
    integer array $ArrayName$_IndexStack
    //##End of IndexGenerator for $ArrayName$
endglobals

function Get$ArrayName$Index takes nothing returns integer //<----- .allocate()/.create() method
    if $ArrayName$_StackN==0 then
        set $ArrayName$_StackMax=$ArrayName$_StackMax+1
        debug call BJDebugMsg("Increasing Index... new index is: "+I2S($ArrayName$_StackMax))
        return $ArrayName$_StackMax
    endif
    set $ArrayName$_StackN=$ArrayName$_StackN-1
    debug call BJDebugMsg("Re-using Index: "+I2S($ArrayName$_IndexStack[$ArrayName$_StackN]))
    return $ArrayName$_IndexStack[$ArrayName$_StackN]
endfunction

function Recycle$ArrayName$Index takes integer n returns nothing //<----- Index Recycler/ .ondestroy() method
    set $ArrayName$_IndexStack[$ArrayName$_StackN]=n
    debug call BJDebugMsg("Recycling Index: "+I2S(n))
    set $ArrayName$_StackN=$ArrayName$_StackN+1
endfunction
//! endtextmacro
endlibrary
please note that I just assumed it worked this way, I also can't understand the inner workings of vex's allocator.

EDIT: NON-Textmacro version:
Collapse JASS:
library IndexGenerator
globals
    integer [structname]_StackN=0
    integer [structname]_StackMax=0
    integer array [structname]_IndexStack
endglobals

function Get[structname]Index takes nothing returns integer
    if [structname]_StackN==0 then
        set [structname]_StackMax=[structname]_StackMax+1
        debug call BJDebugMsg("Increasing Index... new index is: "+I2S([structname]_StackMax))
        return [structname]_StackMax
    endif
    set [structname]_StackN=[structname]_StackN-1
    debug call BJDebugMsg("Re-using Index: "+I2S([structname]_IndexStack[[structname]_StackN]))
    return [structname]_IndexStack[[structname]_StackN]
endfunction

function Recycle[structname]Index takes integer n returns nothing
    set [structname]_IndexStack[[structname]_StackN]=n
    debug call BJDebugMsg("Recycling Index: "+I2S(n))
    set [structname]_StackN=[structname]_StackN+1
endfunction

endlibrary

Umm Darkwulfv can you describe what the spell does?