• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Problem with private variables

Status
Not open for further replies.
Level 5
Joined
Oct 2, 2013
Messages
95
Hey all!

This is my very first vJASS script. It is a work in progress for a spell.

My spell will create three damage-absorbing runes around the caster. All damage done to the caster is redirected to the rune with the least health. The caster may then use a secondary, targeted spell to throw one of his rune's at an enemy, dealing damage based on the rune's health.

Right now, I've finished the damage mitigation and rune rotation. But when working on the rune throwing i get some problems. At the DoomRitesThrowCast function, you can see I've used multiple debug messages. When using the throw ingame, all the messages I see are:
"Checking throw..."
"0..."

and thats it.

It looks like the trigger I created inside my main trigger won't load the scope's private vars, trashing the GroupAdd/RemoveUnit functions.

Also, because it is my first code, it IS full of trashy functions and unnecessary vars. If you guys could help me at that too, I'd be grateful :D

JASS:
scope DoomRites
    globals
        private unit caster
        private unit target
        private timer duration = CreateTimer()
        private group runes = CreateGroup()
        private real rotation = 0
        private integer runecount = 0
        private unit thrownrune
        private group thrown = CreateGroup()
        // Vars used for the damage protection.
        private real oldlife = 0
        private integer runecount2 = 0
        private unit defendingrune
        private real damage = 0
        private real comparehealth
        private real comparehealth2
        //====================================//
        private unit attacker
        // This is the trigger used for damage detection.
        private trigger gg_trg_DoomRitesDmg
        // This is the one used to detect the gaze spellcast.
        private trigger gg_trg_DoomRitesThw
    endglobals

    function Trig_DoomRites_Conditions takes nothing returns boolean
        if ( not (GetSpellAbilityId() == 'A05R')) then
            return false
        endif
        return true
    endfunction
 
    function Trig_DoomRites_Conditions2 takes nothing returns boolean
        if ( not (GetSpellAbilityId() == 'A05S')) then
            return false
        endif
        return true
    endfunction
    
    function DoomRitesRotateGo takes nothing returns nothing
        local unit u = GetEnumUnit()
        local location l = GetUnitLoc(caster)
        local real x = GetLocationX(l) + 100 * Cos((rotation + ( 120 * runecount)) * bj_DEGTORAD)
        local real y = GetLocationY(l) + 100 * Sin((rotation + ( 120 * runecount)) * bj_DEGTORAD)
        local location m = Location(x,y)
        set runecount = runecount + 1
        set rotation = rotation + 1
        call SetUnitPositionLoc( u, m)
        call RemoveLocation(m)
        call RemoveLocation(l)
        set u = null
    endfunction
    
    function DoomRitesRotationPre takes nothing returns nothing
        set runecount = 0
        call ForGroup( runes, function DoomRitesRotateGo)
    endfunction
    
    function DoomRitesNewCast takes nothing returns nothing
        local integer i = 0
        local location l = GetUnitLoc(caster)
        local player p = GetOwningPlayer(caster)
        local timer t = CreateTimer()
        loop
            exitwhen i == 3
            call GroupAddUnit( runes, CreateUnitAtLoc( p, 'e00P' , l, 0))
            call DisplayTextToForce( GetPlayersAll(), "Unit created" )
            set i = i + 1
        endloop
        call TimerStart( t, 0.03, true, function DoomRitesRotationPre)
        call DisplayTextToForce( GetPlayersAll(), "The cast declares" )
        call RemoveLocation(l)
        set t = null
        set p = null
    endfunction
    
    function DoomRitesSortRunesByHealth takes nothing returns nothing
        local unit picked = GetEnumUnit()
        if ( GetUnitState( picked, UNIT_STATE_LIFE)<= comparehealth) then
            set defendingrune = picked
        endif
        set runecount2 = runecount2 + 1
        set picked = null
    endfunction

    function DoomRitesDefend takes nothing returns nothing
        local real templife
        local real passdmg
        call DisplayTextToPlayer(GetLocalPlayer(),0,0,"Works!")
        set comparehealth = 999999
        loop
            exitwhen damage <= 0
            set runecount2 = 0
            call ForGroup( runes, function DoomRitesSortRunesByHealth)
            set templife = GetUnitState( defendingrune, UNIT_STATE_LIFE)
            call SetUnitState( defendingrune, UNIT_STATE_LIFE,((GetUnitState( defendingrune, UNIT_STATE_LIFE)) - damage))
            if ( damage >= templife ) then
                set damage = (damage - (damage - templife))
            else
                set damage = 0
            endif
            if ( runecount2 == 0 ) then
                call DisplayTextToForce( GetPlayersAll(), "Some damage passes throw the runes!" )
                set passdmg = damage
                set damage = 0
            endif
        endloop
        call UnitRemoveAbility( caster,'A043')
        call UnitDamageTarget( attacker, caster, passdmg, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
    endfunction
    
    function Trig_DoomRitesDmg_Actions takes nothing returns nothing
        local timer t = CreateTimer()
        set oldlife = GetUnitState( caster, UNIT_STATE_LIFE)
        set attacker = udg_DamageEventSource
        set damage = udg_DamageEventAmount
        call UnitAddAbility(caster,'A043')
        call TimerStart( t, 0, false, function DoomRitesDefend )
        set t = null
    endfunction
    
    function DmgTrig_DoomRites takes nothing returns nothing
        set gg_trg_DoomRitesDmg = CreateTrigger( )
        call TriggerRegisterVariableEvent( gg_trg_DoomRitesDmg, "udg_DamageEvent", EQUAL, 1.00 )
        call TriggerAddAction( gg_trg_DoomRitesDmg, function Trig_DoomRitesDmg_Actions)
    endfunction
    
    function DoomRitesThrowMove takes nothing returns nothing
        local unit u = GetEnumUnit()
        local location t = GetUnitLoc(target)
        local location r = GetUnitLoc(u)
        local real a = bj_RADTODEG * Atan2(GetLocationY(t) - GetLocationY(r), GetLocationX(t) - GetLocationX(r))
        local real x = GetLocationX(r) + 60 * Cos(a * bj_DEGTORAD)
        local real y = GetLocationY(r) + 0 * Sin(a * bj_DEGTORAD)
        local location m = Location(x, y)
        call SetUnitPositionLoc( u, m)
        set u =null
        call RemoveLocation(t)
        call RemoveLocation(r)
        call RemoveLocation(m)
    endfunction
    
    function DoomRitesThrowPre takes nothing returns nothing
        call ForGroup( thrown, function DoomRitesThrowMove )
    endfunction
    
    function DoomRitesSortRunesByHealth2 takes nothing returns nothing
        local unit picked = GetEnumUnit()
        if ( GetUnitState( picked, UNIT_STATE_LIFE)<= comparehealth2) then
            set thrownrune = picked
        endif
        set picked = null
    endfunction
       
    function DoomRitesThrowCast takes nothing returns nothing
        local timer t = CreateTimer()
        set comparehealth2 = 0
        set runes = runes
        set target = GetSpellTargetUnit()
        call DisplayTextToForce( GetPlayersAll(), "Checking Throw..." )
        set comparehealth2 = 0
        call ForGroup( runes, function DoomRitesSortRunesByHealth2)
        call DisplayTextToForce( GetPlayersAll(), "0..." )
        call GroupRemoveUnit( runes, thrownrune )
        call DisplayTextToForce( GetPlayersAll(), "1..." )
        call GroupAddUnit( thrown, thrownrune )
        call DisplayTextToForce( GetPlayersAll(), "2..." )
        call TimerStart( t, 0.03, true, function DoomRitesThrowPre )
        call DisplayTextToForce( GetPlayersAll(), "Throw. Fuck yiss." )
        set t = null
    endfunction
    
    function DoomRitesThrowTrig takes nothing returns nothing
        set gg_trg_DoomRitesThw = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( gg_trg_DoomRitesThw, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( gg_trg_DoomRitesThw, Condition( function Trig_DoomRites_Conditions2 ) )
        call TriggerAddAction( gg_trg_DoomRitesThw, function DoomRitesThrowCast )
    endfunction
    
    function Trig_DoomRites_Actions takes nothing returns nothing
        set caster = GetTriggerUnit()
        set target = GetSpellTargetUnit()
        if ( GetSpellAbilityId() == 'A05R' ) then
            call DoomRitesNewCast()
            call DmgTrig_DoomRites()
            call DoomRitesThrowTrig()
            call DisplayTextToForce( GetPlayersAll(), "Coomrites  catr" )
        endif
        call DisplayTextToForce( GetPlayersAll(), "Spellcast" )
    endfunction

endscope

//===========================================================================

function InitTrig_DoomRites takes nothing returns nothing
    local integer index
    set gg_trg_DoomRites = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_DoomRites, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    //loop
        //call TriggerRegisterPlayerUnitEvent(gg_trg_DoomRites, Player(index), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        //set index = index + 1
        //exitwhen index == bj_MAX_PLAYER_SLOTS
    //endloop
    call TriggerAddCondition( gg_trg_DoomRites, Condition( function Trig_DoomRites_Conditions ) )
    call TriggerAddAction( gg_trg_DoomRites, function Trig_DoomRites_Actions )
endfunction
 
Level 12
Joined
Feb 22, 2010
Messages
1,115
Not sure I understand spell completely, so warn me if I mistake.And please make your global variables' first letter capital next time(or not sure how a syntax coders prefer here) it is hard to distinct variables.

JASS:
    function DoomRitesThrowCast takes nothing returns nothing
        local timer t = CreateTimer()
        set comparehealth2 = 0
        set runes = runes
        set target = GetSpellTargetUnit()
        call DisplayTextToForce( GetPlayersAll(), "Checking Throw..." )
        set comparehealth2 = 0


        // AT THIS LINE, VARIABLE COMPAREHEALTH2 EQUALS TO 0
        

        call ForGroup( runes, function DoomRitesSortRunesByHealth2)
    endfunction

so when your code jumps to function DoomRitesSortRunesByHealth2, it looks like this


JASS:
function DoomRitesSortRunesByHealth2 takes nothing returns nothing
        local unit picked = GetEnumUnit()
        if ( GetUnitState( picked, UNIT_STATE_LIFE)<= 0) then
            set thrownrune = picked
        endif
        set picked = null
    endfunction

and I dont think your rune has 0 hp when you use that, so variable throwrune isn't getting set to any value and stays as null.

call GroupRemoveUnit( runes, thrownrune )

So when you try to use a null variable inside a function here, your code stoppes working there.
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
Put all functions inside the scope.

Private variables mean that they cannot be read outside of the scope so you need to put them inside the scope.

Make your functions private functions. Shorten all there names.

JASS:
scope DoomRites initializer InitDoomRites
    globals
        private unit caster
        private unit target
        private timer duration = CreateTimer()
        private group runes = CreateGroup()
        private real rotation = 0
        private integer runecount = 0
        private unit thrownrune
        private group thrown = CreateGroup()
        // Vars used for the damage protection.
        private real oldlife = 0
        private integer runecount2 = 0
        private unit defendingrune
        private real damage = 0
        private real comparehealth
        private real comparehealth2
        //====================================//
        private unit attacker
        // This is the trigger used for damage detection.
        private trigger gg_trg_DoomRitesDmg
        // This is the one used to detect the gaze spellcast.
        private trigger gg_trg_DoomRitesThw
    endglobals

    private function Trig_DoomRites_Conditions takes nothing returns boolean
        if ( not (GetSpellAbilityId() == 'A05R')) then
            return false
        endif
        return true
    endfunction
 
    private function Trig_DoomRites_Conditions2 takes nothing returns boolean
        if ( not (GetSpellAbilityId() == 'A05S')) then
            return false
        endif
        return true
    endfunction
    
    private function DoomRitesRotateGo takes nothing returns nothing
        local unit u = GetEnumUnit()
        local location l = GetUnitLoc(caster)
        local real x = GetLocationX(l) + 100 * Cos((rotation + ( 120 * runecount)) * bj_DEGTORAD)
        local real y = GetLocationY(l) + 100 * Sin((rotation + ( 120 * runecount)) * bj_DEGTORAD)
        local location m = Location(x,y)
        set runecount = runecount + 1
        set rotation = rotation + 1
        call SetUnitPositionLoc( u, m)
        call RemoveLocation(m)
        call RemoveLocation(l)
        set u = null
    endfunction
    
    private function DoomRitesRotationPre takes nothing returns nothing
        set runecount = 0
        call ForGroup( runes, function DoomRitesRotateGo)
    endfunction
    
    private function DoomRitesNewCast takes nothing returns nothing
        local integer i = 0
        local location l = GetUnitLoc(caster)
        local player p = GetOwningPlayer(caster)
        local timer t = CreateTimer()
        loop
            exitwhen i == 3
            call GroupAddUnit( runes, CreateUnitAtLoc( p, 'e00P' , l, 0))
            call DisplayTextToForce( GetPlayersAll(), "Unit created" )
            set i = i + 1
        endloop
        call TimerStart( t, 0.03, true, function DoomRitesRotationPre)
        call DisplayTextToForce( GetPlayersAll(), "The cast declares" )
        call RemoveLocation(l)
        set t = null
        set p = null
    endfunction
    
    private function DoomRitesSortRunesByHealth takes nothing returns nothing
        local unit picked = GetEnumUnit()
        if ( GetUnitState( picked, UNIT_STATE_LIFE)<= comparehealth) then
            set defendingrune = picked
        endif
        set runecount2 = runecount2 + 1
        set picked = null
    endfunction

    private function DoomRitesDefend takes nothing returns nothing
        local real templife
        local real passdmg
        call DisplayTextToPlayer(GetLocalPlayer(),0,0,"Works!")
        set comparehealth = 999999
        loop
            exitwhen damage <= 0
            set runecount2 = 0
            call ForGroup( runes, function DoomRitesSortRunesByHealth)
            set templife = GetUnitState( defendingrune, UNIT_STATE_LIFE)
            call SetUnitState( defendingrune, UNIT_STATE_LIFE,((GetUnitState( defendingrune, UNIT_STATE_LIFE)) - damage))
            if ( damage >= templife ) then
                set damage = (damage - (damage - templife))
            else
                set damage = 0
            endif
            if ( runecount2 == 0 ) then
                call DisplayTextToForce( GetPlayersAll(), "Some damage passes throw the runes!" )
                set passdmg = damage
                set damage = 0
            endif
        endloop
        call UnitRemoveAbility( caster,'A043')
        call UnitDamageTarget( attacker, caster, passdmg, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
    endfunction
    
    private function Trig_DoomRitesDmg_Actions takes nothing returns nothing
        local timer t = CreateTimer()
        set oldlife = GetUnitState( caster, UNIT_STATE_LIFE)
        set attacker = udg_DamageEventSource
        set damage = udg_DamageEventAmount
        call UnitAddAbility(caster,'A043')
        call TimerStart( t, 0, false, function DoomRitesDefend )
        set t = null
    endfunction
    
    private function DmgTrig_DoomRites takes nothing returns nothing
        set gg_trg_DoomRitesDmg = CreateTrigger( )
        call TriggerRegisterVariableEvent( gg_trg_DoomRitesDmg, "udg_DamageEvent", EQUAL, 1.00 )
        call TriggerAddAction( gg_trg_DoomRitesDmg, function Trig_DoomRitesDmg_Actions)
    endfunction
    
    function DoomRitesThrowMove takes nothing returns nothing
        local unit u = GetEnumUnit()
        local location t = GetUnitLoc(target)
        local location r = GetUnitLoc(u)
        local real a = bj_RADTODEG * Atan2(GetLocationY(t) - GetLocationY(r), GetLocationX(t) - GetLocationX(r))
        local real x = GetLocationX(r) + 60 * Cos(a * bj_DEGTORAD)
        local real y = GetLocationY(r) + 0 * Sin(a * bj_DEGTORAD)
        local location m = Location(x, y)
        call SetUnitPositionLoc( u, m)
        set u =null
        call RemoveLocation(t)
        call RemoveLocation(r)
        call RemoveLocation(m)
    endfunction
    
    private function DoomRitesThrowPre takes nothing returns nothing
        call ForGroup( thrown, function DoomRitesThrowMove )
    endfunction
    
    private function DoomRitesSortRunesByHealth2 takes nothing returns nothing
        local unit picked = GetEnumUnit()
        if ( GetUnitState( picked, UNIT_STATE_LIFE)<= comparehealth2) then
            set thrownrune = picked
        endif
        set picked = null
    endfunction
       
    private function DoomRitesThrowCast takes nothing returns nothing
        local timer t = CreateTimer()
        set comparehealth2 = 0
        set runes = runes
        set target = GetSpellTargetUnit()
        call DisplayTextToForce( GetPlayersAll(), "Checking Throw..." )
        set comparehealth2 = 0
        call ForGroup( runes, function DoomRitesSortRunesByHealth2)
        call DisplayTextToForce( GetPlayersAll(), "0..." )
        call GroupRemoveUnit( runes, thrownrune )
        call DisplayTextToForce( GetPlayersAll(), "1..." )
        call GroupAddUnit( thrown, thrownrune )
        call DisplayTextToForce( GetPlayersAll(), "2..." )
        call TimerStart( t, 0.03, true, function DoomRitesThrowPre )
        call DisplayTextToForce( GetPlayersAll(), "Throw. Fuck yiss." )
        set t = null
    endfunction
    
    private function DoomRitesThrowTrig takes nothing returns nothing
        set gg_trg_DoomRitesThw = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( gg_trg_DoomRitesThw, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( gg_trg_DoomRitesThw, Condition( function Trig_DoomRites_Conditions2 ) )
        call TriggerAddAction( gg_trg_DoomRitesThw, function DoomRitesThrowCast )
    endfunction
    
    private function Actions takes nothing returns nothing
        set caster = GetTriggerUnit()
        set target = GetSpellTargetUnit()
        if ( GetSpellAbilityId() == 'A05R' ) then
            call DoomRitesNewCast()
            call DmgTrig_DoomRites()
            call DoomRitesThrowTrig()
            call DisplayTextToForce( GetPlayersAll(), "Coomrites  catr" )
        endif
        call DisplayTextToForce( GetPlayersAll(), "Spellcast" )
    endfunction
    
    private function InitDoomRites takes nothing returns nothing
        local integer index
        local trigger t = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        //loop
            //call TriggerRegisterPlayerUnitEvent(gg_trg_DoomRites, Player(index), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
            //set index = index + 1
            //exitwhen index == bj_MAX_PLAYER_SLOTS
        //endloop
        call TriggerAddCondition( t, Condition( function Trig_DoomRites_Conditions ) )
        call TriggerAddAction( t, function Actions )
    endfunction

endscope
Get rid of TriggerAddActions and never use it again.
Change the condition functions.
Don't use
JASS:
GetUnitState( picked, UNIT_STATE_LIFE)<= 0)
First a unit is dead when its life is under 0.405
Use
JASS:
GetWidgetLife( unit)
Using GetWidgetLife alone will not detect dead units.
Don't use triggers called gg_trg_ they can collide with editor triggers and cause a crash.
For spells all you need is local triggers.
Function names and global variables should have first letter of each words caps.

Also take a look at my tutorial Converting GUI to efficient Jass.
Take a look at some of the spells in spell section.
 
Level 5
Joined
Oct 2, 2013
Messages
95
Not sure I understand spell completely, so warn me if I mistake.And please make your global variables' first letter capital next time(or not sure how a syntax coders prefer here) it is hard to distinct variables.

JASS:
    function DoomRitesThrowCast takes nothing returns nothing
        local timer t = CreateTimer()
        set comparehealth2 = 0
        set runes = runes
        set target = GetSpellTargetUnit()
        call DisplayTextToForce( GetPlayersAll(), "Checking Throw..." )
        set comparehealth2 = 0


        // AT THIS LINE, VARIABLE COMPAREHEALTH2 EQUALS TO 0
        

        call ForGroup( runes, function DoomRitesSortRunesByHealth2)
    endfunction

so when your code jumps to function DoomRitesSortRunesByHealth2, it looks like this


JASS:
function DoomRitesSortRunesByHealth2 takes nothing returns nothing
        local unit picked = GetEnumUnit()
        if ( GetUnitState( picked, UNIT_STATE_LIFE)<= 0) then
            set thrownrune = picked
        endif
        set picked = null
    endfunction

and I dont think your rune has 0 hp when you use that, so variable throwrune isn't getting set to any value and stays as null.

call GroupRemoveUnit( runes, thrownrune )

So when you try to use a null variable inside a function here, your code stoppes working there.

That fixed it thanks!!!

@deathismyfriend

Okay, im going to post the final result here.
 
Level 5
Joined
Oct 2, 2013
Messages
95
Hey I've just corrected all the stuff you said and they went on good...

But after a few tests i found out my spell is not MUI.

I searched online for MUI spell methods and found a few of them:

Using only local vars ( don't think it would work on my spell because it uses a TimerStart function that doesn't take any parameters, making impossible for me to transition a local var from a function to another )

Struct Indexing ( looks like my best bet )

Hashtables ( ugh )

But im not sure these are all of them and would like to know what is the best/most efficient MUI method to make a MUI vJass spell?
 
Level 12
Joined
Feb 22, 2010
Messages
1,115
Yeah, using only local variables not go with a timer.(Especially fast timers, <.20)There is nothing to learn about them anyway.

If you want to learn, struct things/timer systems are your best bet.

Hashtables are easy to learn and use like local variables.
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
Try to stay away from hashtables in spells. You can only have 255 hashtables in your map. Hashtables are really slow.

Look at my tutorial Things you should know when using Triggers / GUI. The chapter called how to index shows you how to properly make spells MUI.
You can use the same method I use for GUI / Jass / vJass / all the others.
Look at some of my spells. They are in Jass / vJass. Also if you post your spell maybe I can help to fix it.
 
Level 5
Joined
Oct 2, 2013
Messages
95
I decided to start with a much simpler spell before moving to more complex ones. This script should create units and then move them in a cone ( not the finished spell, that's all the script does ).

JASS:
scope SeventhBlade initializer InitTrig_SeventhBlade_Copy

    globals
        private integer MaxIndex
        private timer MoveTimer = CreateTimer()
        private SeventhBlade array Data[1]
    endglobals
        
    private struct SeventhBlade extends array
        private unit Caster
        private group Blades
        private real Distance
        private integer DeadBladesCount
        private group Affected

        private method SeventhBladeGo takes nothing returns nothing
            local unit u = GetEnumUnit()
            local real px = GetUnitX(u)
            local real py = GetUnitY(u)
            local real a = GetUnitFacing(u)
            local real nx = px + 25 * Cos(a * bj_DEGTORAD)
            local real ny = py + 25 * Sin(a * bj_DEGTORAD)
            call SetUnitX( u, nx )
            call SetUnitY( u, ny )
        endmethod
        
        private method SeventhBladeMove takes nothing returns nothing
            local integer i = 0
            loop
                exitwhen i == MaxIndex
                //call ForGroup( Data[i].Blades, method SeventhBladeGo )
                set i = i + 1
            endloop
        endmethod

        private method SeventhBladeAct takes nothing returns nothing
            local unit u = GetTriggerUnit()
            local integer i = 0
            local player p
            local location l
            set MaxIndex = MaxIndex + 1
            set Data[MaxIndex].Caster = u
            set l = GetUnitLoc(Data[MaxIndex].Caster)
            set p = GetOwningPlayer(Data[MaxIndex].Caster)
            call GroupClear( Data[MaxIndex].Blades )
            set Data[MaxIndex].Distance = 0
            set Data[MaxIndex].DeadBladesCount = 0
            loop
                exitwhen i == 7
                call GroupAddUnit( Data[MaxIndex].Blades, CreateUnitAtLoc( p, 'e00Q', l, ((GetUnitFacing(Data[MaxIndex].Caster) - 50) + (14.3 * I2R(i))))
                //call GroupClear( Data[MaxIndex].Affected[i] )
                set i = i + 1
            endloop
            call TimerStart( MoveTimer, 0.03, true, method SeventhBladeMove )
            set u = null
        endmethod

        private method Trig_SeventhBlade_Actions takes nothing returns boolean
            if ( GetSpellAbilityId() == 'A05T' ) then
                call SeventhBladeAct()
            endif
            return false
        endmethod
    endstruct
//===========================================================================
    private function InitTrig_SeventhBlade_Copy takes nothing returns nothing
        set gg_trg_SeventhBlade_Copy = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( gg_trg_SeventhBlade_Copy, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( gg_trg_SeventhBlade_Copy, Condition( method Trig_SeventhBlade_Actions ) )
    endfunction
//===========================================================================
endscope

Is this the right way to do dynamic indexing? I get some errors saying that "something doesn't support syntax" and I can't create array variables inside the struct i made.
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
Hard to tell from the way your thing is coded.
You never call end.
You never loop.

You are storing everything correctly.
Put this in an ITE that checks if the max index is greater than one so you don't keep starting the timer.
JASS:
call TimerStart( MoveTimer, 0.03, true, method SeventhBladeMove )
You do not need a unit group here.

Don't use locations.
 
Level 5
Joined
Oct 2, 2013
Messages
95
Okay I'm back.

That spell of mine that moves projectiles in a cone is not working and I dunno how to fix that crap! The code:

JASS:
scope SeventhBladeSpell

    globals
        private timer MoveTimer = CreateTimer()
        private integer Index = 0
        //private SeventhBladeData array Spell
    endglobals
    
    private struct SeventhBladeData
        unit Caster
        group Blades
        real Distance
        integer DeadBladesCount
        group Affected
        //private timer MoveTimer = CreateTimer()
        //private integer Index = 0
        static thistype array Spell[1]

        static method move takes nothing returns nothing
            local unit u = GetEnumUnit()
            local real px = GetUnitX(u)
            local real py = GetUnitY(u)
            local real a = GetUnitFacing(u)
            local real nx = px + 25 * Cos(a * bj_DEGTORAD)
            local real ny = py + 25 * Sin(a * bj_DEGTORAD)
            call SetUnitX( u, nx )
            call SetUnitY( u, ny )
        endmethod
        
        static method pick takes nothing returns nothing
            local group g
            local thistype data
            local integer i = 0
            loop
                exitwhen i == Index
                set data = Spell[Index]
                set g = data.Blades
                call ForGroup( g, function thistype.move )
                set i = i + 1
            endloop
            call DestroyGroup(g)
        endmethod

        static method create takes unit caster returns thistype
            local thistype tempdat
            local integer i = 0
            local player p
            local real x
            local real y
            set Index = Index + 1
            set tempdat.Caster = caster
            set x = GetUnitX( caster )
            set y = GetUnitY( caster )
            set p = GetOwningPlayer( caster )
            call GroupClear( tempdat.Blades )
            set tempdat.Distance = 0
            set tempdat.DeadBladesCount = 0
            loop
                exitwhen i == 7
                call GroupAddUnit( tempdat.Blades, CreateUnit( p, 'e00Q', x, y, ((GetUnitFacing( caster ) - 50) + (14.3 * I2R(i)))))
                //call GroupClear( tempdat.Affected[i] )
                set i = i + 1
            endloop
            //set Spell[Index] = tempdat
            set caster = null
            return tempdat
        endmethod
        
        static method action takes nothing returns nothing
            set Spell[Index] = thistype.create( GetTriggerUnit())
            call TimerStart( MoveTimer, 0.03, true, function thistype.pick )
        endmethod
        
        static method condition takes nothing returns boolean
            if ( GetSpellAbilityId() == 'A05T' ) then
                call thistype.action()
            endif
            return false 
        endmethod
        
        static method onInit takes nothing returns nothing
            //private thistype array Spell[1]
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ( gg_trg_SeventhBlade_Copy, EVENT_PLAYER_UNIT_SPELL_EFFECT )
            call TriggerAddCondition( gg_trg_SeventhBlade_Copy, Condition( function thistype.condition ) )
            set Index = 0
            set MoveTimer = CreateTimer()
            set t = null
        endmethod
        
    endstruct
    
    globals
        private SeventhBladeData array Spell[1]
    endglobals

endscope

No matter where I declare variable Spell ( a struct array ), JassHelper says it has not been declared! WTF!?? I tried declaring it in globals, in the onInit trigger, inside the struct as a static var... nothing worked.

Then I have up on that spell and decided to make another one without using methods.

This spell draws an pentagram on a target area. That's it for now. I still gotta do the damage and deindexing.

JASS:
 scope DemonicPentagram initializer DemonicPentagram
    
    globals
        // Configuration Starts
        // "Size" defines the radius of the cursed area.
        private real Size = 500
        //Configuration End
    endglobals
    
    private struct Pentagram extends array
        unit Caster
        unit Draw
        unit Dummy
        group Effects
        real Distance
        integer SidesDrawn
    endstruct
    
    globals
        private timer Periodic = CreateTimer()
        private integer Index = 0
        private real TriSideSize
        private boolean Active = false
        private Pentagram array Data[8192]
    endglobals
    
    private function DemonicPentagramBoom takes nothing returns nothing
        local unit u = GetEnumUnit()
        local real x = GetUnitX(u)
        local real y = GetUnitY(u)
        local effect s = AddSpecialEffect( "Abilities\\Spells\\Human\\Flare\\FlareCaster.mdl", x, y )
        call DestroyEffect( s )
        call KillUnit(u)
        set u = null
    endfunction

    private function DemonicPentagramDraw takes nothing returns nothing
        local integer i = 1
        local real ox
        local real oy
        local real nx
        local real ny
        local real a
        local player p
        loop
            exitwhen i > Index
            call DisplayTextToPlayer( GetLocalPlayer(),0,0, I2S(i))
            set p = GetOwningPlayer( Data[i].Caster )
            set ox = GetUnitX( Data[i].Draw )
            set oy = GetUnitY( Data[i].Draw )
            set a = GetUnitFacing( Data[i].Draw )
            set nx = ox + 10 * Cos( a * bj_DEGTORAD)
            set ny = oy + 10 * Sin( a * bj_DEGTORAD)
            call SetUnitPosition( Data[i].Draw, nx, ny )
            call GroupAddUnit( Data[i].Effects, CreateUnit( p, 'e00R', nx, ny, 0 ))
            set Data[i].Distance = Data[i].Distance + 10
            if ( Data[i].Distance >= TriSideSize  ) then
                set Data[i].Distance = 0
                call KillUnit( Data[i].Draw )
                set Data[i].Draw = CreateUnit( p, 'e000' , nx, ny, ( a - 144 ) )
                set Data[i].SidesDrawn = Data[i].SidesDrawn + 1
                if ( Data[i].SidesDrawn == 5 ) then
                    call ForGroup( Data[i].Effects, function DemonicPentagramBoom )
                    call KillUnit( Data[i].Draw )
                    call KillUnit( Data[i].Dummy ) 
                endif
            endif
            set i = i + 1
        endloop
    endfunction
    
    private function Trig_DemonicPentagram_Actions takes nothing returns nothing
        local integer i = 0
        local player p
        local real x
        local real y
        local real dx
        local real dy
        local location l = GetSpellTargetLoc()
        set Index = Index + 1
        set Data[Index].SidesDrawn = 0
        set Data[Index].Caster = GetTriggerUnit()
        set p = GetOwningPlayer( Data[Index].Caster )
        set x = GetLocationX( l )
        set y = GetLocationY( l )
        set Data[Index].Dummy = CreateUnit( p, 'e000', x, y , 0 )
        set Data[Index].Draw = CreateUnit( p, 'e000', x, (y-Size), 108 )
        set x = GetUnitX( Data[Index].Dummy )
        set y = GetUnitY( Data[Index].Dummy )
        set Data[Index].Distance = 0
        call DestroyGroup( Data[Index].Effects )
        set Data[Index].Effects = CreateGroup()
        loop
            exitwhen i == 360
            set dx = x + Size * Cos( i * bj_DEGTORAD)
            set dy = y + Size * Sin( i * bj_DEGTORAD)
            call GroupAddUnit( Data[Index].Effects, CreateUnit( p, 'e00R', dx, dy, 0 ))
            set i = i + 2
        endloop
        if ( Active == false ) then
            call TimerStart( Periodic, 0.03, true, function DemonicPentagramDraw )
            set Active = true
        endif
        call DisplayTextToPlayer( GetLocalPlayer(),0,0, "Aesd bacchannalia")
        call RemoveLocation(l)
        set p = null
    endfunction

    private function Trig_DemonicPentagram_Conditions takes nothing returns boolean
        if ( GetSpellAbilityId() == 'A05V' ) then
            call Trig_DemonicPentagram_Actions()
        endif
        return false
    endfunction
    
    function DemonicPentagramSizeCalc takes real size returns real
        local real tri = ( 0.809 * size ) * 2
        return tri
    endfunction
    
//===========================================================================

    private function DemonicPentagram takes nothing returns nothing
        set gg_trg_DemonicPentagram = CreateTrigger(  )
        set TriSideSize = DemonicPentagramSizeCalc( Size )
        call TriggerRegisterAnyUnitEventBJ( gg_trg_DemonicPentagram, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( gg_trg_DemonicPentagram, Condition( function Trig_DemonicPentagram_Conditions ) )
    endfunction

endscope

On this spell, event tho i successfully implemented a struct array, it's not MUI. And each time I cast it, the pentagram drawing gets faster and faster. And laggier (at the point of crashing war3).

Help please? I'm really stuck and I tried LOTS of things, none of them worked.
 
First off, let's go over quickly how structs work. Structs are packages of variables, and you can
associate them with one identity. To refer to some variable or function in a struct, you simply use
the '.' (dot) syntax.

So let's say you have this:
JASS:
struct A
    unit caster
    unit target
endstruct
And let's say you create one:
JASS:
local A data = A.create()
local A otherData = A.create()

set data.caster = Knight
set data.target = Shaman

set otherData.caster = Footman
set otherData.target = Priest

This is invalid code (unless Knight, Shaman, etc. are globals), but notice how you can have two different
"packages" containing completely separate data. "data" has a caster and target of a knight and shaman.
"otherData" has a caster and target of a footman and a priest. This is quite convenient! This means, for each
spell cast, we can have our own little collection of data. That is where structs really get their advantage.

What does .create() do? It generates a unique 'identity' for data, otherData, etc. Don't worry
about it too much for now, just think of it as a name for your collection. "data" points to one collection,
and "otherData" points to a separate one.

Notice how I didn't use extends array. That has its own purposes. Just ignore it for now. When you use
'extends array', you lose the .create() and .destroy() methods, so basically this:
JASS:
struct A extends array
    unit caster
    unit target
endstruct
Is the same thing as:
JASS:
globals
    unit array caster
    unit array target
endglobals

Alright, now let's take a look at your demonic pentagram.

Demonic Pentagram

Let's practice by making the entire spell within the struct.

So let's start off!
JASS:
    globals
        private real Size = 500
        private real TriSideSize = 0.809*Size*2
        private timer Periodic = CreateTimer()
    endglobals

    private struct Pentagram
        unit Caster
        unit Draw
        unit Dummy
        group Effects
        real Distance
        integer SidesDrawn

        private static method actions takes nothing returns nothing
            // actions
        endmethod

        private static method conditions takes nothing returns boolean
            if GetSpellAbilityId() == 'A05V' then
                call Pentagram.actions()
            endif
            return false
        endmethod

        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function Pentagram.conditions))
        endmethod

    endstruct

Methods are basically functions within a struct. Notice that all these are static.
These are exactly the same as normal functions. Methods without the 'static' keyword behave differently.
We'll go into it later.

Anyway, that will be the basic mesh of your code.
  • onInit does what it says. It is a special struct function that will run
    automatically on initialization.
    You don't have to write "initializer" or anything. Just name it onInit!
  • conditions just checks if the spell is the correct ID, and then it calls the actions.
  • actions is where the spell's main code will be.

Now let's move your "Trig_DemonicPentagram_Actions" into the "actions" method. In this example,
we'll be ignoring the rest of the struct for now. The first thing to do is to create the struct:
JASS:
    static method actions takes nothing returns nothing
        local Pentagram data = Pentagram.create()
    endmethod

This will allow us to assign some of its members. Now we're going to take a big jump. I'm just
going to translate your code over:
JASS:
    static Pentagram array instances
    static integer count = 0

    static method demonicDraw takes nothing returns nothing

    endmethod

    static method actions takes nothing returns nothing
        local Pentagram data = Pentagram.create()

        local integer i = 0
        local player p = GetTriggerPlayer()
        local real x = GetSpellTargetX()
        local real y = GetSpellTargetY()
        local real dx
        local real dy 

        set data.SidesDrawn = 0
        set data.Distance = 0
        set data.Effects = CreateGroup()
        set data.Caster = GetTriggerUnit()
        set data.Dummy = CreateUnit(p, 'e000', x, y, 0)
        set data.Draw  = CreateUnit(p, 'e000', x, (y - Size), 108)

        set count = count + 1
        set instances[count] = data

        set x = GetUnitX(data.Dummy)
        set y = GetUnitY(data.Dummy)

        loop
            exitwhen i == 360
            set dx = x + Size*Cos(i*bj_DEGTORAD)
            set dy = y + Size*Sin(i*bj_DEGTORAD)
            call GroupAddUnit(data.Effects, CreateUnit(p, 'e00R', dx, dy, 0))
            set i = i + 2
        endloop

        if count == 1 then
            call TimerStart(Periodic, 0.03, true, function Pentagram.demonicDraw)
        endif

        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Aesd bacchannalia")
        set p = null
    endmethod

That is quite a bit to take in. So let's take it in step-by-step:
  • static Pentagram array instances - This will just be the array to hold the spell instances.
    It is like your array, "Data".
  • static integer count - This will be the number of active instances. It is the same as your "Index".
  • static method demonicDraw - This will be the function that is called repeatedly from the timer.
  • In 'actions', I simply declare the locals, set data's members, then I add it to the array. It is all the same, but
    doesn't it look a lot prettier? It is a lot easier to read than using all those brackets. And the saving is a lot easier.
    All you need to pass is "data", and then you have access to all of its members!

Now let's move on to the actual timer. Notice that it is, again, a static method.
As such, we don't automagically get "data". But it isn't too difficult. We simply loop through the array
of instances, and then we do whatever we need for that particular spell cast.

JASS:
    static method demonicBoom takes nothing returns nothing
        // boom
    endmethod

    static method demonicDraw takes nothing returns nothing
        local Pentagram data

        local integer i = 1
        local real ox
        local real oy
        local real nx
        local real ny
        local real a
        local player p

        loop
            exitwhen i > count

            // retrieve the data for this spell cast
            set data = instances[i]

            set p  = GetOwningPlayer(data.Caster)
            set ox = GetUnitX(data.Draw)
            set oy = GetUnitY(data.Draw)
            set a  = GetUnitFacing(data.Draw)
            set nx = ox + 10*Cos(a*bj_DEGTORAD)
            set ny = oy + 10*Sin(a*bj_DEGTORAD)

            call SetUnitPosition(data.Draw, nx, ny)
            call GroupAddUnit(data.Effects, CreateUnit(p, 'e00R', nx, ny, 0))
            set data.Distance = data.Distance + 10

            if data.Distance >= TriSideSize then

                set data.Distance = 0
                call KillUnit(data.Draw)
                set data.Draw = CreateUnit(p, 'e000', nx, ny, (a - 144))
                set data.SidesDrawn = data.SidesDrawn + 1

                if (data.SidesDrawn == 5) then
                    call ForGroup(data.Effects, function Pentagram.demonicBoom)
                    call KillUnit(data.Draw)
                    call KillUnit(data.Dummy)

                    // deallocation
                    call data.destroy()
                    set instances[i] = instances[count]
                    set count = count - 1
                    set i = i - 1

                    if count == 0 then
                        call PauseTimer(Periodic)
                    endif
                endif

            endif

            set i = i + 1
        endloop
    endmethod

Again, this is a lot to process. It is mostly just translated code, but I've added some things.
  • demonicBoom - This will be where you will kill all the effects in the group.
  • In the start of the loop, I assign "data" to the current ith instance. So we can
    just use "data.member" for all the data. Again, this is just a lot cleaner and prettier than dealing with arrays.
  • The rest is exactly the same, just using struct members. Notice that I added a deallocation part though.
    This it to prevent the spell cast "index" from going continuously up. Without deallocation, it would hit 8192 after 8192
    spell casts. That is the limit for arrays. After that, it would stop working!
    Deallocation keeps recycling the data and indexes, so you can use them later.
  • data.destroy(): think of it as cleaning up a leak. This doesn't really clear memory, but it frees "data"
    so we can use it again. Like arrays, structs have an 8192 limit, so we have to make sure we're conservative!
    Generally, you do this once the spell is over. That way, the next spell can use your unique 'identity', but overwrite
    its data with its own. :)
  • The instances = instances[count], and reducing count/i by 1 is also just part of the recycling process.
    If you need an explanation, I highly recommend looking at this tutorial:
    http://www.hiveworkshop.com/forums/...orials-279/visualize-dynamic-indexing-241896/
    It will have pictures to show what happens. Just picture "Index" as the struct instead. But it is essentially the same process!
    [*] if count == 0 then - If there aren't any active spell instances, just pause the timer.
    Otherwise you'll be wasting performance doing nothing productive.


Now to finish the spell off:
JASS:
    static method demonicBoom takes nothing returns nothing
        local unit u = GetEnumUnit()
        local real x = GetUnitX(u)
        local real y = GetUnitY(u)
        call DestroyEffect(AddSpecialEffect( "Abilities\\Spells\\Human\\Flare\\FlareCaster.mdl", x, y))
        set u = null
    endmethod

That is about it. In the end, your spell will look something like this:
JASS:
scope Pentagram

    globals
        private real Size = 500
        private real TriSideSize = 0.809*Size*2
        private timer Periodic = CreateTimer()
    endglobals

    private struct Pentagram
        unit Caster
        unit Draw
        unit Dummy
        group Effects
        real Distance
        integer SidesDrawn

        static Pentagram array instances
        static integer count = 0

        static method demonicBoom takes nothing returns nothing
            local unit u = GetEnumUnit()
            local real x = GetUnitX(u)
            local real y = GetUnitY(u)
            call DestroyEffect(AddSpecialEffect( "Abilities\\Spells\\Human\\Flare\\FlareCaster.mdl", x, y))
            set u = null
        endmethod

        static method demonicDraw takes nothing returns nothing
            local Pentagram data

            local integer i = 1
            local real ox
            local real oy
            local real nx
            local real ny
            local real a
            local player p

            loop
                exitwhen i > count

                // retrieve the data for this spell cast
                set data = instances[i]

                set p  = GetOwningPlayer(data.Caster)
                set ox = GetUnitX(data.Draw)
                set oy = GetUnitY(data.Draw)
                set a  = GetUnitFacing(data.Draw)
                set nx = ox + 10*Cos(a*bj_DEGTORAD)
                set ny = oy + 10*Sin(a*bj_DEGTORAD)

                call SetUnitPosition(data.Draw, nx, ny)
                call GroupAddUnit(data.Effects, CreateUnit(p, 'e00R', nx, ny, 0))
                set data.Distance = data.Distance + 10

                if data.Distance >= TriSideSize then

                    set data.Distance = 0
                    call KillUnit(data.Draw)
                    set data.Draw = CreateUnit(p, 'e000', nx, ny, (a - 144))
                    set data.SidesDrawn = data.SidesDrawn + 1

                    if (data.SidesDrawn == 5) then
                        call ForGroup(data.Effects, function Pentagram.demonicBoom)
                        call KillUnit(data.Draw)
                        call KillUnit(data.Dummy)

                        // deallocation
                        call data.destroy()
                        set instances[i] = instances[count]
                        set count = count - 1
                        set i = i - 1
                    endif

                endif

                set i = i + 1
            endloop
        endmethod

        static method actions takes nothing returns nothing
            local Pentagram data = Pentagram.create()

            local integer i = 0
            local player p = GetTriggerPlayer()
            local real x = GetSpellTargetX()
            local real y = GetSpellTargetY()
            local real dx
            local real dy 

            set data.SidesDrawn = 0
            set data.Distance = 0
            set data.Effects = CreateGroup()
            set data.Caster = GetTriggerUnit()
            set data.Dummy = CreateUnit(p, 'e000', x, y, 0)
            set data.Draw  = CreateUnit(p, 'e000', x, (y - Size), 108)

            set count = count + 1
            set instances[count] = data

            set x = GetUnitX(data.Dummy)
            set y = GetUnitY(data.Dummy)

            loop
                exitwhen i == 360
                set dx = x + Size*Cos(i*bj_DEGTORAD)
                set dy = y + Size*Sin(i*bj_DEGTORAD)
                call GroupAddUnit(data.Effects, CreateUnit(p, 'e00R', dx, dy, 0))
                set i = i + 2
            endloop

            if count == 1 then
                call TimerStart(Periodic, 0.03, true, function Pentagram.demonicDraw)
            endif

            call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Aesd bacchannalia")
            set p = null
        endmethod

        private static method conditions takes nothing returns boolean
            if GetSpellAbilityId() == 'A05V' then
                call Pentagram.actions()
            endif
            return false
        endmethod

        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function Pentagram.conditions))
        endmethod

    endstruct

endscope

There might be syntax errors though. I didn't even test if it worked or compiled.

But hopefully that'll show you a bit how structs can be used.

-------

Now to answer your question: why is it lagging? Well, without deindexing, it will be looping through the actions,
trying to perform actions on units that are already dead. This can cause a whole slew of problems. Pro tip: always
implement deindexing right when you create the timer function. It is a good habit. That way, everything won't keep going
on forever.

Another reason: make sure your dummies are set to "Does not raise, does not decay" in the object editor. This can also
save some performance when the dummies are dying.

Also, creating 180 dummies is a lot. Consider reducing it to like 30 or something. If you end up playing it on a
crappy computer, it can cause serious lag spikes.

Why is it getting faster? No clue. It could be confounded with the lag (since lag will sometimes freeze, and then speed
things up for a brief moment).

Let me know if you have any more issues. I know this is a LONG read, but once you get the basics down, you are free
to do whatever you'd like!
 
Level 5
Joined
Oct 2, 2013
Messages
95
@PurgeandFire

THANKS ALOT!

That tut of yours really helped. I got my spells fixed and even uploaded one of them : Demonic Pentagram

However, I still got some questions:

1. - Is it any better to create spells inside a struct ( using only methods - no functions )

2. - See post #13 first issue - still not able to solve it :(

3. - This is another issue with structs:
JASS:
    private function DoomRitesRotationPre takes nothing returns nothing
        local boolean empty
        local unit u
        local group g = CreateGroup()
        local real x
        local real y
        local integer i = 1
        loop
            exitwhen i > Index
            set empty = true
            set g = Data[i].RunesGroup
            set u = null
            loop
                set u = FirstOfGroup( g )
                exitwhen u == null
                call DisplayTextToForce( GetPlayersAll(), "ROTATION" )
                set x = GetUnitX(Data[i].Caster) + 100 * Cos((Data[i].Rotation + ( 120 * Data[i].RuneCount)) * bj_DEGTORAD)
                set y = GetUnitY(Data[i].Caster) + 100 * Sin((Data[i].Rotation + ( 120 * Data[i].RuneCount)) * bj_DEGTORAD)
                set Data[i].RuneCount = Data[i].RuneCount + 1
                call DisplayTextToForce( GetPlayersAll(), I2S(Data[i].RuneCount))
                set Data[i].Rotation = Data[i].Rotation + 1
                call SetUnitPosition( u, x, y )
                set empty = false
                call GroupRemoveUnit( g, u )
            endloop
            //call ForGroup( Data[i].RunesGroup, function DoomRitesRotateGo)
            call DisplayTextToForce( GetPlayersAll(), I2S(Data[i].RuneCount))
            if ( Data[i].RuneCount == 0 ) then
                set Data[i] = Data[Index]
                call Data[Index].destroy()
                set Index = Index - 1
                call DisplayTextToForce( GetPlayersAll(), "Isdead!" )
                if ( Index == 0 ) then
                    call PauseTimer( Periodic )
                    call DestroyTrigger( gg_trg_DoomRitesThw )
                    call DestroyTrigger( gg_trg_DoomRitesDmg )
                    call DisplayTextToForce( GetPlayersAll(), "Isdead!" )
                endif
                set i = i - 1
            endif
            set Data[i].RuneCount = 0
            set i = i + 1
        endloop
        call DestroyGroup(g)
        set u = null
        set g = null
    endfunction

The second loop doesn't seem to be working. It prints this:

ROTATION
1
ROTATION
2
ROTATION
3
3
0
isDead!
isDead!

So even when the group is not empty, it says it is. And it prints the last "3" twice. And when the loop ends, the .RuneCount variable is 0, when it was set to 3 during its last "set" ( inside the loop ) and not altered after that. I don't understand.

Thanks for all the help!
 
Level 12
Joined
Feb 22, 2010
Messages
1,115
1) Wait for a Vjasser for this

2) Again, wait for a Vjasser for this, but if I remember correctly when you declare a struct variable with array, you need to specify its size, for example.

private SeventhBladeData array Spell[20]

If you want to know why this is needed go read jasshelper manual for details.
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
1) It is basically the same. I prefer it inside a struct as it looks nicer and is easier to maintain.

2) Your struct is private. It will not allow you to read the struct as it cannot see it.
You can use an integer array to store your struct ids in it.
Example:
JASS:
globals
    private integer array Structs
endglobals

private method loadStruct takes nothing returns nothing
    local thistype this = Structs[0]
endmethod

You should also look up how to extend array for structs as it is a lot better to use.
 
About #3. It is because the group is empty.

When you do this:
JASS:
local group g = CreateGroup()
// ...
set g = Data[i].RunesGroup
You aren't actually making a copy of the group. First, a group is created and stored in memory. Then you abandon it (== leak), and assign it to the group that Data.RunesGroup points to.

So it actually goes through the loop the first time, and then on the next tick of the timer, it realizes the group is empty so it immediately exits (and RuneCount == 0). Then it does the "isDead" messages, etc.

Why? Both variables point to the same group. So if you change g, you are changing Data.RunesGroup. Here is a quick explanation why:

Variables in JASS are pointers. As such, they never really "hold" a value. They simply point to some object in memory.

Reading a variable is kind of like reading a sign that "points" in the direction of the bathroom. The sign isn't the bathroom itself,
but it gives you the information on where it is. Variables do the exact same thing. They just tell you where it is in memory.

Now, when you first assign g, this is what the environment looks like:
attachment.php


g points to an empty group.
The RunesGroup (Data.RunesGroup) points to some filled group.

When you reassign g, you actually abandon the "Empty Group", and then it points to the same group as RunesGroup.
attachment.php


It doesn't make a copy. As such, g == RunesGroup.

And when you reassign a variable, nothing happens to the old group. It just stays there in memory. That is what we refer to as a leak.
To prevent it, you would just destroy the group before reassigning it.

Anyway, basically if you modify g, you are modifying the same object in memory as RunesGroup points to. Any changes you make
will affect the other variable since they both point to the same object.


Anyway, the best fix would be to simply use ForGroup() to run those actions. It will allow you to perform actions on the group without
removing any units from it. It is also possible to make a copy of the group, but that is wasteful and rather ugly.
JASS:
globals
    private Data data // rename Data to whatever your struct is named
endglobals

private function DoomGroupRotation takes nothing returns nothing
    local unit dummy = GetEnumUnit()
    local real x = GetUnitX(data.Caster) + 100*Cos((data.Rotation + (120*data.RuneCount)*bj_RADTODEG))
    local real y = GetUnitX(data.Caster) + 100*Sin((data.Rotation + (120*data.RuneCount)*bj_RADTODEG))
    set data.RuneCount = data.RuneCount + 1
    set data.Rotation = data.Rotation + 1
    call SetUnitPosition(dummy, x, y)
    set dummy = null
endfunction

//... in the loop
set data = Data[i]
call ForGroup(Data[i].RunesGroup, function DoomGroupRotation)

In the excerpt above, you simply assign Data to a temporary variable, and then use it in the ForGroup callback.
It is still MUI because the ForGroup() is evaluated instantly before any of the actions in the loop continue.
 

Attachments

  • EnvDiagram1.png
    EnvDiagram1.png
    13.5 KB · Views: 114
  • EnvDiagram2.png
    EnvDiagram2.png
    14.3 KB · Views: 113
Status
Not open for further replies.
Top